第一章 构建缓存核心原理

1.1 构建缓存作用机制

Gradle 构建缓存通过重用先前构建的输出(例如 .class 文件、JAR、测试报告)来避免重复执行相同的任务。其核心是 任务输出缓存:当任务输入与历史构建完全一致时,Gradle 直接复用缓存中的输出,跳过任务执行。

下图展示了缓存从输入到命中的完整生命周期:

Gradle 构建缓存核心流程 1. 任务输入 (源文件 / 依赖) 2. 缓存键 (哈希组合) 3. 键/值数据库 (本地 / 远程) 4. 命中/未命中 (精确匹配) 5. 输出工件 (.class/.jar) 未命中则重新执行

流程解读:任务输入(源文件路径与内容、依赖坐标、Gradle 版本、操作系统)经过统一散列生成缓存键;该键作为唯一标识在键/值数据库中查询;若命中,则直接返回输出工件;若未命中,任务正常执行并将输出存入缓存。

1.2 缓存作用域:本地与远程

作用域决定了缓存数据在哪个范围内共享。实现层面分为两级:

特性 本地缓存 (Local) 远程缓存 (Remote)
存储位置 ~/.gradle/caches/build-cache-1/ HTTP 服务器 (如 S3、Artifactory)
网络依赖 无 需要 HTTP(S) 连接
共享范围 单机 团队 / CI 所有节点
磁盘空间 由 Gradle 自动清理 (LRU) 服务器端策略控制
缓存键校验 文件系统哈希 HTTP ETag / Content‑Hash
作用域配置入口:在 settings.gradle.kts 中启用并定义作用域范围。
// settings.gradle.kts
buildCache {
    local {
        enabled = true
        directory = File(System.getProperty("user.home"), ".gradle/caches/build-cache-1")
    }
    remote {
        url = uri("https://build-cache.mycompany.com/cache/")
        isPush = true
        credentials {
            username = "cache-user"
            password = System.getenv("CACHE_") ?: ""
        }
    }
}
关键提醒:远程缓存需要显式声明 isPush = true 才会上传缓存条目;CI 环境下务必通过环境变量注入密码,避免将凭证硬编码在版本控制中。

1.3 缓存键的精确组成

缓存键是一个长度固定的十六进制字符串(SHA-256),由以下因素共同决定:

  1. 任务类型标识符 —— 如 com.example.MyTask,确保不同类型的任务不会相互污染。
  2. 输入文件路径及内容哈希 —— 每个声明为 @InputFiles 或 @InputFile 的文件都会被加入计算。
  3. 输入属性的值 —— 标注了 @Input 的字符串、整数等。
  4. Gradle 版本与操作系统名称 —— 防止跨平台二进制不兼容。
  5. 输出文件路径的通配结构 —— 确保输出位置不会意外改变键值。

Gradle 内部通过 HashCode 类将上述元素拼接为唯一的 32 字节摘要。任何一项发生变化都会导致新键生成,从而触发缓存未命中。

1.4 缓存命中条件

命中条件遵循严格的全等比较原则:

  • 键精确匹配:本地或远程存储中存在与当前生成键完全相同的条目。
  • 输出结构可用:缓存中的输出目录结构与任务声明的 @OutputDirectory / @OutputFile 一致。
  • 未达到生存期 (TTL):远程缓存条目未过期(可通过 HTTP Cache‑Control 头控制)。
调试命中状态:使用 --info 或 --debug 日志级别运行构建,Gradle 会输出类似 Task :compileJava: cache hit 或 cache miss for key: 2a3f8c... 的日志行。这是排查缓存未命中最直接的手段。
# 启用缓存并输出详细日志
gradle build --build-cache --info

1.5 缓存存储结构与回收

本地缓存采用分层文件系统存储,按键的前缀分目录保存:

~/.gradle/caches/build-cache-1/
├── 2a/
│   └── 3f/
│       └── 8c... (缓存工件)
├── ab/
│   └── cd/
│       └── ef...
└── gc.properties   # 回收策略配置

每个缓存条目包含两部分:

  • 元数据文件 —— 存储键、创建时间、任务类型、输入指纹。
  • 输出快照 —— 任务输出的压缩归档(tar/zst 格式)。

Gradle 在构建完成或启动时会检查磁盘占用,若超过 MaxCacheSize(默认 10 GB)则按最近最少使用 (LRU) 策略清理旧条目。

性能陷阱:超大单个缓存文件(如包含全部依赖的 fat JAR)会显著降低缓存上传/下载速度,并容易撑满本地存储。在 build.gradle.kts 中合理拆分任务输出,避免将不必要的文件纳入缓存。