4、瀑布图(Flame Chart / Waterfall)基础概念

说实话,我刚接触构建性能分析那会儿,看到一堆花花绿绿的条条框框,头都是大的。什么火焰图、瀑布图、调用树——听着就晕。但后来我发现,只要搞懂其中一种,其他就能触类旁通。今天咱们就聊聊瀑布图(Waterfall),它在AGP版本升级和缓存瓶颈定位里,几乎是必用的工具。

4.1 什么是瀑布图?

瀑布图,也叫时序瀑布图,本质上是一张时间轴展开的任务执行记录。每一条横条代表一个任务(比如编译一个模块、执行一次AAPT2、运行一次DexBuilder),横条的长度表示它耗时多久,横条的排列顺序就是任务的执行顺序。

你可以想象一下:从构建开始到结束,所有任务像瀑布一样从上往下流淌。每个任务都会在它对应的时刻开始,然后结束。如果某个任务特别长,或者两个任务本该并行却串行了,一眼就能看出来。

核心价值:瀑布图能让你看见构建过程的时间消耗分布,而不是靠猜。

我个人习惯把瀑布图叫做「构建过程的X光片」——它把那些看不见的「慢点」赤裸裸地曝光出来。

4.2 瀑布图 vs 火焰图:别搞混了

你可能还听说过火焰图(Flame Chart)。很多新手会把这两者搞混,我刚开始学的时候也踩过坑。这里给你一张对比表,我建议你直接收藏:

维度 瀑布图(Waterfall) 火焰图(Flame Chart)
关注点 任务的执行时序与耗时 调用栈的深度与热区
横轴 时间 采样次数 / 耗时占比
纵轴 任务 ID 或线程 调用栈层级(越往上越底层)
典型用途 定位串行阻塞、缓存失效、资源等待 定位热点函数、CPU 占用、算法瓶颈
AGP 升级场景 非常有用,能看出哪个任务拖慢了整体 较少直接使用,适合分析编译内部 hook

说白了,瀑布图回答「哪里等得久」,火焰图回答「哪里算得久」。在AGP版本升级和缓存失效排查中,我们更关心「时间线上的等待」,所以瀑布图是主角。

我的经验:有一次团队升级AGP 7.0到8.0,构建时间反而变长了。大家怀疑是新版问题。我导出一次完整构建的Profile,用瀑布图一看——原来是某个第三方插件把一个并行任务改成了串行。瀑布图里两条本该重叠的横条变成了首尾相接,一目了然。

4.3 瀑布图的核心构成要素

要读懂瀑布图,你得先认识它的几个基本元素。下面这张图我手绘了一个简化版,你可以对照着看:

瀑布图核心要素示意图 时间 0ms 500ms 1000ms 1500ms 任务A (MergingManifest) 任务B (AAPT2 Compile) 任务C (MergeResources) 任务D (DexBuilder) — 缓存失效导致重编译 ⚠ 阻塞开始(等待前序任务) 任务E (ProcessResources, 延迟启动) 💡 瀑布图中如何看缓存效果? • 如果某个任务横条很短(<200ms),说明缓存命中,直接复用上次结果。 • 如果横条很长且连续出现,极有可能是缓存未命中,整条链被重新执行。 • 多条横条首尾相连(串行)而非重叠(并行),可能是资源锁或依赖冲突。 瀑布图 → 瓶颈定位 → 优化目标 每次升级AGP后,建议先跑一次完整构建,导出瀑布图做对比

从上图你能看到几个关键点:

  • 横条长度 = 任务耗时,越长越可疑。
  • 横条重叠 = 并行执行,理想情况。
  • 横条首尾相接 = 强制串行,可能因为依赖关系或全局锁。
  • 红色或橙色横条(在我常用的工具里)往往标注出异常耗时。

4.4 瀑布图的常见数据来源

在Android构建领域,你能拿到瀑布图的地方主要有三个:

  1. Android Studio Build Analyzer —— 最方便,直接在IDE里就能看。AGP 7.0以上自带。
  2. Gradle Profiler —— 命令行工具,输出JSON/HTML报告,适合CI环境自动化采集。
  3. Build Scan(Gradle Enterprise) —— 企业版,带时序视图,但需要付费。

我个人偏爱Build Analyzer,因为操作简单。但你如果做自动化对比,Gradle Profiler更合适。

注意:Build Analyzer默认只记录任务级耗时,不包含Gradle配置阶段(Configuration)。如果你发现配置阶段过长,需要用 --profile 或 --scan 单独分析。

4.5 如何从瀑布图里找到缓存瓶颈?

好,理论讲完了,咱们来点实战的。假设你刚升级完AGP,发现构建时间没降反升。打开瀑布图,你应该优先找这几种「危险信号」:

4.5.1 信号一:连续很多个「满长度」的任务

你看,正常缓存命中时,像 compileDebugJavaWithJavac 这类任务应该只有几百毫秒。但如果它每次都是好几秒——说明增量编译失效了。我遇到过一回,发现是因为AGP 8.0把某些注解处理器的incremental选项改成了默认关闭,需要手动开启。

4.5.2 信号二:多个任务之间出现大段空白

如果两张横条之间有一段「空窗期」(没有任务在跑),那很可能是等待I/O或者外部资源。比如,一些自定义Transform任务会写文件到磁盘,如果同时还有别的任务也在写同一块目录,就会互相等待。这种在瀑布图上表现为一条条空档。

4.5.3 信号三:某个任务反复出现「突变」

你连续跑两次同样的构建,对比瀑布图。如果某个任务前一次耗时0.5秒,后一次变成5秒——那就是缓存被清了。我曾经碰到过,因为某插件在clean阶段偷偷删了build/cache目录,导致整个缓存失效。查了一天才找到。

避坑指南:我曾经被「假并行」骗过。瀑布图里两个任务看起来重叠,但实际上它们共享同一个工作线程池,只是时间片轮转。真正的并行要看线程ID或者Executor是否独立。所以分析时别只看横条重叠,还得看底层的线程分配。

4.6 实操:5分钟导出你的第一张瀑布图

我建议你现在就动手试一下。步骤如下:

  1. 在Android Studio中打开你的项目。
  2. 点击菜单栏 Build → Build Analyzer。
  3. 点击 Run Build,等待一次完整的assembleDebug执行完毕。
  4. 在打开的Build Analyzer界面里,选择 Timeline 标签页。
  5. 你就看到了真·瀑布图。

这时你可以用鼠标拖动、缩放,把所有任务摊开。重点看:

  • compile 相关的任务是否耗时异常?
  • mergeResources、processResources 是不是每次都在重新生成?
  • 有没有某些任务被标记为 UP-TO-DATE 但实际却跑了?(这是一个常见的Gradle缓存bug)

如果发现某些任务的耗时超过了你的预期,别急——下一节我们会专门讲怎么拆解这些长耗时任务,看看问题到底出在CPU、I/O还是锁竞争上。

嗯,瀑布图这东西,第一次看可能觉得眼花。但相信我,多用几次,你就能一秒揪出「谁在摸鱼」。它是AGP升级之旅当中,你最值得信赖的伙伴之一。