7、生命周期与钩子:Gradle构建阶段、Project与Settings回调、TaskGraph监听、buildFinished与buildStarted
Gradle 的构建过程,说白了就是一场精心编排的舞台剧。每个演员(Task)什么时候上场,什么时候退场,都有严格的剧本。这个剧本,就是 Gradle 的生命周期。
我刚开始接触 Gradle 时,总觉得它像个黑盒子。点一下运行,哗啦啦一堆日志,然后就出 APK 了。至于中间发生了什么,完全没概念。直到有一次,我在自定义插件里写了个耗时操作,结果构建卡死,我才意识到——不了解生命周期,你连问题出在哪都找不到。
这一章,我们就来拆解 Gradle 的构建阶段,以及那些关键的钩子回调。掌握了这些,你就能在构建的任意时刻插入自己的逻辑。
7.1 三大构建阶段
Gradle 的构建过程分为三个阶段:初始化(Initialization)、配置(Configuration)、执行(Execution)。嗯,简称 ICE,好记。
| 阶段 | 做什么 | 关键对象 |
|---|---|---|
| 初始化 | 解析 settings.gradle,确定哪些项目参与构建 | Settings |
| 配置 | 解析每个项目的 build.gradle,创建 Task 对象 | Project |
| 执行 | 按依赖顺序运行选中的 Task | Task |
你想想看,这三个阶段是串行的。配置阶段没跑完,执行阶段绝对不会开始。我在项目中遇到过有人想在配置阶段打印 Task 的执行结果——那当然打印不出来,因为 Task 还没跑呢。
7.2 初始化阶段与 Settings 回调
初始化阶段,Gradle 会先找 settings.gradle(或 settings.gradle.kts)。这个文件决定了你的项目是单模块还是多模块。
我个人习惯在 settings.gradle.kts 里做两件事:
- 声明模块路径
- 配置插件管理仓库
但很多人不知道,settings.gradle 里也能挂回调。比如 gradle.settingsEvaluated 和 gradle.projectsLoaded。
// settings.gradle.kts
gradle.settingsEvaluated {
println("Settings 已解析完成")
}
gradle.projectsLoaded {
println("所有 Project 对象已创建")
}
这两个回调有什么用?举个例子。我曾经需要根据 settings 里的某个属性,动态决定是否加载某个模块。在 settingsEvaluated 里判断,就能提前做决策,避免无效加载。
7.3 配置阶段与 Project 回调
配置阶段是 Gradle 最“热闹”的阶段。每个模块的 build.gradle 都会被解析,所有的 Task 对象被创建,依赖关系被建立。
这个阶段有几个重要的钩子:
project.beforeEvaluate:在 Project 被配置之前触发project.afterEvaluate:在 Project 配置完成后触发gradle.projectsEvaluated:所有 Project 都配置完成后触发
看个例子:
// 在 app/build.gradle.kts 中
project.beforeEvaluate {
println("${project.name} 即将开始配置")
}
project.afterEvaluate {
println("${project.name} 配置完成")
}
gradle.projectsEvaluated {
println("所有项目配置完毕")
}
这里有个坑,我必须要说。 beforeEvaluate 这个回调,如果你写在 build.gradle 文件里,它可能不会执行。为什么?因为当 Gradle 开始解析这个文件时,配置阶段已经开始了,beforeEvaluate 的时机已经错过了。
build.gradle 里写 beforeEvaluate,调试了半天就是不触发。后来才发现,这个回调只能在插件或 settings.gradle 里注册才有效。切记!
7.4 TaskGraph 监听
配置阶段结束后,Gradle 会生成一个完整的 Task 依赖图(TaskGraph)。这个图决定了 Task 的执行顺序。
TaskGraph 提供了两个关键回调:
whenReady:Task 图构建完成,但还没开始执行beforeTask/afterTask:每个 Task 执行前后触发
gradle.taskGraph.whenReady { graph ->
println("Task 图已就绪,共 ${graph.allTasks.size} 个 Task")
}
gradle.taskGraph.beforeTask { task ->
println("即将执行: ${task.name}")
}
gradle.taskGraph.afterTask { task ->
println("执行完毕: ${task.name}")
}
我个人觉得 whenReady 特别有用。比如你想在构建开始前检查一下,有没有某个 Task 被包含进来。如果没有,就提前报错,而不是等到执行到一半才崩溃。
举个例子:
gradle.taskGraph.whenReady {
if (hasTask(":app:assembleRelease")) {
println("Release 构建已确认")
} else {
throw GradleException("缺少 assembleRelease Task,请检查配置")
}
}
嗯,这种“提前校验”的思路,能帮你省下不少调试时间。
7.5 buildFinished 与 buildStarted
这两个回调,一个在构建开始前,一个在构建结束后。它们属于 Gradle 生命周期里最外层的钩子。
gradle.buildStarted:构建开始前触发(注意:这个回调在 settings 或 init script 中注册才有效)gradle.buildFinished:构建结束后触发,无论成功还是失败
// 在 init.gradle 或 settings.gradle 中
gradle.buildStarted {
println("构建开始于 ${System.currentTimeMillis()}")
}
gradle.buildFinished { result ->
println("构建结束,状态: ${result.failure ? "失败" : "成功"}")
if (result.failure != null) {
println("异常信息: ${result.failure.message}")
}
}
我曾经用 buildFinished 做过一个自动上报构建耗时的小工具。每次构建完,把耗时、成功/失败状态、模块数量等信息,通过 HTTP 发到团队的后台。这样就能统计出哪个模块构建最慢,哪个开发者经常构建失败。
7.6 一张图看懂生命周期
说了这么多,不如一张图来得直观。下面是我画的 Gradle 构建生命周期流程图,包含了各个阶段和关键回调的位置。
7.7 实战:用生命周期钩子做构建耗时统计
光说不练假把式。我们结合前面学的,写一个简单的构建耗时统计插件。
// BuildTimerPlugin.kt
class BuildTimerPlugin : Plugin<Project> {
override fun apply(project: Project) {
val startTime = System.currentTimeMillis()
project.gradle.buildStarted {
println("构建开始")
}
project.gradle.taskGraph.whenReady {
println("Task 图就绪,准备执行")
}
project.gradle.buildFinished { result ->
val duration = System.currentTimeMillis() - startTime
println("构建耗时: ${duration}ms")
if (result.failure != null) {
println("构建失败: ${result.failure.message}")
}
}
}
}
这个插件虽然简单,但很实用。我在团队里推广过,大家反馈说能直观看到每次构建花了多久,对优化构建速度很有帮助。
afterEvaluate 里注册 buildFinished。这样就不会影响其他模块。
好了,这一章的内容就到这里。生命周期这东西,刚开始觉得抽象,用多了就会发现——它其实就是 Gradle 给你留的一扇窗。透过这扇窗,你可以在构建的任意时刻做你想做的事。
公众号:蓝海资料掘金营,微信deep3321