构建扫描(Build Scan)介绍
什么是构建扫描?说简单点,它就是 Gradle 给我们的一个“黑匣子记录仪”。每次构建结束后,你可以选择生成一个远程链接,点开这个链接就能看到这次构建的每个细节——哪个任务跑得久、哪些依赖被缓存了、配置阶段花了多少秒,甚至哪些插件起了作用。我最初接触它时还觉得多此一举,直到有一次线上构建突然慢了三倍,靠肉眼翻日志找了半天没头绪,最后是构建扫描直接告诉我是某个自定义任务重复执行了十几次。嗯,从那时起我就养成了习惯:每次优化构建前,先扫一遍。
什么是构建扫描
构建扫描(Build Scan)是 Gradle 提供的一项服务。你只需要在构建时加一个参数,构建完成后就会得到一个 https://scans.gradle.com/s/xxxx 格式的链接,任何人都可以通过这个链接查看详细的构建报告。
这份报告里有什么?我列几个核心模块:
| 模块 | 内容 | 价值 |
|---|---|---|
| 任务耗时 | 每个 Gradle Task 的执行时间、是否被跳过、是否增量 | 一眼看出最耗时的任务 |
| 依赖详情 | 所有 resolved 的依赖、缓存命中情况、版本冲突解决 | 判断缓存是否生效,依赖解析是否拖慢 |
| 配置阶段 | Gradle 初始化 + 配置所花时间,包括每个插件和脚本 | 定位“配置慢”的根源 |
| 输出大小 | 每个任务的输出产物体积、是否被压缩 | 避免大文件 IO 导致磁盘瓶颈 |
| 构建失败分析 | 具体失败原因、堆栈、错误日志上下文 | 快速定位构建失败的原因 |
说白了,构建扫描把 Gradle 内部那个复杂的执行流水线,变成了一个你能看懂、能点击的网页。你不用再猜“到底是编译慢还是测试慢”,数据直接甩你脸上。
为什么需要构建扫描
你可能觉得:我本地跑构建,自己看控制台输出不就行了?控制台只会告诉你“BUILD SUCCESSFUL in 2m 45s”,你连哪一步花了 2 分钟都不知道。而构建扫描能给出毫秒级别的粒度,我甚至会用它对比两次构建的差异——比如上一轮 2 分 45 秒,这一轮 2 分 10 秒,究竟少了哪个任务?扫一眼就明白。
我在项目中遇到过几个典型场景,没有构建扫描会非常痛苦:
- 本地快,CI 慢——在 CI 上生成一次构建扫描,对比参数就能发现是资源限制、还是并发策略不同。
- 升级 Gradle 版本后变慢——同时生成升级前后的扫描,任务耗时差异一目了然,不需要靠猜。
- 插件行为诡异——有一回某个插件把所有的源码文件都重新生成了一遍,我就是在扫描的任务输出列表里发现它的“输出大小”异常大,顺着找到了 bug。
--scan 参数生成本地 XML 报告(不过不方便分享)。个人建议在 CI 上每次构建都生成一份,方便回查。
如何生成构建扫描
老版本(Gradle 4.3 之前)需要额外安装 Build Scan Plugin,现在的新版本直接内置了。只需要:
./gradlew build --scan
然后在提示下输入 yes(或者设置环境变量自动同意):
Publishing build scan...
https://scans.gradle.com/s/abcdef12345
打开链接,你就进入了一个类似数据仪表盘的页面。我个人习惯在 CI 脚本中加上 --scan 并与 CI_PIPELINE_ID 关联,方便以后按构建编号检索。
还有一个技巧:如果不想每次都手动输 yes,可以在 gradle.properties 里加上:
org.gradle.scan.acceptTermsOfService=true
这样扫描会自动生成,省去交互步骤。改一次,后面就不用管了。
--no-build-cache 对比缓存开启和关闭时的扫描结果,看看缓存到底起了多大作用。我曾经用这个方法发现某个模块的缓存命中率只有 3%,果断砍掉了它的缓存配置。
这张图展示了从构建到分析的完整链路。注意每个分析维度并不是孤立的——比如发现“任务耗时”很大,你接着要去“依赖缓存分析”看看是不是缓存没命中,再去“配置阶段”看看是不是脚本惹的祸。我一般先看总耗时分布,顺着红点(耗时异常的任务)往下追。