4. 依赖缓存目录结构解读:层级、.gradle/caches/modules-2 与哈希子目录
依赖缓存是 Gradle 复用已下载组件的核心机制,但也是许多“构建慢”“下载失败”“ClassNotFoundException”的源头。当缓存的文件被误删、部分损坏或哈希冲突时,需要深入目录结构才能精准定位问题。
4.1 现象识别
构建时出现以下症状,通常与缓存目录结构异常有关:
- “Could not resolve” —— 依赖解析失败,但网络正常。
- “Checksum mismatch” —— 缓存的 artifact 哈希与原始不符。
- “Unexpected error reading /home/user/.gradle/caches/modules-2/files-2.1/...” —— 损坏的 metadata 或缺失的哈希目录。
- 构建突然变慢 —— 每次重新下载全部依赖,而非使用缓存。
4.2 数据采集
首先进入缓存根目录,查看 modules-2 的结构:
ls -la ~/.gradle/caches/modules-2/
# 输出示例:
# drwxr-xr-x ... files-2.1/
# drwxr-xr-x ... metadata-2.76/
# drwxr-xr-x ... modules-2.lock
# -rw-r--r-- ... modules-2.crc
核心子目录及含义如下:
| 目录/文件 | 含义 |
|---|---|
files-2.1/ |
存储所有下载的 jar/aar/pom 等二进制文件 |
metadata-2.76/ |
存储依赖的元数据(版本列表、依赖树、sha1 校验) |
modules-2.lock |
锁文件,防止并发读写冲突 |
modules-2.crc |
缓存区域的 CRC 校验文件 |
进一步深入到 files-2.1 内部,会看到按组 ID 和模块 ID 组织的目录:
~/.gradle/caches/modules-2/files-2.1/
com.google.guava/
guava/
30.1-jre/
a1b2c3d4.../ # ← 哈希子目录
guava-30.1-jre.jar
guava-30.1-jre.pom
30.1-android/
e5f6g7h8.../
guava-30.1-android.jar
排查要点:哈希子目录(如
a1b2c3d4...)是 artifact 文件内容的 SHA1 校验值的前 8 位。当同一坐标的多个版本文件内容完全相同时,它们会共用同一个哈希子目录;若内容不同则分别存放。如果哈希目录缺失或文件校验不通过,Gradle 会认为缓存无效并重新下载。
4.3 原因定位
利用采集到的目录结构,可按以下故障树定位:
- 文件全部缺失 → 可能 $GRADLE_USER_HOME 被清理,或磁盘空间满导致缓存被删。
- 指定版本的哈希子目录不存在 → 缓存未下载完成,或之前构建中断。
- 哈希子目录存在但 artifact 文件不完整 → 部分写入,比对
metadata-2.76/...中记录的 size 和 sha1。 - metadata 中的校验值与实际文件不符 → 文件被篡改或磁盘静默损坏。
- 锁文件被残留 → 前一次 Gradle 异常退出,删除
modules-2.lock即可。
4.4 修复动作
根据定位结果执行对应操作:
- 修复单个依赖缓存:删除对应模块的整个版本目录,重新构建。
- 修复整个缓存:
rm -rf ~/.gradle/caches/modules-2(谨慎,将丢失所有本地缓存)。 - 验证 metadata 一致性:手动检查
metadata-2.76/descriptors/组ID/模块ID/版本/... .module文件中的 sha1 值。
# 以 guava 为例,删除后重新下载
rm -rf ~/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/30.1-jre/
./gradlew --refresh-dependencies
注意:
--refresh-dependencies 只会标记依赖为过时并重新校验,不会删除损坏的文件。如果文件本身已损坏,仍然需要手动清理对应哈希子目录。
4.5 复测
- 运行
./gradlew clean build,观察控制台日志中是否重新出现 “Downloading ... ”。 - 检查
files-2.1下对应的哈希子目录是否重新创建,且文件大小与原 metadata 一致。 - 使用
gradle --offline测试:“Could not resolve” 是否消失。
4.6 预防
- 定期执行
gradle cleanBuildCache或rm -rf $GRADLE_USER_HOME/caches(仅当确实需要)。 - 启用 Gradle 的并行依赖解析与缓存锁定,避免并发写入冲突。
- 使用
buildscript { dependencies { ... } }时指定精确版本,减少哈希碰撞概率。 - 在 CI 环境中将缓存目录挂载为持久化 volume,并设置备份。
4.7 依赖缓存目录结构图
下图展示了 modules-2 下两个主要存储区的层级关系: