Gradle 构建基础

Gradle 是 Android 项目的默认构建系统。很多朋友刚开始接触时,只觉得它是个“配置文件堆砌”的工具,build.gradle 里的东西照着官方文档抄就行了。但我在做模块化 CI 的那段时间发现——不了解 Gradle 的核心机制,你连编译为什么慢、依赖为什么冲突都搞不清楚。

说白了,Gradle 就是一个基于 Groovy(或 Kotlin)的构建框架。它定义了三件事:有什么(Project)、做什么(Task)、用什么(Plugin)。整个构建过程就是按剧本把 Task 串起来跑一遍。

2.1 构建生命周期:三个阶段

Gradle 的执行流程分成三个大阶段。我记得刚入职时,看到构建日志里那么多“Configured”和“Executing”,完全分不清先后。后来自己写了个自定义插件才彻底搞明白。

阶段工作内容典型事件
Initialization解析 settings.gradle,确定哪些 Project 参与构建include ':app', ':library'
Configuration解析每个 Project 的 build.gradle,创建 Project 对象和所有 Taskapply plugin, dependencies, defaultConfig
Execution根据用户输入的任务名称,找到依赖图谱并依次执行./gradlew assembleDebug

我曾犯过的错: 在配置阶段做耗时操作(比如网络请求版本号)。Gradle 的配置阶段是单线程执行的,而且不论最终执行哪个 Task,所有 Project 的配置阶段都会跑一遍。你想想看,一个多模块项目,配置阶段可能就要花几十秒。

所以经验是:不要把耗时的计算或 I/O 放到配置阶段。用 doLast 或 gradle.taskGraph.whenReady 推迟到执行阶段。

2.2 核心元素:Project、Task、Plugin

每个 build.gradle 文件对应一个 Project 对象。Project 是 Gradle 的顶层抽象,你写的所有配置本质上就是在操作这个对象。

Task 是真正干活的单元。编译、打包、签名、lint,每个动作都是一个 Task。我习惯把 Task 比喻成“流水线上的操作工人”。

Plugin 则是“插件包”,它帮你注册一堆 Task 和扩展 DSL。比如 com.android.application 就帮你生成了 assemble、compile 等几十个 Task。

// 一个简单的自定义 Task
task hello {
    doLast {
        println 'Hello from Gradle!'
    }
}

你可以在命令行 ./gradlew hello 执行它。Task 之间可以依赖:

task taskA {
    doLast { println 'A' }
}
task taskB(dependsOn: taskA) {
    doLast { println 'B' }
}

运行 taskB 时会先执行 taskA。这就是 Task 依赖图的基本原理。Gradle 会分析整个依赖图,确保每个 Task 只执行一次,且按正确的顺序。

2.3 构建脚本的核心配置

除了 Task,日常打交道最多的就是 dependencies、android、plugins 这些 DSL 块。它们本质上都是 Project 对象的扩展属性。

配置块作用常见写法
plugins声明插件(推荐新写法)id 'com.android.application' version '8.2.0'
androidAGP 的配置扩展compileSdk, defaultConfig, buildTypes
dependencies声明依赖implementation, api, annotationProcessor
ext自定义扩展属性ext.kotlinVersion = '1.9.0'

个人习惯: 我通常在根项目的 build.gradle 里用 ext 定义版本号,然后在子模块里用 rootProject.ext.kotlinVersion 引用。这样升级版本时只改一处就可以了。

注意 implementation 和 api 的区别。如果你写了一个 library 模块,implementation 会让依赖仅对当前模块可见,而 api 会传递到依赖该模块的消费者。我在项目中遇到过因为误用 api 导致依赖版本冲突的坑——最终用了 implementation 解决问题。

2.4 多项目构建:模块化的基石

模块化项目需要在 settings.gradle 里声明子模块:

rootProject.name = 'MyApp'
include ':app'
include ':library:core'
include ':library:network'

每个子模块都有自己的 build.gradle。你可以用 subprojects 块给所有子模块统一配置:

subprojects {
    apply plugin: 'java-library'
    // 公共配置...
}

注意顺序:allprojects 包括根项目,subprojects 只包括子模块。别搞混了。

2.5 属性管理与 Gradle 缓存

gradle.properties 文件是放全局配置的好地方。比如:

org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
android.useAndroidX=true
android.enableJetifier=true

我还喜欢在这里放一些构建开关:

IS_CI=false
ENABLE_R8=true

然后在 build.gradle 里通过 project.property('IS_CI') 读取。这样就能在 CI 环境和本地开发环境切换不同的配置了。

关于 Gradle 缓存,默认开启了构建缓存(Build Cache)。但要注意:缓存 key 是基于 Task 输入和输出计算的。如果 Task 声明了不稳定的输入(比如时间戳),缓存就失效了。我曾经因为一个自定义 Task 里忘了声明 outputs.upToDateWhen,导致每次构建都重新运行——白白浪费了几分钟。

关键总结:

  • Gradle 构建三阶段:Initialization → Configuration → Execution
  • Project 是核心,Task 是执行单元,Plugin 是功能包
  • 任务依赖图决定执行顺序,请确保无环
  • 多模块项目用 settings.gradle 声明,subprojects 做公共配置
  • gradle.properties 和 ext 是管理版本和开关的好方法
  • 注意缓存失效问题,尤其是自定义 Task
Gradle 构建核心概念与生命周期 初始化阶段 解析 settings.gradle 确定参与 Project 创建 Project 对象 配置阶段 解析所有 build.gradle 创建 Task 对象 构建依赖图 执行阶段 按依赖图顺序 执行每个 Task 输出产物/日志 Project 构建脚本的容器 Task 最小执行单元 Plugin 注册 Task & DSL 包含 注册 ext 扩展属性 自定义版本号等 Dependency implementation/api 三阶段串行执行,核心元素协作完成构建

这张图展示了 Gradle 的构建三阶段与 Project、Task、Plugin 的关系。看似简单的流程,实际踩过的坑可能不少。比如配置阶段如果写死了时间依赖,或者 Task 之间形成了环形依赖,Gradle 就会报错甚至死循环。我在刚接触模块化时,就因为两个模块互相声明了 implementation project 导致配置阶段直接报异常——排查了一下午才发现是循环引用。

构建基础就聊到这里。接下来你要投入实战,很快就会发现:理解了这些底层概念,才能看懂 AGP 版本适配和 CI 流水线里的各种“魔法”到底是怎么工作的。