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 原因定位

利用采集到的目录结构,可按以下故障树定位:

  1. 文件全部缺失 → 可能 $GRADLE_USER_HOME 被清理,或磁盘空间满导致缓存被删。
  2. 指定版本的哈希子目录不存在 → 缓存未下载完成,或之前构建中断。
  3. 哈希子目录存在但 artifact 文件不完整 → 部分写入,比对 metadata-2.76/... 中记录的 size 和 sha1。
  4. metadata 中的校验值与实际文件不符 → 文件被篡改或磁盘静默损坏。
  5. 锁文件被残留 → 前一次 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 复测

  1. 运行 ./gradlew clean build,观察控制台日志中是否重新出现 “Downloading ... ”。
  2. 检查 files-2.1 下对应的哈希子目录是否重新创建,且文件大小与原 metadata 一致。
  3. 使用 gradle --offline 测试:“Could not resolve” 是否消失。

4.6 预防

  • 定期执行 gradle cleanBuildCache 或 rm -rf $GRADLE_USER_HOME/caches(仅当确实需要)。
  • 启用 Gradle 的并行依赖解析与缓存锁定,避免并发写入冲突。
  • 使用 buildscript { dependencies { ... } } 时指定精确版本,减少哈希碰撞概率。
  • 在 CI 环境中将缓存目录挂载为持久化 volume,并设置备份。

4.7 依赖缓存目录结构图

下图展示了 modules-2 下两个主要存储区的层级关系:

Gradle 依赖缓存 modules-2 目录结构 ~/.gradle/caches/modules-2/ files-2.1/ metadata-2.76/ com.google.guava/ (组ID) guava/ (模块ID) 30.1-jre/ (版本) a1b2c3d4.../ (哈希) guava-30.1-jre.jar / .pom descriptors/ 组ID/模块ID/版本/ 30.1-jre/ guava-30.1-jre.module 哈希子目录 = 文件内容的 SHA1 前 8 位。md 文件记录校验值。 目录结构不一致 → 缓存未命中或校验失败。