22、Gradle Enterprise与构建缓存:构建缓存原理、远程缓存配置、缓存命中率优化、构建扫描分析、团队协作
说到Gradle构建,很多团队一开始都挺乐观的——写个脚本,配个依赖,跑起来就完事了。但随着项目膨胀,你会发现每次构建都要等上几分钟甚至十几分钟。这时候,构建缓存就成了救命稻草。
我个人习惯把构建缓存比作「记忆」。你做过的事情,如果结果没变,就别再重复做一遍。Gradle的构建缓存,说白了就是把这个「记忆」存下来,下次直接复用。
构建缓存原理:它到底在缓存什么?
很多人以为构建缓存就是缓存编译后的class文件。其实不止。Gradle的构建缓存缓存的是任务的输出。每个Task执行完后,它的输出会被打上「输入指纹」——也就是输入文件、参数、依赖版本等信息的哈希值。下次执行时,如果输入指纹完全一致,Gradle就直接从缓存里拿结果,跳过任务执行。
我在项目中遇到过一个问题:明明代码没改,但每次构建都重新编译。查了半天,发现是某个插件在每次构建时生成了一个时间戳文件,导致输入指纹变了。嗯,这种坑真的很隐蔽。
为什么会这样?因为Gradle把每个Task都看作一个「黑盒」。只要输入不变,输出就应该不变。这个设计思路其实很优雅——你不需要关心Task内部做了什么,只需要保证输入是可复现的。
远程缓存配置:让团队共享缓存
本地缓存只能加速你自己的构建。但团队协作时,如果每个人都要自己「热缓存」,那效率还是上不去。远程缓存就是解决这个问题的。
远程缓存的配置其实不复杂。以Gradle Enterprise为例,你需要在settings.gradle.kts里加上插件和配置:
// settings.gradle.kts
plugins {
id("com.gradle.enterprise") version("3.15.1")
}
gradleEnterprise {
server = "https://你的企业服务器地址"
buildCache {
remote(HttpBuildCache) {
url = uri("https://你的缓存服务器地址/cache/")
credentials {
username = "缓存用户名"
password = "缓存密码"
}
push = true // 是否允许上传缓存
}
}
}
这里有个细节:push = true表示你的构建结果会上传到远程缓存。我建议只在CI/CD环境里开启push,开发机只拉取不上传。否则每个人都在上传,缓存服务器压力会很大。
缓存命中率优化:怎么让缓存「打中」?
缓存命中率低,说白了就是「输入指纹总在变」。我总结了几种常见原因:
- 绝对路径依赖:有些Task依赖了文件的绝对路径,比如
/home/user/project/...。换个机器路径就变了,缓存肯定不命中。 - 时间戳/随机数:Task里生成时间戳或UUID,每次都不一样,缓存直接失效。
- 依赖版本不固定:用了
+号版本号,比如com.example:lib:1.+。每次解析的版本可能不同,缓存就乱了。 - BuildConfig字段:如果BuildConfig里包含了构建时间、Git Commit等动态值,每次构建都会变。
怎么优化?我建议从这几个方向入手:
- 固定依赖版本:用
versions.toml统一管理,别用动态版本。 - 避免Task生成动态内容:如果必须生成时间戳,把它放到一个独立的Task里,并且不要缓存这个Task。
- 使用规范化路径:Gradle的
relativePath()可以帮你把绝对路径转成相对路径。 - 开启缓存调试:在
gradle.properties里加上org.gradle.caching.debug=true,可以看到每个Task的缓存命中情况。
@JvmStatic注解,结果每次构建都会重新生成一个匿名类,导致缓存永远不命中。排查了整整两天才发现。所以,如果你用了代码生成或注解处理器,一定要检查它们的输出是否稳定。
构建扫描分析:用数据说话
构建扫描是Gradle Enterprise提供的可视化工具。它能告诉你每个Task花了多少时间、缓存是否命中、依赖解析耗时等。我个人觉得,构建扫描是优化构建速度的「第一手资料」。
你可以在构建命令后加上--scan参数:
./gradlew assembleDebug --scan
执行完后,终端会输出一个链接。打开它,你会看到类似这样的信息:
| 指标 | 说明 | 优化方向 |
|---|---|---|
| Task执行时间 | 每个Task的耗时 | 找出耗时最长的Task,看能否缓存或优化 |
| 缓存命中率 | 从缓存中恢复的任务占比 | 如果低于80%,检查输入指纹变化原因 |
| 依赖解析时间 | 解析依赖库的时间 | 使用dependency locking或固定版本 |
| 配置阶段时间 | Gradle配置脚本的执行时间 | 减少allprojects和subprojects中的逻辑 |
你想想看,如果没有构建扫描,你只能靠猜来优化构建。有了数据,你就能精准定位问题。我每次接手一个新项目,第一件事就是跑一次--scan,看看构建的「体检报告」。
团队协作:让缓存成为团队习惯
构建缓存不是配好了就完事的。它需要团队共同维护。我见过不少团队,配了远程缓存,但没人用——因为大家不知道缓存的好处,或者不知道怎么用。
我建议从这几个方面入手:
- 统一Gradle版本:团队所有成员和CI使用同一个Gradle版本,否则缓存可能不兼容。
- 规范Task输入:写自定义插件时,明确声明
@Input和@Output,不要漏掉任何输入。 - 定期审查构建扫描:每周或每两周看一次构建扫描,发现缓存命中率下降就及时排查。
- CI/CD优先使用缓存:CI环境通常从零开始构建,远程缓存能大幅缩短构建时间。
--build-cache拉取远程缓存,如果缓存命中,构建时间能从10分钟降到1分钟。如果没命中,再走完整构建。这样既保证了速度,又不会因为缓存问题导致构建失败。
最后,我想说一句:构建缓存不是银弹。它解决的是「重复劳动」的问题,但如果你本身构建逻辑就有问题——比如Task设计不合理、依赖关系混乱——那缓存也救不了你。先把构建脚本写干净,再考虑缓存优化,这才是正路。