3、Gradle构建生命周期:初始化阶段、配置阶段、执行阶段、生命周期监听回调、project.beforeEvaluate/afterEvaluate实战

说实话,很多Android开发者写了几年Gradle脚本,却从来没搞清楚Gradle到底是怎么运行的。你写了个task,它什么时候执行?你配置了个dependencies,它又是在哪个阶段被解析的?

我刚开始带团队的时候,有个同事跑来问我:“为什么我在build.gradle里写了个打印语句,它每次运行都输出,但我的task却有时候不执行?”

嗯,这个问题其实就指向了Gradle构建生命周期的核心。今天我们就把它彻底讲透。

Gradle构建的三个阶段

Gradle的构建过程,说白了就是三个阶段:初始化配置执行。我习惯叫它“ICE模型”,好记。

核心要点:三个阶段是顺序执行的,每个阶段都有对应的监听回调。理解了这个,你就能精准控制脚本的执行时机。

1. 初始化阶段(Initialization)

这个阶段Gradle会做两件事:

  • 确定哪些项目参与构建(对于多模块项目)
  • 为每个项目创建Project对象

说白了,Gradle先读settings.gradle,看看你有哪些子模块。然后给每个模块创建一个Project实例。这时候你的build.gradle还没被执行呢。

小技巧:如果你想在初始化阶段干点事,可以在settings.gradle里写代码。比如我经常在这里做版本检查:

// settings.gradle
if (GradleVersion.current() < GradleVersion.version("7.0")) {
    throw new GradleException("需要Gradle 7.0+,当前版本:" + GradleVersion.current())
}

2. 配置阶段(Configuration)

这是最容易被误解的阶段。很多人以为build.gradle里的代码就是“执行”的,其实不是——它是在配置阶段被执行的。

配置阶段会:

  • 执行每个项目的build.gradle脚本
  • 创建并配置所有的Task对象
  • 构建整个Task依赖图

举个例子你就明白了:

// build.gradle
println "这句话在配置阶段就会输出"

task myTask {
    println "这个println也在配置阶段执行"
    doLast {
        println "这个println在执行阶段才执行"
    }
}

运行./gradlew myTask,你会看到前两个println先输出,然后才是doLast里的内容。为什么会这样?因为Task的配置代码(花括号里的内容)是在配置阶段执行的,而doLast里的动作是在执行阶段才触发的。

我曾经踩过的坑:在配置阶段执行耗时操作,比如网络请求或文件IO。这会导致每次运行Gradle命令都很慢,哪怕你只是想看个任务列表。记住:配置阶段应该只做配置,不要做真正的“工作”。

3. 执行阶段(Execution)

这个阶段就简单了——Gradle根据你传入的任务名称和依赖关系,按顺序执行那些需要执行的Task。

注意我说的是“需要执行的”。Gradle很聪明,如果某个Task的输入输出没有变化,它就会跳过执行。这就是增量构建的核心机制。

生命周期监听回调

Gradle提供了丰富的生命周期回调,让你能在不同阶段插入自定义逻辑。我整理了一张表:

回调方法 所属对象 触发时机
settingsEvaluated Settings settings.gradle执行完毕后
projectsLoaded Gradle 所有Project对象创建完毕后
beforeSettings Settings settings.gradle执行前
beforeProject Project 每个Project配置前
afterProject Project 每个Project配置后
projectsEvaluated Gradle 所有Project配置完毕后
buildFinished Gradle 构建完成后(无论成功失败)

我个人最常用的是projectsEvaluated,因为这时候所有模块的配置都完成了,你可以拿到完整的项目信息来做一些全局操作。

project.beforeEvaluate / afterEvaluate 实战

这两个回调是Project级别的,分别在当前Project配置前后触发。注意:beforeEvaluate只能在settings.gradle或父Project中注册才有效,因为当前Project还没开始配置呢。

来看一个实战场景:

// 在根项目的build.gradle中
subprojects { subProject ->
    // 在每个子模块配置前执行
    subProject.beforeEvaluate {
        println "即将配置模块:${it.name}"
        // 可以在这里做一些前置检查
        if (!file("${it.projectDir}/src/main/AndroidManifest.xml").exists()) {
            throw new GradleException("${it.name} 缺少AndroidManifest.xml")
        }
    }

    // 在每个子模块配置后执行
    subProject.afterEvaluate {
        println "模块 ${it.name} 配置完成"
        // 这里可以读取模块的配置信息
        if (it.plugins.hasPlugin('com.android.application')) {
            println "${it.name} 是一个Application模块"
            // 可以动态修改applicationId之类的
            it.android {
                defaultConfig {
                    applicationId "com.example.${it.name}"
                }
            }
        }
    }
}

我的经验:afterEvaluate是插件开发中最常用的回调之一。比如你想在所有模块配置完成后,统一检查版本号是否一致,或者给所有模块添加一个公共的Task,这时候afterEvaluate就是最佳时机。

一张图看懂构建生命周期

下面这张SVG图,把整个流程串起来了:

Gradle构建生命周期流程图 初始化阶段 读取settings.gradle 创建Project对象 配置阶段 执行build.gradle 创建Task依赖图 执行阶段 按依赖执行Task 增量构建 生命周期回调 settingsEvaluated projectsLoaded beforeProject afterProject projectsEvaluated buildFinished beforeEvaluate / afterEvaluate 是Project级别的回调,在配置阶段触发

实战:利用生命周期做统一配置

最后分享一个我在实际项目中用的模式。我们有个多模块项目,每个模块都要配置统一的编译选项。以前是每个模块复制粘贴,后来我用afterEvaluate统一处理:

// 根项目的build.gradle
allprojects {
    afterEvaluate { project ->
        // 只处理Android模块
        if (project.hasProperty('android')) {
            project.android {
                compileSdkVersion 34
                buildToolsVersion "34.0.0"

                defaultConfig {
                    minSdkVersion 21
                    targetSdkVersion 34
                    versionCode 1
                    versionName "1.0.0"
                }

                compileOptions {
                    sourceCompatibility JavaVersion.VERSION_17
                    targetCompatibility JavaVersion.VERSION_17
                }
            }

            // 统一添加一个自定义Task
            project.tasks.register('checkModuleHealth') {
                doLast {
                    println "检查模块 ${project.name} 的健康状态..."
                    // 这里可以做lint检查、依赖分析等
                }
            }
        }
    }
}

这样每个子模块的build.gradle就只需要关注自己特有的配置,公共部分由根项目统一管理。维护起来轻松多了。

注意:afterEvaluate里修改android块的内容时,要确保子模块已经应用了Android插件。否则project.android会报空指针。我建议先检查project.plugins.hasPlugin('com.android.application')'com.android.library'

好了,关于Gradle构建生命周期,核心就是这三个阶段和对应的回调。你只要记住:配置阶段只做配置,执行阶段才做实际工作。下次再遇到“脚本执行顺序”的问题,你就能一眼看穿了。

公众号:蓝海资料掘金营,微信deep3321