项目小的时候就几个人,编译慢点无所谓。但当模块数超过 20、CI 流水线每天跑几十次、一次全量构建要 10 分钟以上,构建优化就成了真问题。
这篇进入专家篇,讲 Gradle 构建优化和自定义插件。目标不是让你成为 Gradle 专家,而是让你能诊断和解决真实项目里的构建慢问题。
一、为什么你的 Gradle 构建这么慢?
构建慢通常来自这几个方面:
Android 项目最常见的两个慢点:
Kotlin 编译慢:增量编译失效、Annotation Processor 全量跑。
Gradle 配置慢:每次构建都在重复做一些可以缓存的事。
二、诊断工具:先看清楚问题在哪
2.1 Build Scan
./gradlew assembleDebug --scan
Build Scan 会生成一个在线报告,告诉你每个 Task 耗时、依赖、缓存命中情况。定位瓶颈最直观。
2.2 Profile Report
./gradlew assembleDebug --profile
生成 HTML 报告,适合离线查看。
2.3 本地 Build Analyzer
Android Studio 自带 Build Analyzer,每次构建后都能看到每个模块、每个 Task 的耗时。
三、配置阶段优化
3.1 不要用 allprojects 做复杂逻辑
// 不好
allprojects {
afterEvaluate {
// 大量逻辑
}
}
allprojects 会在每个项目评估后执行,配置阶段被拖慢。
优化:把逻辑下沉到 buildSrc 或 convention plugins。
3.2 启用 Configuration Cache
# gradle.properties
org.gradle.configuration-cache=true
org.gradle.configuration-cache.parallel=true
Configuration Cache 会把配置阶段的结果缓存起来,下次构建如果脚本没变,可以直接复用。
注意:有些老插件不兼容,需要逐步迁移。
3.3 固定依赖版本
// 不好
implementation("com.squareup.retrofit2:retrofit:2.9.+")
// 好
implementation("com.squareup.retrofit2:retrofit:2.9.0")
动态版本每次都要去仓库解析,拖慢配置阶段。
四、执行阶段优化
4.1 开启 Build Cache
org.gradle.caching=true
Build Cache 能复用其他构建(甚至 CI)已经生成的产物。团队项目一定要配远程 Build Cache(如 Gradle Enterprise)。
4.2 启用 Daemon
org.gradle.daemon=true
Daemon 会保留一个 Gradle 进程,避免每次启动 JVM。
4.3 增量 Annotation Processing
KAPT(Kotlin Annotation Processing Tool)是编译慢的常见元凶。
kapt.incremental.apt=true
kapt.include.compile.classpath=false
如果项目允许,迁移到 KSP(Kotlin Symbol Processing),速度比 KAPT 快很多:
plugins {
id("com.google.devtools.ksp")
}
4.4 并行编译
org.gradle.parallel=true
org.gradle.workers.max=8
根据机器 CPU 核数调整 workers 数量。
五、模块化与 Gradle 优化
5.1 模块化分层
:app
├── :feature:home
├── :feature:login
├── :feature:profile
├── :core:network
├── :core:database
└── :core:common
每个 feature 模块可以独立编译、独立测试,改动只影响依赖它的模块。
5.2 用 Convention Plugin 统一配置
把重复配置抽成 Convention Plugin:
// buildSrc/src/main/kotlin/android-feature.gradle.kts
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
compileSdk = 34
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
// build.gradle.kts (:feature:home)
plugins {
id("android-feature")
}
这样 :feature:home 和 :feature:login 的配置完全一致,修改也只需要改一处。
六、自定义 Gradle 插件
当 Convention Plugin 还不够用时,可以写自定义 Gradle 插件给团队提效。
6.1 最小插件示例
在 buildSrc 里创建:
// buildSrc/src/main/kotlin/com/example/CheckPlugin.kt
class CheckPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("checkApiLevel") {
group = "verification"
description = "检查 API 级别是否合规"
doLast {
project.subprojects {
val compileSdk = extensions.findByType<AppExtension>()?.compileSdkVersion
println("$name compileSdk: $compileSdk")
}
}
}
}
}
注册插件:
// buildSrc/src/main/resources/META-INF/gradle-plugins/com.example.check.properties
implementation-class=com.example.CheckPlugin
使用:
plugins {
id("com.example.check")
}
6.2 自动生成 Build 信息
class BuildInfoPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("generateBuildInfo") {
doLast {
val gitCommit = "git rev-parse --short HEAD".runCommand()
val buildTime = System.currentTimeMillis()
val outputDir = project.buildDir.resolve("generated/build-info")
outputDir.mkdirs()
outputDir.resolve("BuildInfo.kt").writeText(
"""
object BuildInfo {
const val GIT_COMMIT = "$gitCommit"
const val BUILD_TIME = $buildTime
}
""".trimIndent()
)
}
}
}
}
6.3 自定义 Lint 检查
class LintConfigPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("checkForbiddenImports") {
doLast {
val forbidden = listOf("android.util.Log", "java.util.Random")
project.fileTree("src").matching {
include("**/*.kt")
}.forEach { file ->
forbidden.forEach { import ->
if (file.readText().contains(import)) {
throw GradleException("Forbidden import $import in ${file.path}")
}
}
}
}
}
}
}
七、实际优化效果参考
一个 30 模块、百万行代码的 Android 项目,经过以下优化后:
当然,具体收益取决于项目本身。但方向是对的。
小结
构建优化不是一次性工作,是持续工程:
先用 Build Scan / Build Analyzer 定位瓶颈。
配置阶段用 Configuration Cache + 固定版本 + Convention Plugin。
执行阶段用 Build Cache + KSP + 并行编译。
自定义插件把团队规范自动化。
模块化是构建优化的长期基础。
下篇预告
下一篇《第 12 篇:Native 集成:JNI、NDK、C++ 与 Kotlin 的协作》会进入 Android 的 Native 世界:
什么时候必须用 C/C++。
JNI 基本类型、引用类型、方法签名。
NDK 构建、CMake、性能与包体积权衡。
参考阅读
Gradle 官方性能优化指南
Android 官方构建优化文档
KSP 官方文档
上一篇文章:第 10 篇《Android 网络层架构:Retrofit + OkHttp + 协程的最佳实践》