8、构建生命周期:初始化阶段、配置阶段、执行阶段、生命周期钩子、监听器与回调

Gradle 的构建生命周期,说白了就是它从「接到任务」到「干完活」的完整流程。很多新手觉得这玩意儿抽象,不就是跑个任务吗?其实不然。我见过不少团队,因为不理解生命周期,在配置阶段写了不该写的逻辑,结果每次构建都慢得像蜗牛。

今天我们就把它彻底拆开。你想想看,Gradle 执行一次构建,到底经历了什么?

8.1 三个阶段:初始化、配置、执行

Gradle 的构建过程,严格分为三个阶段。每个阶段干的事完全不同。

阶段 核心工作 典型耗时
初始化阶段 解析 settings.gradle,确定哪些项目参与构建 毫秒级
配置阶段 执行所有 build.gradle,生成 Task 依赖图 秒级(项目越大越慢)
执行阶段 按依赖顺序执行选中的 Task 取决于任务本身

初始化阶段:Gradle 会先找 settings.gradle 文件。如果找不到,就认为这是一个单项目构建。我个人习惯在 settings.gradle 里只做项目声明,别写复杂逻辑。为什么呢?因为初始化阶段一旦出错,整个构建直接崩掉。

配置阶段:这是最容易被误解的阶段。很多人以为 build.gradle 里的代码只在需要时才执行。错!配置阶段会执行所有 build.gradle 里的脚本,不管这个 Task 最终会不会被执行。我在项目中遇到过,有人在配置阶段写了个耗时的网络请求,结果每次编译都要等 10 秒。其实完全可以用 doFirstdoLast 挪到执行阶段去。

执行阶段:只有这个阶段,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 图,把整个生命周期串起来了。你可以看到三个阶段、钩子、监听器分别在什么位置触发。

Gradle 构建生命周期全景 初始化阶段 解析 settings.gradle 配置阶段 执行 build.gradle 执行阶段 运行 Task 生命周期钩子 beforeSettings settingsEvaluated projectsLoaded projectsEvaluated 监听器与回调 buildStarted 构建开始 TaskExecutionListener Task 执行前后 buildFinished 构建结束 配置阶段只做声明,执行阶段才做动作 —— 这是 Gradle 性能优化的核心

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