--scan 是 Gradle 最可靠、开箱即用的构建耗时对比方式,支持跨构建比对,可精准识别任务变慢、缓存失效、配置阶段增长等问题,且需确保同一环境、版本与 Daemon 状态以排除干扰。

用 --scan 生成可比对的构建报告
Gradle 自带的 --scan 是目前最可靠、开箱即用的耗时对比方式。它不依赖本地插件,也不修改构建脚本,生成的网页报告里包含完整任务树、各阶段耗时、缓存命中率、依赖下载详情等维度,且支持跨构建比对。
执行两次构建时都加 --scan 参数,比如:
./gradlew assembleDebug --scan
./gradlew assembleDebug --scan
两次构建完成后,你会收到两个独立的 Build Scan URL。打开后点击右上角 “Compare builds”,粘贴另一个 URL 就能直接拉出差异视图——哪些任务变慢了、哪些缓存失效了、配置阶段是否增长,一目了然。
- 必须用同一台机器、同一 Gradle 版本、同一 AGP 版本对比,否则 JVM 启动、Daemon 状态、缓存路径等变量会干扰结果
- 如果提示 “Scan not enabled”,说明项目未启用 Build Scan(Gradle 6.0+ 默认支持,但部分 CI 环境或老版 wrapper 可能禁用),临时加一行
org.gradle.configuration-cache=warn到gradle.properties通常可解 - Build Scan 的“Configuration phase”耗时变化比总耗时更敏感——很多优化(如移除
subprojects{}中的重复逻辑)只影响这一块,但总时间可能只差 1–2 秒,容易被忽略
--profile 报告适合快速定位单次瓶颈,但不适合跨构建对比
--profile 生成的是本地 HTML 报告,路径在 build/reports/profile/ 下,结构清晰,能看每个 task 的执行耗时和依赖关系。但它没有内置比对功能,也不记录缓存状态,纯靠人眼对两份 HTML 文件手动翻找差异,效率低、易出错。
- 适合“这次构建特别慢,我马上要查哪几个 task 拖后腿”的即时诊断场景
- 它的“Configuration time”是整个配置阶段总耗时,但不区分 root project 和 subproject;而
--scan能精确到每个build.gradle(.kts)文件的解析耗时 - 注意:同一命令连续跑两次
--profile,第二次的报告里“Task execution”耗时可能明显下降(因 Daemon 复用、JVM 预热),但这不是真实优化效果,只是 warm build 基线漂移
自己写脚本统计时,必须固定测量点
如果要用 shell 或 Python 脚本记录 wall-clock 时间(比如 time ./gradlew assembleDebug),关键不是测“总秒数”,而是确保每次测量覆盖**完全一致的生命周期边界**。
- 不要只测
./gradlew命令行耗时——它包含 shell 启动、参数解析、甚至终端渲染时间;应统一用SECONDS=0; ./gradlew assembleDebug; echo $SECONDS - 真正有意义的对比维度是:
buildFinished回调触发时刻减去projectsLoaded完成时刻,这个区间排除了 Daemon 启动和 settings 解析,接近纯构建逻辑耗时;可用 Gradle 生命周期钩子注入打印(见知识库中 3.27 节代码) - CI 环境下务必关闭
--no-daemon,否则每次都是冷启动,测出来的“差异”其实是 JVM 初始化波动,不是构建逻辑问题
容易被忽略的干扰项:Daemon 状态和 AGP 行为变更
两次构建之间看似只改了一行配置,但实际耗时差异可能来自完全无关的底层机制变化。
- 执行
./gradlew --stop再构建,等于主动放弃 Daemon 复用;而 IDE 启动的构建默认走 Daemon,命令行有时却没配好,导致对比失真 - AGP 8.2 升级到 8.3 后,
aapt2默认启用增量资源编译,但某些资源命名冲突会让它 fallback 到全量处理——这种变化不会体现在 task 名称里,但--scan的 “Task inputs” 标签页会标红提示 “inputs changed” - 模块化项目中,某个子模块的
build.gradle.kts新增了apply(plugin = "com.android.library"),会导致整个配置阶段重算——--scan的 “Configuration cache” 标签页会直接报 “not cacheable due to plugin application”
真正可靠的对比,永远建立在控制变量之上:同一环境、同一 Daemon、同一 AGP、同一缓存开关状态。其余任何“看起来一样”的对比,大概率是在比较噪音。










