8、构建生命周期:初始化阶段、配置阶段、执行阶段、生命周期钩子、监听器与回调
Gradle 的构建生命周期,说白了就是它从「接到任务」到「干完活」的完整流程。很多新手觉得这玩意儿抽象,不就是跑个任务吗?其实不然。我见过不少团队,因为不理解生命周期,在配置阶段写了不该写的逻辑,结果每次构建都慢得像蜗牛。
今天我们就把它彻底拆开。你想想看,Gradle 执行一次构建,到底经历了什么?
8.1 三个阶段:初始化、配置、执行
Gradle 的构建过程,严格分为三个阶段。每个阶段干的事完全不同。
| 阶段 | 核心工作 | 典型耗时 |
|---|---|---|
| 初始化阶段 | 解析 settings.gradle,确定哪些项目参与构建 | 毫秒级 |
| 配置阶段 | 执行所有 build.gradle,生成 Task 依赖图 | 秒级(项目越大越慢) |
| 执行阶段 | 按依赖顺序执行选中的 Task | 取决于任务本身 |
初始化阶段:Gradle 会先找 settings.gradle 文件。如果找不到,就认为这是一个单项目构建。我个人习惯在 settings.gradle 里只做项目声明,别写复杂逻辑。为什么呢?因为初始化阶段一旦出错,整个构建直接崩掉。
配置阶段:这是最容易被误解的阶段。很多人以为 build.gradle 里的代码只在需要时才执行。错!配置阶段会执行所有 build.gradle 里的脚本,不管这个 Task 最终会不会被执行。我在项目中遇到过,有人在配置阶段写了个耗时的网络请求,结果每次编译都要等 10 秒。其实完全可以用 doFirst 或 doLast 挪到执行阶段去。
执行阶段:只有这个阶段,Task 的 action 才会真正运行。Gradle 会根据依赖图,决定哪些 Task 需要执行,哪些可以跳过(比如输入输出没变化)。
核心原则:配置阶段只做「声明」,执行阶段才做「动作」。把耗时操作放在配置阶段,是性能杀手。
8.2 生命周期钩子:在关键时刻插入逻辑
Gradle 提供了几个钩子,让你能在不同阶段插入自定义逻辑。说白了,就是「在某个时间点,帮我干点事」。
常用的钩子有这些:
settingsEvaluated:settings.gradle 执行完毕后触发projectsLoaded:所有项目对象创建完毕beforeSettings:settings.gradle 执行前projectsEvaluated:所有 build.gradle 执行完毕buildFinished:构建结束(无论成功还是失败)
举个例子,我想在所有项目配置完成后,打印出所有 Task 的名字:
gradle.projectsEvaluated {
rootProject.allprojects { project ->
project.tasks.each { task ->
println "Task: ${task.path}"
}
}
}
嗯,这里要注意:projectsEvaluated 是在配置阶段结束时触发的。如果你在这个钩子里修改 Task 的配置,是来得及的。但如果你试图添加新的 Task,就得小心了——有些场景下可能不会生效。
我的经验:钩子虽好,不要滥用。我见过有人把业务逻辑写在 buildFinished 里,结果构建失败时还发了一堆错误通知。钩子适合做「监控」和「日志」,不适合做「核心业务」。
8.3 监听器与回调:更细粒度的控制
钩子是「粗粒度」的,监听器则是「细粒度」的。你可以监听某个 Task 的执行开始、结束、甚至失败。
Gradle 提供了 TaskExecutionListener 接口:
gradle.addListener(new TaskExecutionListener() {
@Override
void beforeExecute(Task task) {
println "开始执行: ${task.name}"
}
@Override
void afterExecute(Task task, TaskState state) {
if (state.failure) {
println "执行失败: ${task.name}"
} else {
println "执行成功: ${task.name}"
}
}
})
我曾经在一个大型微服务项目里,用监听器统计每个模块的编译时间。结果发现某个模块的编译时间异常长,排查后发现是 annotation processor 配置错了。如果没有监听器,这种问题很难定位。
除了 Task 监听器,还有 BuildListener:
buildStarted:构建开始buildFinished:构建结束projectsLoaded:项目加载完毕projectsEvaluated:项目配置完毕
这些监听器,本质上就是回调函数。Gradle 在特定事件发生时,会调用你注册的方法。
避坑指南:我曾经在 buildFinished 里调用了外部 API 发送构建报告。结果有一次网络超时,导致整个构建进程卡住。后来我加了个超时机制,并且用异步线程处理。记住:监听器里的代码,不要阻塞主流程。
8.4 一张图看懂生命周期
下面这张 SVG 图,把整个生命周期串起来了。你可以看到三个阶段、钩子、监听器分别在什么位置触发。
8.5 实战:用生命周期做性能监控
光说不练假把式。我分享一个实际用过的监控脚本。把它放在 build.gradle 的根项目里,就能统计每个 Task 的执行时间:
def taskTimes = [:]
gradle.addListener(new TaskExecutionListener() {
@Override
void beforeExecute(Task task) {
taskTimes[task.path] = System.currentTimeMillis()
}
@Override
void afterExecute(Task task, TaskState state) {
def start = taskTimes.remove(task.path)
if (start != null) {
def duration = System.currentTimeMillis() - start
if (duration > 1000) {
println "⚠️ 耗时任务: ${task.path} -> ${duration}ms"
}
}
}
})
gradle.buildFinished {
println "构建完成,共监控 ${taskTimes.size()} 个任务"
}
这个脚本帮我发现过不少问题。比如有一次,某个模块的编译任务耗时 30 秒,排查后发现是依赖了不必要的插件。去掉之后,构建时间直接砍半。
小技巧:你可以把监听器注册在 init.gradle 里,这样所有项目都能生效。我习惯在团队里统一维护一个 init.gradle,里面放一些通用的监控和日志逻辑。
8.6 总结
构建生命周期,说白了就是 Gradle 的「工作流程」。理解它,你才能写出高效的构建脚本。记住三点:
- 配置阶段别干重活
- 钩子适合做「时机控制」
- 监听器适合做「监控和日志」
下次遇到构建慢的问题,先想想:是不是有人在配置阶段偷偷干了不该干的事?
公众号:蓝海资料掘金营,微信deep3321