启用和配置AGP构建缓存
构建缓存这东西,说白了就是让Gradle记住你上一次编译产生的中产物。下次再编译时,如果输入没变,直接从缓存里拿,省去重新编译的时间。我当年刚接触Android构建时,看着每次clean build那漫长的等待,就琢磨着肯定有办法加速。后来发现AGP(Android Gradle Plugin)自带构建缓存机制,配置得当的话,增量编译速度能翻一倍。
但要注意——默认情况下,构建缓存并没有完全开启。你需要手动配置,而且配置的方式直接影响效果。踩过坑的人都知道,配不好反而会引入缓存冲突,导致诡异的问题。
一、构建缓存的核心概念
AGP的构建缓存,本质上是任务级别的输出缓存。每个Gradle Task(比如编译Java/Kotlin、处理Android资源、生成Dex等)执行后,会将输出产物(.class文件、.dex文件、压缩的资源包等)按照输入内容的哈希值存入缓存。下次再跑同一个Task,如果输入没变,直接复用缓存。
| 术语 | 含义 |
|---|---|
| 缓存键(Cache Key) | 由Task的输入文件、参数、依赖等计算出的唯一哈希值 |
| 缓存命中(Cache Hit) | 当前Task的缓存键已存在于缓存中,直接复用上一次的输出 |
| 缓存未命中(Cache Miss) | 缓存键不存在,需要重新执行Task,并将结果存入缓存 |
| 缓存目录 | 存放缓存产物的本地文件夹,默认在 ~/.gradle/caches/ |
我个人习惯把缓存想象成一个“任务快照仓库”。你改了一行Java代码,只有涉及这个文件的Task会重新编译,其他Task直接从仓库里拿现成的结果。嗯,这里要注意:并不是所有Task都支持缓存,比如某些自定义Task如果没标注@CacheableTask,就不会被缓存。
@CacheableTask注解,并且正确声明输入输出。否则会被AGP跳过,白忙一场。
二、如何启用构建缓存
启用方式很简单,就一行配置。但我建议你根据自己的项目情况选择路径。
方式一:全局配置(gradle.properties)
在项目根目录下的 gradle.properties 文件中添加:
org.gradle.caching=true
这会启用Gradle的通用构建缓存。但你想想看,AGP在7.0之后有自己的缓存策略,所以还需要额外开启AGP的缓存:
android.enableBuildCache=true
两者结合,才能让Android相关的Task也受益。
方式二:按模块配置(build.gradle)
如果你只想针对某个模块启用,可以在模块级别的build.gradle里写:
android {
buildCache {
local {
enabled = true
}
}
}
我见过一些大项目,因为各模块依赖复杂,全局启用缓存后反而导致“哈希冲突”或“缓存污染”。这时候按模块细粒度控制更稳妥。
buildCache.local.enabled = true并配合cacheConfiguration指定了缓存目录,问题才解决。
三、配置缓存目录与大小
默认缓存目录在 ~/.gradle/caches/ 下,但你可以通过下面的配置自定义:
android {
buildCache {
local {
directory = new File(rootDir, "build-cache")
removeUnusedEntriesAfterDays = 7
// 以下3个参数只有Gradle 7.0+支持
targetSizeInMB = 1024
maxSizeInMB = 2048
}
}
}
directory:指定缓存目录,推荐放在项目目录内(方便清除),或者放在SSD上提升I/O速度。removeUnusedEntriesAfterDays:自动清理超过多少天未使用的缓存,默认7天。我习惯设为3天,因为迭代快,旧缓存意义不大。targetSizeInMB/maxSizeInMB:控制缓存上限。我曾遇到过CI服务器磁盘爆满,就是因为缓存积累太多没清理。务必设置上限。
remote区块和认证信息。但本地开发建议优先把本地缓存配好,远程缓存更适合CI/CD环境。
四、缓存命中率的验证手段
配置完了,你得验证到底有没有生效。我常用的招数有两个:
1. 通过Gradle命令的--info日志
./gradlew assembleDebug --info | grep "CACHED"
如果输出中出现 Task :xxx:compileDebugKotlin FROM CACHE 或 Task :xxx:mergeDebugResources FROM CACHE,说明缓存命中了。
2. 利用Build Analyzer插件
Android Studio自带的Build Analyzer(在Build菜单下)能展示每个Task的执行时间、缓存命中情况。这是我个人最推荐的诊断工具——可视化的,一看就懂。
我曾经在一个20+模块的项目里,通过Build Analyzer发现某个资源处理Task始终没命中缓存。排查后发现是因为配置了aaptOptions.additionalParameters,这个参数没被包含在缓存键里,导致每次都被当作不同的输入。修正配置后,缓存命中率从30%飙升到85%。
1.+)、是否使用了--rerun-tasks或者clean命令频繁。另外,BuildConfig中的时间戳也可能导致缓存失效,建议在buildConfigField中避免使用System.currentTimeMillis()。
五、构建缓存工作流程图
下面这张图展示了AGP构建缓存的核心流程:从源文件变化到最终复用缓存的决策过程。
六、高级配置:远程缓存(CI/CD场景)
如果你的项目使用了持续集成(CI),本地缓存每次构建都会从零开始。这时候远程缓存就派上用场了。配置如下:
// 在项目根目录 build.gradle 或 settings.gradle 中
buildCache {
remote(HttpBuildCache) {
url = 'https://build-cache.yourcompany.com/cache/'
allowUntrustedServer = false
// 如果需要认证
credentials {
username = 'cache-user'
password = 'cache-password'
}
}
}
要注意:远程缓存服务器需要支持 Gradle Build Cache Node 或兼容的HTTP API。我建议团队里统一部署一个缓存节点,构建速度能提升40%以上。
--build-cache参数执行Gradle,导致每次CI都是冷启动。还有一个坑:远程缓存和本地缓存的优先级问题。默认情况下,Gradle先查本地再查远程。如果你本地缓存太大、I/O慢,建议调小本地缓存上限,让远程缓存发挥更大作用。具体做法是设置local.enabled = false,只使用远程缓存。
七、清理缓存的最佳实践
缓存不是越多越好。遇到以下情况时,你需要手动清理:
- 升级Gradle或AGP版本后:缓存格式可能变更,旧缓存无法使用,反而占用空间。建议执行
./gradlew cleanBuildCache。 - 遇到奇怪的编译错误,而别人能编译过:大概率是本地缓存污染。先执行
./gradlew cleanBuildCache再rebuild。 - 磁盘空间告急:直接删除
~/.gradle/caches/build-cache-*目录,或者用./gradlew --stop停止守护进程后手动删除。
我个人习惯在团队内推行每周一清理一次本地缓存。因为周末没人构建,周一第一次构建可以容忍慢一些,之后缓存就是热乎的了。有效避免了“缓存腐化”引发的奇怪问题。
1. 始终在
gradle.properties 中开启 org.gradle.caching=true 和 android.enableBuildCache=true。2. 设定缓存大小上限,避免撑爆磁盘。
3. 修改Task输入后,注意检查缓存键是否覆盖所有依赖。
4. CI环境中优先使用远程缓存,并设置
--build-cache参数开启缓存。5. 遇到疑难杂症,先清缓存再试——这招能解决80%的“幻觉问题”。
好了,构建缓存的配置就说到这里。你想想看,这些配置加起来也就几十行代码,但带来的编译提速效果却非常显著。下一步,你可以去自己项目里试试,看看增量编译从60秒降到10秒是什么体验。