3. Gradle 缓存机制原理:构建缓存、配置缓存与本地缓存目录结构
在多模块项目中,每次 AGP 版本升级都会触发大量任务重新执行、配置重新解析,导致构建时间倍增。理解 Gradle 的三层缓存体系——构建缓存(Build Cache)、配置缓存(Configuration Cache)以及本地缓存目录结构——是解决版本升级后缓存同步与冲突问题的前提。本章从底层机制到实用配置,逐层剖析每个缓存的作用域、失效场景和调试方法。
概念速览:构建缓存缓存任务输出,配置缓存缓存构建配置阶段的结果,本地缓存目录负责存储下载的依赖、生成的中间文件。三者相互独立又共同影响构建性能。
3.1 构建缓存(Build Cache)
3.1.1 原理与作用域
构建缓存将每个任务的输入(inputs)哈希后,缓存任务的输出文件。当输入不变时,后续构建可直接从缓存拉取输出,跳过任务执行。在 AGP 升级场景下,如果模块 A 的编译任务输入(源码、AGP API 版本、依赖坐标)未变,即使 AGP 版本号变化(但 API 行为兼容),缓存仍可命中。
3.1.2 配置方式
// settings.gradle.kts
buildCache {
local(DirectoryBuildCache::class) {
directory = File(rootDir, ".build-cache")
removeUnusedEntriesAfterDays = 7
}
remote(HttpBuildCache::class) {
url = URI("https://cache.example.com/")
isPush = true
credentials {
username = "cache-user"
password = "cache-pass"
}
}
}
3.1.3 调试命令
# 查看当前缓存的命中统计
./gradlew build --build-cache --info | grep "Build cache"
# 强制清空本地构建缓存
./gradlew cleanBuildCache
版本升级冲突点:AGP 版本变化会导致某些任务的
@CacheableTask注解检测到输出类型变化(如 AAPT2 生成的资源二进制格式不同),从而默认不命中。此时需检查 taskOutputs.upToDateWhen 是否合理,或考虑使用 cacheIf 自定义缓存条件。
3.2 配置缓存(Configuration Cache)
3.2.1 原理与生命周期
配置缓存将 settings.gradle.kts 和 build.gradle.kts 的配置阶段脚本执行结果序列化为二进制缓存,后续构建如果检测到 Gradle 脚本、环境变量、系统属性未变,则直接反序列化配置,跳过配置阶段。在 AGP 升级后,如果 apply plugin: "com.android.application" 或依赖仓库地址未变,但 AGP 版本号变了,默认不会触发配置缓存失效,因为配置缓存的 key 不包含构建脚本中的版本号(除非显式声明了输入属性)。
3.2.2 启用与验证
# 启用配置缓存(gradle.properties 或命令行)
org.gradle.configuration-cache=true
# 首次构建写入缓存,第二次验证是否重用
./gradlew assembleDebug --configuration-cache
# 查看缓存失效原因
./gradlew assembleDebug --configuration-cache --info | grep "configuration-cache"
3.2.3 常见错误及修复
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
Gradle could not serialize... | 配置缓存不支持某些闭包或匿名类 | 改为使用一等函数或 Provider 接口 |
Configuration cache state invalidated | 脚本中引用了非确定性值(如 System.currentTimeMillis()) | 使用 providers.systemProperty() 代替 |
Cached configuration is stale | AGP 升级后某些 task 的 input 属性在配置阶段未注册 | 检查 task.inputs 声明是否完整 |
性能陷阱:配置缓存与 AGP 的
buildConfig 字段结合时,如果 BuildConfig 依赖 project.property 而未在 input 中显式声明,缓存可能产生错误的 BuildConfig 值。务必使用 BasePluginExtension.buildConfigField 并确保所有变量通过 providers 注入。
3.3 本地缓存目录结构
3.3.1 目录全景
Gradle 默认在 $HOME/.gradle/caches/ 下存储所有本地缓存,结构如下:
$HOME/.gradle/caches/
├── journal-1/ # 缓存元数据日志
├── modules-2/ # 解析后的模块元数据(POM、Ivy)
├── transforms-3/ # 资源/文件转换缓存(如 AAPT2)
├── jars-3/ # 已下载的 JAR/AAR
├── kotlin-dsl/ # Kotlin DSL 编译缓存
├── build-cache-1/ # 构建缓存本地存储(如果开启)
├── configuration-cache/ # 配置缓存序列化文件
│ ├── 1/ # 按 Gradle 版本分目录
│ └── ...
└── gradle-wrapper/ # Gradle Wrapper 下载的发行版
3.3.2 版本升级后的冲突点
- AGP 版本升级后,
transforms-3/中的缓存可能因 AAPT/资源编译器版本变更而失效,建议在升级后执行./gradlew clean清除 transforms 缓存。 - modules-2/ 中缓存的依赖元数据如果包含旧的 Gradle Module Metadata(GMM),可能导致新 AGP 解析失败。可执行
./gradlew --refresh-dependencies强制刷新。 - configuration-cache/ 目录按 Gradle 版本号存放,升级 AGP 后如果 Gradle 版本未变,旧配置缓存可能残留,需手动删除或使用
--no-configuration-cache重新生成。
3.3.3 清理策略
# 仅清理构建缓存
./gradlew cleanBuildCache
# 清理所有本地缓存(注意:会删除所有依赖和转换缓存)
rm -rf $HOME/.gradle/caches/ && mkdir -p $HOME/.gradle/caches/
# 安全删除指定版本缓存(保留已下载的 jar 包)
rm -rf $HOME/.gradle/caches/transforms-3/
多模块项目建议:在 CI 上每次 AGP 升级后,执行一次
rm -rf $HOME/.gradle/caches/transforms-3/ $HOME/.gradle/caches/configuration-cache/,可减少 80% 的缓存冲突导致的构建失败。
3.4 三种缓存的协作关系
下图展示了在多模块 AGP 升级过程中,三个缓存如何串联:
3.5 性能与稳定性优化
- 为构建缓存配置合适的远程 URL:多模块项目中,使用同一台 CI 机器时,远程构建缓存可以跨构建共享已编译的 AAR,减少重复任务。
- 配置缓存开启前清理序列化风险:在 AGP 升级后,先执行
--no-configuration-cache正常构建一次,确保所有 task 配置正常,再启用配置缓存。 - 本地缓存目录与项目分离:在
gradle.properties中设置org.gradle.caches.dir=./.gradle-caches将缓存放在项目目录内,便于 CI 流程整体清理。 - 利用
--rerun-tasks验证一致性:AGP 升级后,可先执行./gradlew assembleDebug --rerun-tasks --build-cache强制所有任务重跑,然后将生成的构建缓存推送至远程,确保缓存与新版 AGP 一致。