瀑布图概念:一张图讲清楚编译到底慢在哪
老实说,我刚接触 Android 构建性能优化时,一看到「瀑布图」这三个字,第一反应是「这不就是甘特图吗?」后来用多了才发现,两者完全不是一回事。瀑布图(Waterfall Chart)在编译分析领域,它的核心价值就是一件事:把耗时拆开给你看。
你想想看,Hilt 注解处理到底有多慢?慢在「KAPT 的整体执行时间」还是慢在「KSP 的符号解析」?单看日志里的一行数字,你根本说不清。但丢进瀑布图里,一眼就能找到那个最宽的色块——瓶颈就藏在那里。
什么是瀑布图
说白了,瀑布图就是一种堆叠式柱状图,但它把「每个步骤的开始、持续、结束」画成了水平方向流动的横条。每个横条代表一个任务或阶段,横条长度代表耗时,多个横条按时间轴对齐排列,看起来像水流从左边流到右边。
瀑布图的三个核心特征:
- 时间轴:横轴是时间,从构建开始到结束
- 任务条:每个矩形条代表一个独立阶段(比如 KAPT、Dagger 代码生成、javac 编译等)
- 堆叠关系:任务可以并行或串行,条形图会根据依赖关系首尾相连或堆叠在一起
我这里举个实际例子。我在优化一个老项目时,Hilt 注解处理占了整个构建时间的 60% 以上。光看 Gradle 输出的「:app:kaptDebugKotlin 12s」这一行,你只知道它用了 12 秒,但这 12 秒里有多少是 Hilt 的处理器运行?有多少是 javac 的编译?瀑布图一展开,才发现 Hilt 本身只用了 3 秒,剩下的 9 秒全浪费在 Proguard 的混淆等待上——因为并行调度没做好。这就是瀑布图的威力。
为什么用它分析编译延迟
传统方式看构建日志,一行一行翻,你最多知道每个 task 的总耗时。但 Hilt 注解处理的延迟往往不是单个 task 的问题,而是多个 task 之间的依赖阻塞、线程池竞争引起的。
举个例子,Hilt 的处理器需要在所有源文件解析完之后才能生成代码,如果之前的 KAPT 阶段因为某个类的循环依赖被卡住了,你从日志里根本看不出。瀑布图能让你直观地看到:
- 哪个阶段开始时出现了明显的空档(CPU 空转)
- 哪个并行任务被意外串行化(横条没有重叠)
- 哪个阶段结束时间远晚于预期(棒状条拖尾很长)
我的一个经验:如果你看到两个本该并行的 task 横条是首尾相接的,那十有八九是存在隐式依赖。我在排查 Hilt 编译变慢时,就遇到过 hiltJavaCompileDebug 和 kaptDebugKotlin 被强制串行,原因居然是有一个 Android 插件配置了错误的 mustRunAfter。瀑布图一拉出来,问题当场就暴露了。
所以,用瀑布图分析编译延迟,本质上是在做「时序归因」——不是问「这个 task 有多慢」,而是问「慢是发生在等待依赖,还是发生在执行本身」。
常用工具
Android 生态里,分析编译瀑布图有三件套,我个人最常用前两个:
| 工具 | 适用场景 | 数据来源 |
|---|---|---|
| Android Profiler | 实时或历史的 CPU、内存、网络分析,也能看系统 trace 中的 task 调度 | Android 系统 trace |
| Build Analyzer | Gradle 构建的官方分析工具,直接输出瀑布图和关键指标 | Gradle build scan / profile 文件 |
| Gradle Profiler | 用于压力测试和对比不同配置对构建耗时的影响 | 自定义 benchmark 脚本 |
Android Profiler
这个你应该不陌生,它主要用来分析 App 运行时的性能。但你可能不知道,它也能打开(虽然我不推荐用它看构建)。我更习惯用它来抓取系统级别的 sys trace,观察编译过程中 CPU 的调度情况——比如多个编译器线程是否因为绑核而等待。
不过要注意,它不太擅长展示 Gradle 阶段的耗时。所以 Hilt 延迟排查时,我通常把它作为辅助手段,看「系统有没有在正常调度」。
Build Analyzer
这才是真正的杀手锏。Android Studio 4.1 之后内置了这个工具,每次构建完成都会生成一份报告。你点开 View -> Tool Windows -> Build Analyzer 就能看到。它直接给你画好瀑布图,按 Gradle Task 分组,每个 Task 可展开看到子任务(比如 KAPT 的各个 processor)。
踩坑提醒:我曾经发现 Build Analyzer 导出的瀑布图里,Hilt 相关的 task 名称是 :app:hiltJavaCompileDebug,但耗时显示为 0。一开始以为没执行,查了半天才发现是 Build Analyzer 的 bug——它把耗时算到了 parent task 里。后来我改用 Gradle Build Scan 的 --scan 参数查看原始数据才确认。嗯,这里的建议是:不要完全依赖工具的可视化结果,必要时直接看 build/reports/profile 下的原始 json。
具体怎么用?你只需要在命令行加一个 flag:
./gradlew assembleDebug --profile
然后打开 build/reports/profile/profile-timeline.html,就能看到一份基于 HTML 的瀑布图。也能用它配合 Build Analyzer 一起看。
一句话总结这三个工具的角色:
- Android Profiler 看「系统调度是否合理」
- Build Analyzer 看「Gradle 阶段耗时分布」
- Gradle Profiler 看「不同优化方案的对比效果」
知识体系图解
下面这张图把本章的核心内容串了一下,方便你理解瀑布图在 Hilt 延迟排查中的位置。
我的小建议:本章内容偏概念,但非常基础。实际排查时,你只需要记住「瀑布图是时间轴的快照」这个本质。一开始看瀑布图可能会被各种色块吓到,别慌——先找到最宽的那一块,那是你的主要瓶颈,把它记下来,然后用 Build Analyzer 看这块的内部细节。Hilt 相关的 task 一般都会带有 hilt 或 dagger 关键词,很好定位。