2. Gradle 构建系统入门:Groovy 与 Kotlin DSL,构建生命周期,Task 概念

如果你做过几年 Android 开发,肯定对 Gradle 又爱又恨。爱的是它把依赖管理、构建配置全包了,恨的是报错信息有时候跟天书一样。我刚开始接触 Gradle 时也一头雾水,后来才明白——核心就三样东西:用什么语言写脚本、构建分哪几步、任务(Task)是怎么串联的。把这三个搞明白,后面讲 AGP 配置优化你才能听得下去。

2.1 Groovy DSL vs Kotlin DSL

Gradle 脚本最初只能用 Groovy 写,文件后缀是 .gradle。后来官方推出了 Kotlin DSL,后缀 .gradle.kts。目前两者都在用,但 Google 和 Gradle 官方明显在推 Kotlin DSL。

Groovy DSL

  • 语法灵活,支持闭包、动态类型、字符串可以不写括号
  • 示例:
    android {
        compileSdk 34
        defaultConfig {
            applicationId "com.example.app"
        }
    }
  • 缺点:IDE 智能提示弱,写错了很难定位

Kotlin DSL

  • 静态类型,IDE 补全一流,编译期就能发现错误
  • 示例:
    android {
        compileSdk = 34
        defaultConfig {
            applicationId = "com.example.app"
        }
    }
  • 缺点:build.gradle.kts 文件报错时堆栈偶尔有点乱

我的建议:新项目直接用 Kotlin DSL。老项目如果要迁移,可以一个模块一个模块地改,不用一次全改完。我曾经在团队里推过迁移,花了两周把所有 .gradle 转成 .gradle.kts,之后 IDE 加载速度快了,错误也少多了。

2.2 构建生命周期

Gradle 构建过程不是一把梭,它分三个阶段:初始化、配置、执行。你想想看,如果你不了解这三个阶段,想优化构建速度?没门。

第一阶段:初始化

Gradle 会找到你项目的 settings.gradle 或 settings.gradle.kts,确定有哪些模块参与构建。同时创建 Project 对象。

第二阶段:配置

每个模块的 build.gradle 会被解析执行,生成所有 Task 的配置对象。注意,这里只是配置,并没有执行 Task 的 action。我在项目中遇到过一个问题:在配置阶段写了一个很耗时的操作(比如网络请求),结果每次运行 Gradle 命令都卡半天。后来才意识到,那应该放到执行阶段去做。

第三阶段:执行

Gradle 根据用户输入的任务名(比如 ./gradlew assembleDebug),确定哪些 Task 需要执行,然后按依赖顺序依次执行它们的 action。

注意:配置阶段的代码在每个构建中都会执行,哪怕是 ./gradlew clean 这种简单命令。所以不要在这里写耗时逻辑。我曾经见过有人在配置阶段用 exec 执行 shell 命令,那构建慢得你怀疑人生。

为了更直观地理解,下面这张图展示了生命周期和 Task 的关系:

Gradle 构建生命周期与 Task 核心概念 初始化阶段 配置阶段 执行阶段 · 读取 settings.gradle · 确定参与模块 · 创建 Project 对象 · 解析 build.gradle · 配置所有 Task · 形成 Task 图 · 执行 Task action · 按依赖顺序 · 生成输出产物 Task 核心要素 Task 定义 Task 依赖 Task 动作 (action) 使用 project.task() 或 register() 创建 dependsOn 指定 必须在前执行的任务 doLast / doFirst 定义执行逻辑 配置阶段构建 Task 图,执行阶段按图执行 (画布背景 #f0f8ff,节点颜色按参考色板)

2.3 Task 核心概念

Task 是 Gradle 里最小的执行单元。你可以把它理解成一个带名字、带依赖、带动作的“工作包”。比如 assembleDebug 就是一个 Task,它依赖了很多编译、打包的 Task。

创建 Task

Groovy DSL 里用 task hello 就能创建一个 Task。Kotlin DSL 用 register("hello")。切记,创建 Task 是在配置阶段完成的,但 Task 里的 action 要等到执行阶段才跑。

示例(Kotlin DSL):

tasks.register("myTask") {
    doLast {
        println("Hello from myTask")
    }
}

Task 依赖与排序

你可以通过 dependsOn 指定前置任务。比如:

tasks.register("taskB") {
    dependsOn("taskA")
    doLast { println("B 执行") }
}

依赖会形成一个有向无环图(DAG)。Gradle 会自动处理排序,你不需要手动调顺序。

避坑指南:不要用 mustRunAfter 来替代 dependsOn。我一个同事之前为了让两个 Task 顺序执行,用了 mustRunAfter,结果发现它们根本没依赖关系,构建时只跑了一个——因为 mustRunAfter 只是排序建议,不是依赖约束。

常用内置 Task 类型

Task 名称作用
clean删除 build 目录
assemble打包所有变体
check运行所有检查(lint、test)
build先 assemble 再 check

其实,我们后面要讲的 AGP 配置优化,核心就是在配置阶段干预 Task 的创建或参数。比如去掉不需要的 Task、合并资源处理 Task、减少增量编译的损耗。你只有理解了生命周期和 Task 机制,才不会被那些 afterEvaluate、gradle.projectsLoaded 回调搞晕。

小技巧:想看看你的项目里到底有哪些 Task?运行 ./gradlew tasks 能看到全部。运行时加 --console=plain 能让输出更清爽。我平时排查构建问题,第一步就是看 Task 依赖树——./gradlew :app:dependencies 和 ./gradlew taskTree 能让你快速定位冗余环节。

好了,这一节把基础打牢。下一部分我们开始深入 AGP 的配置项本身。记住,Gradle 是个强大的工具,但它的灵活性也是双刃剑——写对了能帮我们优化包体积和构建速度,写错了…嗯,你懂的。