Build Scan 是 Gradle 官方云端可观测性服务,需显式启用且依赖 gradle.properties 配置、交互授权及 --no-daemon 等条件;而 --profile 仅为本地静态 HTML 报告,仅记录单次 task 耗时与顺序,不支持跨构建对比与深层指标分析。

什么是 Build Scan,它和 --profile 有什么区别
Build Scan 是 Gradle 官方提供的云端可观测性服务,不是本地 HTML 报告。它默认不开启,必须显式启用;而 --profile 是纯本地生成静态 HTML,不上传、无聚合、不支持跨构建对比。--profile 只记录单次构建的 task 执行耗时与顺序,但无法告诉你 kapt 为什么比上次多花了 1200ms,也看不出 :app:mergeDebugResources 的耗时是否随模块增多线性增长。Build Scan 则会捕获 Configuration 阶段耗时、task 输入指纹、缓存命中状态、JVM GC 次数等深层信号——这些正是定位“看似没改代码却变慢”的关键。
如何开启并确保 Build Scan 数据可信
直接加 --scan 参数就行,但有三个实际容易忽略的条件:
- 项目根目录下必须存在
gradle.properties,且至少包含org.gradle.configuration-cache=true(AGP 8.0+ 推荐)或org.gradle.parallel=true,否则部分扫描数据会缺失 - 首次运行会弹出交互式授权提示,必须输入
y并回车,否则后续构建不会上传(静默失败,无报错) - CI 环境中需额外加
--no-daemon或确保 Daemon 处于 warm 状态,否则扫描可能只捕获到 JVM 启动开销,而非真实 task 耗时
推荐命令组合:./gradlew assembleDebug --scan --no-daemon --console=plain。加上 --console=plain 是为了防止 ANSI 控制符污染扫描日志解析。
在 Build Scan 页面里快速定位真·耗时任务
打开生成的扫描链接后,不要从首页总时间开始猜。重点关注三个面板:
-
Performance > Task execution:按“Self time”倒序排列,排除掉
compileJava这类必然长的任务,盯住那些“Self time 高 + Invocations 多”的任务,比如反复执行的generateJsonModelDebug,说明增量编译失效 -
Configuration > Configuration time:如果这里耗时超过 800ms,说明
build.gradle里写了阻塞式逻辑(如fileTree遍历、new URL(...).text),而不是 lazy configuration -
Build cache > Hits / Misses:若
:app:compileDebugKotlin缓存命中率低于 30%,要检查kotlinOptions.freeCompilerArgs是否含动态路径或时间戳参数
特别注意:Build Scan 中的 :app:transformClassesWithDexBuilderForDebug 耗时,往往掩盖了真正问题——它高,大概率是因为上游 compile 产出 class 文件太多,而不是 DexBuilder 本身慢。得往上游点开 “Dependencies” 查看输入文件数量。
Build Scan 的坑:什么时候它反而会误导你
Build Scan 不是万能诊断仪,以下情况它给出的时间数据不可直接采信:
- 本地开发时用了
./gradlew --offline,但又开了--scan:扫描仍会生成,但所有网络相关 task(如resolve dependencies)的耗时会被标记为 0ms 或负值 - AGP 版本 android.useNewResourceProcessing=true:
mergeDebugResources在扫描里显示极低,实际耗时被拆到processDebugResources和compileDebugSources里,需要手动关联 - 自定义 Task 使用了
@InputDirectory但没加skipWhenEmpty = true:扫描会把空目录遍历也计入耗时,看起来像 I/O 瓶颈,其实是配置缺陷
最常被忽略的一点:Build Scan 默认只保留最近 30 天数据,且免费版不支持 API 导出。如果你要做周维度趋势分析,必须配合本地 --profile 报告 + 时间戳命名,定期归档 HTML 文件——扫描是快照,不是数据库。











