什么是 Gradle 任务缓存?如何配置本地和远程缓存?

解读

面试官问“任务缓存”并不是想听“把构建结果存起来”这种一句话概念,而是想看你是否真正踩过“CI 排队 20 min、本地反复 clean、远程依赖被墙”这些坑,并给出可落地的国内工程方案。回答时要围绕“输入指纹 → 缓存键 → 命中策略 → 缓存位置 → 失效场景”这条主线展开,同时把国内特色(Maven 镜像、自建 GitLab、混合云、合规审计)带进去,让面试官确信“这人来了就能把我们 15 min 的构建砍到 3 min”。

知识点

  1. Gradle 任务缓存(Build Cache)本质:把“输入快照 → 输出”映射成键值对,命中后跳过 Task 执行,直接取输出。
  2. 输入指纹:包括 Task 类名、classpath、@TaskAction 字节码、输入文件哈希、系统属性、环境变量、编译 SDK 版本等;任何字段变化都会换 key。
  3. 缓存作用域:
    • 本地缓存:默认在 <GRADLE_USER_HOME>/caches/build-cache-1,单机复用,生命周期 7 天未命中即淘汰。
    • 远程缓存:HTTP 协议,支持 HEAD/PUT/GET,返回 404 视为未命中;Gradle 7+ 支持 Bearer Token、自定义 HTTP 头,方便对接国内私有云。
  4. 缓存目录结构:
    • 本地以“哈希前 2 位/哈希”做两级目录,存储 gzip 的 tar 包;
    • 远程以同样哈希做 ObjectKey,兼容 MinIO、阿里云 OSS、腾讯 COS、华为 OBS。
  5. 安全与合规:
    • 缓存包仅含 Task 输出,不含源码,但 AAR/JAR 里可能带 R.class,需开启 buildCache { filter { exclude "/**/*R.class" } } 防止泄漏资源 ID。
    • 国内金融/车机项目要求“不出内网”,远程缓存必须走 HTTPS + 内网 DNS,且开启 isPush=false 做只读镜像。
  6. 失效与调试:
    • --info 能看到 “Caching disabled for task ‘:app:compileDebugJavaWithJavac’: Caching was not enabled” 原因;
    • gradle build --build-cache -Dorg.gradle.caching.debug=true 打印 key 组成;
    • 本地调试直接删 caches/build-cache-1 验证是否重新执行。
  7. 与增量编译关系:任务缓存是“进程级”复用,增量编译是“Task 内”复用;两者互补,不冲突。
  8. 国内常见误区:
    • org.gradle.daemon=false 关了,导致每次 fork 新进程,缓存命中率骤降;
    • 在 CI 里 clean 成瘾,把 build 目录全删,远程缓存形同虚设;
    • 用 Windows 跑 CI,路径大小写不一致,导致 key 变化。

答案

Gradle 任务缓存是把 Task 的“输入指纹”作为 key,把输出目录打成 tar.gz 作为 value,下次 key 相同直接解压复用,跳过执行。
配置分三步:

  1. 启用总开关:
    gradle.properties 里写

    org.gradle.caching=true
    

    或在 CI 命令行加 --build-cache

  2. 本地缓存:默认已开启,路径由 GRADLE_USER_HOME 决定;如需改大小或路径,在 settings.gradle 里写

    buildCache {
        local {
            directory = new File(rootDir, '.gradle/build-cache')
            removeUnusedEntriesAfterDays = 3
        }
    }
    
  3. 远程缓存(国内私有云示例):

    buildCache {
        local {
            enabled = true
        }
        remote(HttpBuildCache) {
            url = 'https://gradle-cache.intra.xxx.com/cache/'
            credentials {
                username = System.getenv("CACHE_USER")
                password = System.getenv("CACHE_PASS")
            }
            // 仅 CI 推送,开发者只读
            push = System.getenv("CI") == "true"
            // 国内 OSS 走内网,允许不校验证书(内网可信)
            allowUntrustedServer = false
            // 连接超时 30s,防止被墙
            connectTimeout = 30000
        }
    }
    

    若公司用 MinIO,只需把 url 指向 https://minio.intra.xxx.com/bucket/,并在 Nginx 加 proxy_cache 做二级加速。

验证:
本地 ./gradlew :app:assembleDebug --build-cache 第一次 3 min,第二次 40 s,且日志出现 “Reusing output from remote build cache” 即成功。

拓展思考

  1. 多级缓存拓扑:北京机房写、深圳机房读,如何用 CDN + 回源 MinIO 做到 <100 ms 延迟?
  2. 缓存污染:A 分支升级了 AGP 8.1,B 分支仍用 7.4,如何设计 key 前缀隔离,避免“旧分支命中新缓存”导致编包异常?
  3. 合规审计:金融 App 要求“构建过程可回溯”,如何把每次缓存命中记录(taskPath、key、gitCommit、构建人)写入 Elasticsearch,并在审计平台一键溯源?
  4. 混合编译:Kotlin 1.9 启用 K2 编译器,字节码特征变化,如何让远程缓存平滑灰度,避免全量失效?
  5. 未来趋势:Gradle 8 引入 Configuration Cache + Build Cache 联动,把 Task 图序列化到磁盘,秒级恢复;国内大型 Monorepo 是否值得一步到位,还是继续拆模块?