11、生命周期与Hook:Gradle构建生命周期(初始化、配置、执行)、各阶段Hook方法
说实话,Gradle 的构建生命周期,是很多初学者容易忽略但又极其重要的知识点。我记得刚接触 Gradle 时,总觉得它就是个“黑盒”——点一下运行,项目就构建好了。直到有一次,我在一个大型多模块项目里遇到了诡异的配置顺序问题,才真正意识到:不理解生命周期,你根本没法掌控 Gradle。
说白了,Gradle 的构建过程就像一场精心编排的舞台剧。它有三个明确的幕次:初始化(Initialization)、配置(Configuration) 和 执行(Execution)。每一幕都有它特定的任务,而 Hook 方法就是你在每一幕中插入自己“剧本”的时机。
一、Gradle 构建的三个阶段
我们先来直观地看一下这三个阶段的关系。下面这张图是我自己画的,希望能帮你建立整体认知。
二、第一阶段:初始化(Initialization)
这个阶段 Gradle 会做两件事:第一,确定哪些项目参与构建;第二,为每个项目创建对应的 Project 对象。
它靠什么来知道有哪些项目?就是 settings.gradle 文件。Gradle 会先找到这个文件,然后解析里面的 include 语句。比如你写了 include ':app', ':library',那 Gradle 就会创建两个 Project 实例。
核心要点:初始化阶段只解析 settings.gradle,不会执行任何 build.gradle 文件。
三、第二阶段:配置(Configuration)
这是最容易被误解的阶段。很多人以为 Gradle 一运行就开始执行任务,其实不是。配置阶段的核心工作是:执行所有项目的 build.gradle 脚本,构建出完整的 Task 依赖图。
换句话说,这个阶段 Gradle 在“画地图”——它要知道有哪些任务、任务之间的依赖关系、执行顺序是什么。但注意,它只是画地图,还没有真正开始走。
我曾经踩过的坑:在配置阶段里写了耗时的逻辑,比如网络请求或文件读写。结果每次运行 Gradle 命令都特别慢,哪怕只是执行一个简单的 clean 任务。后来才明白,配置阶段的代码每次都会执行,不管你要运行哪个 Task。
四、第三阶段:执行(Execution)
到了这一步,Gradle 才真正开始干活。它会根据配置阶段生成的 Task 依赖图,按顺序执行那些被选中的 Task。比如你运行 gradle build,Gradle 就会执行 compileJava、processResources、test、assemble 等一系列任务。
执行阶段有一个重要特性:只有被命令行参数或依赖关系链上的 Task 才会被执行。没有被引用的 Task 会被跳过,不会浪费任何时间。
五、各阶段的 Hook 方法
现在问题来了:我们怎么在这些阶段的“边界”上插入自己的逻辑?Gradle 提供了丰富的 Hook 方法。我按阶段给你梳理一下。
5.1 初始化阶段的 Hook
这个阶段的 Hook 主要写在 settings.gradle 文件中。
| Hook 方法 | 触发时机 | 使用场景 |
|---|---|---|
settingsEvaluated |
settings.gradle 解析完成后 | 检查项目列表、动态添加项目 |
projectsLoaded |
所有 Project 实例创建完成后 | 对所有项目进行统一配置 |
举个例子,我在一个微服务项目中用过 settingsEvaluated 来动态排除某些模块:
// settings.gradle
rootProject.name = 'my-project'
include ':service-a', ':service-b', ':service-c'
gradle.settingsEvaluated { settings ->
// 如果当前环境是 CI,只构建 service-a
if (System.getenv('CI_ENV') == 'true') {
settings.include(':service-a')
// 注意:这里不能直接 remove,需要用 include 重新指定
}
println "项目列表已确定:${settings.rootProject.name}"
}
5.2 配置阶段的 Hook
配置阶段的 Hook 最常用,主要写在 build.gradle 文件中。
| Hook 方法 | 触发时机 | 使用场景 |
|---|---|---|
project.beforeEvaluate |
当前项目的 build.gradle 执行前 | 设置默认值、注入公共配置 |
project.afterEvaluate |
当前项目的 build.gradle 执行后 | 读取已配置的属性、动态添加 Task |
gradle.projectsEvaluated |
所有项目的 build.gradle 执行完成后 | 跨项目配置、全局检查 |
gradle.taskGraph.whenReady |
Task 依赖图构建完成后 | 检查 Task 图、修改 Task 属性 |
我个人最常用的是 afterEvaluate。为什么?因为在这个回调里,所有插件和依赖都已经配置好了,你可以安全地读取或修改它们。
// 在子项目的 build.gradle 中
this.afterEvaluate { project ->
// 确保 android 插件已经应用
if (project.plugins.hasPlugin('com.android.application')) {
android {
defaultConfig {
// 动态设置版本号
versionCode = getVersionCodeFromCI()
}
}
}
println "${project.name} 配置完成"
}
避坑指南:我曾经在 beforeEvaluate 里尝试读取 android 配置,结果报错说找不到。原因很简单——android 插件还没应用呢。所以记住:beforeEvaluate 适合做“准备工作”,afterEvaluate 适合做“收尾工作”。
5.3 执行阶段的 Hook
执行阶段的 Hook 主要围绕 Task 的执行过程。
| Hook 方法 | 触发时机 | 使用场景 |
|---|---|---|
gradle.taskGraph.beforeTask |
每个 Task 执行前 | 记录日志、检查前置条件 |
gradle.taskGraph.afterTask |
每个 Task 执行后 | 统计耗时、发送通知 |
buildFinished |
整个构建过程结束后 | 清理资源、生成报告 |
来看一个实际例子。我在团队里用 beforeTask 和 afterTask 来统计每个任务的执行时间:
// 在根项目的 build.gradle 中
gradle.taskGraph.beforeTask { task ->
task.ext.startTime = System.currentTimeMillis()
println "开始执行:${task.path}"
}
gradle.taskGraph.afterTask { task, state ->
def duration = System.currentTimeMillis() - task.ext.startTime
println "${task.path} 执行完毕,耗时 ${duration}ms"
if (state.failure) {
println "❌ ${task.path} 执行失败:${state.failure.message}"
}
}
六、完整的生命周期流程图
下面这张图把 Hook 方法和三个阶段对应起来,方便你对照理解。
七、总结
Gradle 的生命周期其实不复杂,说白了就是“先看谁参与、再画依赖图、最后干活”。但真正用好它,需要你对每个阶段的 Hook 方法有清晰的认识。
我个人习惯在项目里这样用:
- settings.gradle 里用
settingsEvaluated做环境判断 - 根项目的 build.gradle 里用
projectsEvaluated做全局检查 - 子项目的 build.gradle 里用
afterEvaluate做插件配置的收尾 - 执行阶段用
beforeTask和afterTask做性能监控
嗯,记住一点:Hook 方法不是越多越好。滥用 Hook 会让构建逻辑变得难以追踪。我见过一个项目,build.gradle 里密密麻麻全是 afterEvaluate,最后排查问题花了整整两天。所以,只在真正需要的地方使用 Hook,保持构建脚本的简洁和可读性。
公众号:蓝海资料掘金营,微信deep3321