20、Gradle Build Cache:本地构建缓存、远程构建缓存、缓存键的生成规则、缓存命中率优化、缓存清理策略
构建缓存这东西,说白了就是「让 Gradle 记住上次干活的结果」。我刚开始接触 Android 构建时,最烦的就是每次改一行代码,整个项目要重新编译好几分钟。后来我才明白——大部分时间都浪费在重复劳动上。
Gradle 的 Build Cache 就是为了解决这个问题的。它会把 task 的输出存起来,下次如果输入没变,直接拿缓存结果,跳过执行。嗯,这里要注意:缓存不是万能的,用不好反而会出问题。
20.1 本地构建缓存
本地缓存是最简单的形式。Gradle 默认会在 ~/.gradle/caches/build-cache-1 目录下存缓存文件。你什么都不用配,它就自动工作了。
我个人习惯在 gradle.properties 里显式开启一下:
org.gradle.caching=true
这样能确保缓存功能是激活的。不过要注意,本地缓存只对本机有效。如果你换了电脑,或者 CI 机器上跑,缓存就没了。
我在项目中遇到过一个问题:同事改了代码后,本地缓存没失效,导致他以为自己的改动生效了,结果 CI 上构建失败。后来我养成了一个习惯——每次拉取代码后,先执行一次 clean 再构建,避免缓存污染。
--build-cache 参数临时启用缓存,适合调试时用。
20.2 远程构建缓存
远程缓存才是真正的「团队加速器」。它允许团队成员共享构建产物,谁先构建了某个模块,其他人直接拿结果。
配置远程缓存需要指定一个存储后端。Gradle 官方支持 HTTP 缓存后端,比如用 Nginx 或者 Gradle Enterprise。配置方式如下:
// settings.gradle.kts
buildCache {
remote(HttpBuildCache) {
url = uri("https://cache.example.com/cache/")
isPush = true
credentials {
username = "cache-user"
password = "cache-pass"
}
}
}
这里有个坑:isPush 属性决定了当前构建是否会上传缓存。我建议 CI 机器上设为 true,开发者本地设为 false。为什么呢?因为开发者本地构建的产物可能包含本地特有的路径信息,上传后反而会污染远程缓存。
20.3 缓存键的生成规则
缓存键是 Gradle 判断「这次能不能用缓存」的依据。说白了,就是一组输入参数的哈希值。
Gradle 会计算以下内容的哈希:
- task 的输入文件(源码、资源、配置文件等)
- task 的输入属性(比如编译版本、混淆规则等)
- task 的实现类(如果 task 代码变了,缓存会失效)
- 依赖的版本号
举个例子,对于 compileDebugKotlin 这个 task,它的缓存键会包含:
- 所有
.kt文件的路径和内容哈希 - Kotlin 编译器版本
- JVM 目标版本
- 是否开启了某些编译器选项
为什么会这样设计?因为 Gradle 必须保证缓存结果和重新执行的结果完全一致。如果漏掉了某个输入,就会出现「缓存命中但结果不对」的 bug。
我记得有一次,我们升级了 AGP 版本,结果所有缓存都失效了。原因就是 AGP 版本号被包含在缓存键里。嗯,这是合理的——不同版本的 AGP 生成的产物确实可能不兼容。
20.4 缓存命中率优化
缓存命中率越高,构建越快。但实际项目中,命中率往往不理想。我总结了几条优化经验:
- 避免使用绝对路径:task 的输入中如果包含绝对路径(比如
/Users/xxx/project),换台机器缓存就失效了。用相对路径或者 Gradle 的projectDir变量。 - 减少不必要的输入:有些 task 的输入包含了时间戳、随机数等不稳定因素。比如自定义 task 里用了
System.currentTimeMillis(),那缓存永远命中不了。 - 使用稳定的输出格式:比如生成的 APK 文件名不要包含时间戳,否则缓存键会变。
- 合理拆分 task:把一个大 task 拆成多个小 task,每个小 task 的输入更稳定,缓存命中率更高。
我曾经在项目中遇到一个 case:某个 task 的输入里包含了 build.gradle.kts 文件的修改时间。每次 git 拉取代码后,文件修改时间变了,缓存就失效了。后来改成只哈希文件内容,问题解决。
--info 参数查看缓存命中情况。Gradle 会输出类似 Cache hit for task ':app:compileDebugKotlin' 的信息。
20.5 缓存清理策略
缓存文件会越积越多。本地缓存默认有 7 天的保留时间,超过这个时间的缓存会被自动清理。但如果你项目很大,缓存可能占用几十 GB 空间。
我建议定期手动清理:
# 清理本地缓存
rm -rf ~/.gradle/caches/build-cache-1
# 或者用 Gradle 提供的命令
gradle cleanBuildCache
远程缓存也需要清理策略。我一般设置保留最近 30 天的缓存,超过的自动删除。如果缓存服务器是自建的,可以写个定时任务:
# 每天凌晨清理 30 天前的缓存
0 0 * * * find /path/to/cache -mtime +30 -delete
这里要注意:不要清理得太频繁。如果缓存刚生成就被删了,下次构建又要重新生成,反而更慢。
知识体系图
构建缓存是个好东西,但用不好就是「双刃剑」。我建议你从本地缓存开始,等团队协作成熟了再上远程缓存。记住一个原则:缓存是为了加速,不是为了省事。该 clean 的时候别偷懒,该排查缓存问题的时候别跳过。