第2章 离线依赖缓存机制原理
Gradle 的离线依赖缓存是整个构建系统高速运转的基石。理解缓存目录的组织方式、缓存键的生成逻辑、锁定与校验文件的协作关系,以及命中/未命中的完整路径,才能在设计升级策略时精准决策。本章拆解这四个子模块,并给出可直接用于调试的路径示例与命令。
2.1 缓存目录结构
Gradle 默认将缓存存放在 ~/.gradle/caches/modules-2/files-2.1/ 下。该目录采用 三级嵌套 + SHA-1 子目录 的布局:
- 一级目录:按 Group (组织/公司) 名称拆分,例如
com.example会映射为com/example。 - 二级目录:Artifact 名称,如
my-lib。 - 三级目录:版本号,如
1.2.3。 - 四级目录:由
group:artifact:version:classifier:extension组合计算出的 32 位 SHA-1 哈希值(小写),作为最终存储文件夹。 - 文件:原始 JAR/AAR 文件、
.sha1校验文件、.lock锁定文件。
💡 调试路径示例
~/.gradle/caches/modules-2/files-2.1/
com.android.tools.build/
gradle/
8.5.0/
e2b9e2a5b3c1d4f6.../ # SHA-1 目录
gradle-8.5.0.pom
gradle-8.5.0.pom.sha1
gradle-8.5.0.pom.lock
gradle-8.5.0.jar
gradle-8.5.0.jar.sha1
gradle-8.5.0.jar.lock
通过此结构,Gradle 可以在毫秒级定位到任意坐标对应的缓存文件,无需遍历全部依赖。
2.2 缓存键生成规则
缓存键的本质是一个 SHA-1 哈希值,生成公式如下:
raw_key = group + ":" + artifact + ":" + version
if (classifier != null) raw_key += ":" + classifier
if (extension != null) raw_key += "@" + extension
cache_key = SHA1(raw_key).toLowerCase() # 32 位十六进制字符串
例如 com.example:my-lib:1.2.3:aar 的哈希计算(伪代码):
SHA1("com.example:my-lib:1.2.3:docs@aar") → “a1b2c3d4e5f6789012345678abcdef0123456789”
这个哈希直接决定了文件在缓存中的存放位置,只要坐标不变,哈希就不变,保证了跨构建/跨项目的一致性。
2.3 锁定文件与校验和文件的作用
| 文件类型 | 扩展名 | 作用 |
|---|---|---|
| 锁定文件 | .lock |
防止多个 Gradle 进程同时写同一个缓存文件;写进程持有独占锁,读进程等待或跳过 |
| 校验和文件 | .sha1 |
存储原始文件的 SHA-1 哈希值,用于验证缓存文件是否损坏或被篡改 |
| 元数据文件 | .module 等 |
记录依赖元数据(版本、传递依赖等),同样附带 .sha1 和 .lock |
⚠️ 常见问题:当
.lock 文件残留(如进程强制结束)时,新构建会等待数秒至超时后强制清理。手动删除 ~/.gradle/caches/modules-2/ 下的 .lock 文件可快速恢复。
2.4 缓存命中与未命中流程
整个流程可抽象为以下步骤,直接影响构建耗时:
- 请求解析:Gradle 根据依赖声明生成缓存键(SHA-1 目录名)。
- 检查锁定:尝试获取
.lock文件的读锁;若被其他进程写锁定,等待或跳过。 - 检查文件存在:若目标 JAR/AAR 文件存在,读取其
.sha1并对比预期哈希:
✅ 命中:直接返回文件路径,跳过远程下载。
❌ 未命中:进入远程下载流程。 - 远程下载并校验:下载至临时文件 → 计算 SHA-1 → 与声明的校验和匹配 → 将文件移动到缓存目录 → 写入
.sha1→ 释放锁定。 - 后续构建共享:同一机器的其他 Gradle 进程可直接使用已缓存的依赖。
📊 性能影响
- 缓存命中:仅 0.1 ~ 0.5 ms(文件 I/O + SHA-1 对比)。
- 缓存未命中:依赖网络带宽,平均增加 300 ms ~ 30 s 不等。
- 锁定等待:最多 5 s(默认超时),超过后强制清理并重新下载。
2.5 调试与验证命令
以下命令可在构建过程中实时观察缓存行为:
# 观察缓存目录的锁定文件变化(Linux / macOS)
lsof ~/.gradle/caches/modules-2/files-2.1/ | grep .lock
# 强制清除单个依赖的缓存
rm -rf ~/.gradle/caches/modules-2/files-2.1/com.example/my-lib
# 查看缓存命中统计 (Gradle Profiler)
./gradlew --scan
🔍 调试技巧:使用
--refresh-dependencies 参数可强制忽略缓存重新下载,配合 --info 日志可看到每一步的缓存决策日志,例如 Cache entry found for … 或 Cache entry not found, downloading …。
2.6 常见错误与稳定性优化
- .lock 残留导致构建挂起 → 手动删除
~/.gradle/caches/modules-2/下所有*.lock文件,或重启 Gradle 守护进程。 - 校验和不匹配 → 缓存文件损坏,删除对应 SHA-1 目录后重新下载。
- 跨用户共享缓存 → 将
~/.gradle软链到共享存储时,注意文件权限,否则 .lock 操作可能失败。 - AGP 升级导致旧缓存失效 → 升级后会自动生成新的缓存键(因为版本号变化),但旧缓存不会自动清理。建议定期执行
gradle clean并配合--no-daemon测试。