Skip to content

gradle-lifecycle-demo ​

1. 实验目标与工程要点 ​

用最小 Gradle 工程(一个 settings.gradle.kts + 一个 build.gradle.kts,零插件、零三方依赖)验证 Gradle 三阶段生命周期的回调输出顺序:

  1. Initialization:读取 settings.gradle.kts,确定参与构建的 project 集合;
  2. Configuration:求值每个 project 的 build script 顶层代码,注册任务,产出 Task Graph(beforeProject / afterProject / taskGraph.whenReady 都在这一阶段或其末尾触发);
  3. Execution:按拓扑序真正执行被选中的 task(doLast),最后 buildFinished。

掌握的工程决策:

  • 为什么改一行 build.gradle.kts 连增量构建都会变慢——顶层代码属于 Configuration,每次构建都重新求值;
  • 「任务注册」与「任务执行」是两个阶段的两个动作——tasks.register 在配置期、doLast 在执行期;
  • 排查「我的 println 为什么没打印 / 打印了两次」时,先判断它写在哪个阶段的作用域里。

2. 深入原理与生活浅显比喻 ​

2.1 生活浅显比喻 ​

Gradle 构建像一场婚宴筹备:

  • Initialization = 确定宾客名单(读 settings.gradle.kts,决定哪些 module 上桌);
  • Configuration = 每桌确认菜单与上菜顺序(求值所有 build script、排出任务依赖图)——这一步即使最后只上一道菜也要全桌走一遍;
  • Execution = 后厨按顺序真正做菜上菜(跑 doLast)。

2.2 底层原理分析 ​

  • Gradle 把一次构建拆成三阶段,是为了先收集完整信息再行动:只有所有 build script 都求值完(Configuration 结束),才能得到完整的任务依赖图,再按拓扑排序执行。
  • gradle.beforeProject {} / gradle.afterProject {} 是 project 配置生命周期钩子;gradle.taskGraph.whenReady {} 是配置与执行的分界线;gradle.buildFinished {} 在构建收尾触发。它们都注册在 Gradle 对象上,可以在 settings script 或 init script 中注册。
  • Kotlin DSL 的 tasks.register(x) { ... } 返回 TaskProvider,闭包在任务被实例化/执行前不会运行;而 tasks.create(x) 会立即实例化。本 lab 全部使用 register + doLast 来凸显两阶段差异。

2.3 方案对比矩阵 ​

方案通俗比喻底层原理保证特性吞吐性能运行开销适用场景
本 lab:settings/build 脚本内嵌回调婚宴流程广播Gradle 对象上的生命周期监听器输出顺序即真实阶段顺序单次小工程构建极低(几条 println)学习/验证阶段语义
init script 注入(-I hook.gradle.kts)空降观察员随行记录同一套监听器,但不动宿主脚本零侵入,任意工程可注入低低观测既有大工程(见 Android host 版)
Build Scanner / --profile赛后录像分析构建数据序列化上报时间轴+瓶颈定位中中性能调优而非机制理解

「吞吐性能」与「运行开销」分两栏,遵守仓库规范。


3. Android / 移动端实战场景与误配事故 ​

3.1 实战场景 ​

Android 工程里同样的回调常用于:

kotlin
// 根 build.gradle.kts:统计每个 module 的配置耗时
gradle.afterProject {
    if (project.path == ":app) {
        logger.lifecycle(app 模块配置完成)
    }
}

AGP 的全部 Android 任务(mergeDebugResources、compileDebugKotlin…)都在 Configuration 阶段注册进任务图——所以模块越多配置越慢,这也是 Configuration Cache 存在的意义。

3.2 常见误配与事故注意事项 ​

  • 把执行期逻辑写进配置期:build script 顶层直接 exec { } / 读网络 / 解析大文件,导致每次构建(哪怕 gradle help)都付出这份开销,且 incompatible with Configuration Cache。
  • tasks.create vs tasks.register 混用:create 在配置期就实例化任务并求值其配置闭包,破坏惰性;大工程会拖慢 Configuration。
  • 在 afterEvaluate 里又注册 afterEvaluate:嵌套回调时机难以推理,容易产生「第二次构建才生效」的诡异现象。

3.3 排障思路 ​

  • 现象:「println 打印了但我没跑那个 task」→ 它写在了 Configuration 作用域(build script 顶层或任务配置块),属正常行为;
  • 现象:「task 里改了输入却显示 UP-TO-DATE」→ 改的是执行期局部变量,未进 inputs/outputs,Gradle 认为输入未变;
  • 先看输出顺序:初始化输出 → 配置输出 → 任务行(> Task :hello)→ 收尾输出,任何乱序说明代码放错了作用域。

4. 实验源码与运行验证 ​

关键源码 ​

下面输出对应以上两份源码。

关键参数 / 开关 ​

  • gradle hello:只执行 hello 一个任务,用于观察「goodbye 已注册但未执行」;
  • --console=plain:去掉进度条等富文本干扰,让阶段输出可逐行对照;
  • -q(quiet):反向对照——quiet 下 println 仍会输出(它不走 logger),而 logger.lifecycle 会被吞掉,可用于区分两种输出通道。

运行方式 ​

环境要求:本机需安装 Gradle 8.x(或任意含 Kotlin DSL 支持的 7.x+)与 JDK 17+,无需 Android SDK、无需联网拉依赖(本 lab 零插件零依赖)。当前编写该 lab 的机器未安装 gradle,故以静态自检兜底;有 Gradle 的机器请实跑并把真实输出回填到下节。

bash
cd labs/ci/gradle-lifecycle-demo
node syntax-check.mjs        # 无 gradle 环境:括号配对 + 回调结构静态自检
gradle hello --console=plain # 有 gradle 环境:真实三阶段输出

预期控制台运行输出 ​

gradle hello --console=plain 的预期输出(首次运行会有下载 wrapper/daemon 噪音,以下为稳态形态):

text
阶段1-初始化:读取 settings.gradle.kts,rootProject=gradle-lifecycle-demo
阶段2-配置开始:beforeProject 回调 -> project「root project 'gradle-lifecycle-demo'」
阶段2-配置:build.gradle.kts 顶层代码求值(任何 task 都会触发我)
阶段2-配置结束:hello / goodbye 均已注册进任务容器(注册发生在配置期,执行发生在执行期)
阶段2-配置结束:afterProject 回调 -> project「root project 'gradle-lifecycle-demo'」,此时 build script 已全部求值、任务已注册
阶段2→3 分界:taskGraph.whenReady —— 任务图就绪,共 2 个任务待执行

> Task :hello
阶段3-执行:hello 的 doLast 正在运行(只有被选中的任务才会到这里)

阶段3-执行结束:buildFinished 回调,构建结果=成功

BUILD SUCCESSFUL
1 actionable task: 1 executed

node syntax-check.mjs 在无 Gradle 环境下的真实输出:

text
✔ 存在 settings.gradle.kts
✔ 存在 build.gradle.kts
✔ 存在 README.md
✔ settings.gradle.kts 括号配对完整
✔ build.gradle.kts 括号配对完整
✔ settings.gradle.kts 注册了 beforeProject
✔ settings.gradle.kts 注册了 afterProject
✔ settings.gradle.kts 注册了 taskGraph.whenReady
✔ settings.gradle.kts 注册了 buildFinished
✔ settings.gradle.kts 声明了 rootProject.name
✔ build.gradle.kts 惰性注册了 hello 任务
✔ hello/goodbye 使用 doLast 表达执行期行为

全部自检通过(仅静态检查;真实三阶段输出仍需 Gradle 环境验证)

预期现象与观察重点 ​

  • 预期现象:输出严格按「初始化 → 配置 → > Task :hello → buildFinished」排列;gradle help 时配置段照样打印,但没有任何 > Task :hello 执行行。
  • 观察重点:
    1. goodbye 在配置期已注册(顶层 println 可证),但 gradle hello 时它的 doLast 从不运行;
    2. taskGraph.whenReady 报告的任务数包含未执行的 goodbye——「入选任务图」≠「被执行」;
    3. 第二次运行时任务可能显示 UP-TO-DATE,但配置段输出依旧逐行重跑——这就是 Configuration Cache 要消除的部分。

5. 对应知识库文档 ​

站点构建时间:2026/8/24 23:43:17