关于 visualizing-recomposition-cascades
这项技能通过 Compose Stability Analyzer IntelliJ 插件排查 Jetpack Compose 重组,结合静态调用图和 Android 设备的运行时计数,帮助开发者评估影响并确认热点。
主要功能
- 01静态重组调用图树
- 02级联节点稳定性标记
- 03编辑器内实时重组计数
- 04按可组合项查看事件历史
- 05清除累计计数
适用场景
- 01重构前评估可组合项的下游影响
- 02查找滚动或动画期间的热点
- 03通过运行时行为验证静态分析
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ skydoves/ compose-performance-skills --skill visualizing-recomposition-cascades安装后在 Claude Code 中输入/visualizing-recomposition-cascades确认已加载。
详细介绍
可视化重组级联——IDE 中的静态影响范围和实时热力图
被动的装订线图标可以回答“这个可组合项是否可跳过”(详见 ../using-stability-analyzer-ide-plugin/SKILL.md)。主动调查工具则可以回答另外两个更棘手的问题。级联可视化工具从根 @Composable 出发遍历静态调用图,并显示如果根节点重组,其他哪些内容可能也会重组。实时热力图会订阅设备的 logcat,并在编辑器中每个经过插桩的可组合项上方,以块内嵌的形式显示实际重组次数。两者结合,便能将静态分析与运行时证据连接起来。
这两项功能都集成在同一个 IntelliJ 插件中,并通过右侧固定的 Compose Stability Analyzer 工具窗口呈现。该窗口有三个标签页:Explorer、Cascade 和 Heatmap。
何时使用此技能
- 开发者询问“如果我修改这个可组合项,会牵连哪些内容”——使用 Cascade 标签页。
- 开发者询问“那次滚动过程中它重组了多少次”或“这段动画中哪个可组合项最活跃”——使用 Heatmap 标签页。
- 排查
../../recomposition/debugging-recompositions/SKILL.md中发现的重组峰值,并需要查看调用点上下文。 - 准备重构,并希望在修改共享可组合项之前了解影响范围。
- 用户提到 Cascade 标签页、Heatmap 标签页、重组内嵌提示、Analyze Recomposition Cascade、Toggle Recomposition Heatmap 或 Clear Recomposition Data。
何时不使用此技能
- 纯粹的源码级诊断。
../using-stability-analyzer-ide-plugin/SKILL.md中的装订线图标和UnstableComposable检查足以满足需求。 - 在 CI 中拦截稳定性回归。请使用
../enforcing-stability-in-ci/SKILL.md。 - 使用 FrameTimingMetric 进行完整的发布级滚动/启动测量。请使用
../../measurement/generating-baseline-profiles/SKILL.md。此类测试运行前,请关闭热力图。 - 有关编译器如何判定类型的概念性问题。请使用
../understanding-stability-inference/SKILL.md。
前置条件
- 已安装
../using-stability-analyzer-ide-plugin/SKILL.md中的 IDEA 插件,并且右侧栏中显示 Compose Stability Analyzer 工具窗口。 - 对于 Cascade 工作流:无需额外准备;它只会对已打开项目的 PSI 进行静态分析。
- 对于 Heatmap 工作流:
- 至少有一个标注了
@TraceRecomposition的可组合项(运行时配置请参阅../../measurement/tracing-recompositions-at-runtime/SKILL.md)。 - 通过 ADB 连接 Android 设备或模拟器。
- 应用已安装并正在运行。
- 在
Application.onCreate中调用ComposeStabilityAnalyzer.setEnabled(true)(由BuildConfig.DEBUG或专用标志控制——参阅../../measurement/tracing-recompositions-at-runtime/SKILL.md)。
- 至少有一个标注了
工作流 A——级联可视化工具
A1. 将插入符放在 @Composable 内
操作 Analyze Recomposition Cascade(id com.skydoves.compose.stability.idea.cascade.AnalyzeCascadeAction)仅在插入符位于 @Composable 函数内部时启用。该操作已注册到编辑器弹出菜单中;在可组合项的函数体内任意位置右键,即可显示该操作。
A2. 运行操作
右键单击 → Analyze Recomposition Cascade。右侧锚定的 Compose Stability Analyzer 工具窗口会打开 Cascade 标签页,并显示以所选函数为根节点的树。
排查结果时需要了解的内部机制:
- 遍历从所选的
KtCallExpression开始,并深入访问通过resolveMainReference()解析的调用目标。 - 最大递归深度限制为 10 层,因此,即使组合函数数量非常多,级联也会在第 10 层停止,而不会无限展开。
- 循环检测使用完全限定名称的已访问集合;递归组合函数(例如,树渲染器)只会显示一次,不会再次展开。
- 每个节点都会显示稳定性徽标,使用的引擎与
../using-stability-analyzer-ide-plugin/SKILL.md中驱动装订线图标的引擎相同。
A3. 遍历并排查
按深度优先顺序遍历。对于每个节点:
- 查看稳定性徽标。根节点下游的红色节点表示,重新组合根节点时,该子树也会重新组合,而不会跳过。
- 双击节点可跳转到其源代码。
- 对于分支较多的情况,可通过稳定参数(
../stabilizing-compose-types/SKILL.md)进行剪枝,或在热点叶节点内部延迟读取状态(../../recomposition/deferring-state-reads/SKILL.md)。
A4. 将级联视为静态估算,而非运行时保证
级联是对调用图进行的静态遍历。每个下游节点在运行时是否实际重新组合,取决于编译器和运行时做出的稳定性与跳过决策。得出实际开销的结论前,请通过实时热力图(工作流 B)进行确认。
工作流 B — 实时热力图
B1. 为目标组合函数添加 @TraceRecomposition 注解
热力图由 @TraceRecomposition 运行时发出的 logcat 行提供数据。未插桩的组合函数不会显示内嵌提示。请使用 ../using-stability-analyzer-ide-plugin/SKILL.md 中的 Add @TraceRecomposition 意图操作,或手动添加注解。完整的运行时设置(门控、状态跟踪、限流)请参阅 ../../measurement/tracing-recompositions-at-runtime/SKILL.md。
B2. 运行应用并确认 logcat 输出
在已连接的设备上运行调试构建。打开 Logcat 工具窗口,并按标签 Recomposition 进行筛选。确认插桩的组合函数重新组合时会持续输出 D/Recomposition 行。插件解析器所需的确切格式:
[Recomposition #3] UserProfile (tag: user-screen) (2.30ms)
├─ [param] user: User changed (User@abc123 → User@def456)
├─ [param] count: Int stable (42)
├─ [state] counter: Int changed (5 → 6)
└─ Unstable parameters: [user]
如果没有显示日志行,说明运行时配置有误——请先返回 ../../measurement/tracing-recompositions-at-runtime/SKILL.md,再继续操作。
B3. 开启热力图
在编辑器中右键单击 → Toggle Recomposition Heatmap(操作 ID:com.skydoves.compose.stability.idea.heatmap.ToggleHeatmapAction)。也可以从 Code 菜单中找到此操作。
插件的 AdbLogcatService 会生成以下确切的 ADB 命令以订阅日志:
adb logcat -s Recomposition:D -T 1
-s Recomposition:D 用于筛选调试级别的标签;-T 1 表示从最新一行开始。如果连接了多个设备,会弹出设备选择器。
B4. 查看内嵌提示
HeatmapInlayManager 会在至少有一个事件流入的每个 @Composable 声明上方放置块内嵌提示。每个内嵌提示都显示:
- 开启热力图后(或上次运行 Clear Recomposition Data 后)的重新组合次数。
- 反映相对热度的颜色编码背景。
点击内嵌提示可将工具窗口切换到 Heatmap 标签页,并聚焦显示该组合函数的事件历史记录。将鼠标悬停在内嵌提示上,可通过 AdbLogcatService.getHeatmapData(name) 获取最新数据,并显示 5 秒的工具提示。
B5. 重置累计次数
运行 Code → Clear Recomposition Data(操作 ID:com.skydoves.compose.stability.idea.heatmap.ClearHeatmapDataAction),以在不同场景之间重置计数器。比较两个连续操作(例如,滚动列表与使用搜索筛选)时,可使用此功能,无需重启应用。
B6. 测量前关闭
再次切换热力图操作以停止监听器。热力图会在每次重组时执行 logcat I/O 和 PSI 工作;根据 ../../measurement/generating-baseline-profiles/SKILL.md,运行 Macrobenchmark 或生成 Baseline Profile 必须先将其关闭,否则额外开销会影响计时结果。
工具窗口结构
右侧固定的工具窗口,id 为 Compose Stability Analyzer(图标 /icons/stability.svg),由 StabilityToolWindowFactory 注册了三个标签页:
| 标签页 | 对应类 | 用途 |
|---|---|---|
| Explorer | StabilityToolWindow | 按模块 → 包 → 文件 → 可组合项构建的层级树,并显示稳定性标记。双击可跳转到源代码。可先用此视图浏览整个模块,再深入查看 Cascade 或 Heatmap。 |
| Cascade | CascadePanel | 运行 Analyze Recomposition Cascade 后填充。显示从所选根节点开始的静态调用图树。 |
| Heatmap | HeatmapPanel | 开启实时热力图后填充。显示每个可组合项的实时事件日志。 |
从 Explorer 标签页开始;针对目标深入查看 Cascade;开启 Heatmap 进行确认。
模式
模式:没有 @TraceRecomposition 的热力图不会生成内嵌提示
// WRONG
1. Toggle Recomposition Heatmap on.
2. ADB connects, logcat reader starts.
3. Scroll the screen aggressively.
4. No inlays appear above any composable.
5. Developer concludes "the heatmap is broken".
// WRONG because: the heatmap is fed by logcat lines emitted by `@TraceRecomposition`.
// Without at least one instrumented composable there is nothing to render.
// Add `@TraceRecomposition` per `../../measurement/tracing-recompositions-at-runtime/SKILL.md`,
// rebuild, and toggle again.
// RIGHT — instrument first, then toggle the heatmap
@TraceRecomposition(traceStates = true)
@Composable
fun PriceTicker(price: Price) {
Text(price.formatted)
}
模式:运行 Macrobenchmark 时不要使用热力图
// WRONG
1. Open Macrobenchmark scroll journey.
2. Toggle Recomposition Heatmap on (forgot from earlier session).
3. Run the benchmark.
4. FrameTimingMetric numbers regress vs. baseline; team panics.
// WRONG because: the heatmap drives logcat I/O and PSI updates on every recomposition.
// That overhead skews FrameTimingMetric and any release-grade measurement.
// Toggle the heatmap off before running `../../measurement/generating-baseline-profiles/SKILL.md`
// or any Macrobenchmark journey.
// RIGHT
1. Toggle Recomposition Heatmap off (verify no inlays in editor).
2. Run the Macrobenchmark journey.
3. Compare FrameTimingMetric vs. the committed baseline.
模式:不要把级联分析当作运行时保证
// WRONG
Cascade from PriceTicker shows 12 downstream composables.
Developer concludes "all 12 will recompose every tick" and refactors all 12.
// WRONG because: the cascade is a static call-graph walk. Whether each downstream node
// actually recomposes depends on stability and skipping at runtime. Several of the 12
// may already be skipping today; refactoring them is wasted work.
// Confirm with the live heatmap before drawing conclusions.
// RIGHT — the loop: cascade for blast radius, heatmap for confirmation, fix, re-measure
1. Analyze Recomposition Cascade from PriceTicker -> 12 nodes flagged.
2. Annotate the top 3 (by suspected hotness) with `@TraceRecomposition`.
3. Toggle Recomposition Heatmap on, run a representative scenario.
4. Inlays show 2 nodes with high counts; the other 10 stayed cold.
5. Fix the 2 hot nodes via `../stabilizing-compose-types/SKILL.md`.
6. Toggle the heatmap off, re-measure with `../../measurement/generating-baseline-profiles/SKILL.md`.
模式:根据稳定性而非结构进行剪枝
// WRONG
Cascade shows a wide subtree; developer wraps every leaf in `key { ... }` to force isolation.
// WRONG because: `key` does not make an unstable parameter stable. The leaves still
// receive unstable arguments; the parent still recomposes them. The fix is upstream:
// stabilize the type per `../stabilizing-compose-types/SKILL.md` so the existing
// skipping logic can do its job.
// RIGHT
Identify the unstable parameter via the cascade node's badge.
Stabilize the type at its declaration site.
Re-run the cascade to confirm the badge flipped to stable.
强制规则
- 必须在切换热力图前为目标可组合项添加
@TraceRecomposition注解。否则 logcat 流为空,也不会渲染内嵌提示。 - 必须根据
../../measurement/generating-baseline-profiles/SKILL.md,在运行 Macrobenchmark 或生成 Baseline Profile 前关闭热力图。logcat 开销会影响计时结果。 - 不得在生产环境中启用
ComposeStabilityAnalyzer.setEnabled(true)。根据../../measurement/tracing-recompositions-at-runtime/SKILL.md,应通过BuildConfig.DEBUG或专用标志进行控制。 - 不得将级联分析视为运行时保证。它只是静态影响范围估算;重构前应通过热力图确认。
- 不得以获得绿色级联分析为目标。可跳过性是一种诊断指标,而非 KPI;某些下游节点确实需要重组。
- **首选:**每次调查都从 Explorer 标签页开始,浏览模块;再针对目标根节点查看 Cascade;随后开启 Heatmap,根据运行时证据确认级联分析的预测。
- **首选:**连续比较两个交互场景时,在场景之间运行 Clear Recomposition Data,而不是重启应用。
- **首选:**每次热力图会话只关注一个屏幕。不同场景中的内嵌提示会不断累积,信噪比很快就会下降。
验证
- 在
@Composable内右键会显示 分析重组级联;在任何可组合项之外右键则不会显示。 - 级联选项卡会填充一棵以所选函数为根节点的树,并在深度达到 10 或遇到循环时停止展开。
- 双击级联节点会跳转到该可组合项的源代码。
- 切换重组热力图:连接了多个设备时会提示选择设备;仅连接一个设备时会自动启动。
- 开启热力图并应用
@TraceRecomposition后,Logcat 窗口中会显示D/Recomposition行,匹配的可组合项声明上方会出现代码内嵌提示。 - 点击内嵌提示会将工具窗口切换到 热力图 选项卡,并聚焦到该可组合项。
- 将鼠标悬停在内嵌提示上,会显示包含最新数据的 5 秒工具提示。
- 清除重组数据会重置累计计数和内嵌提示。
- 再次切换热力图操作会停止 ADB logcat 读取器(可通过关闭 Logcat 过滤器,并确认插件不再消耗
D/Recomposition内容来验证)。
参考资料
- skydoves/compose-stability-analyzer — https://github.com/skydoves/compose-stability-analyzer
- 该仓库中的
compose-stability-analyzer-idea模块 — https://github.com/skydoves/compose-stability-analyzer/tree/main/compose-stability-analyzer-idea - skydoves,《通过掌握稳定性优化应用性能》 — https://medium.com/proandroiddev/optimize-app-performance-by-mastering-stability-in-jetpack-compose-69f40a8c785d
- Compose 稳定性概览 — https://developer.android.com/develop/ui/compose/performance/stability
- Layout Inspector 重组计数(辅助工具)— https://developer.android.com/studio/debug/layout-inspector
- Android Logcat 命令行参考 — https://developer.android.com/tools/logcat
有关同一插件的编辑器内被动功能(边栏图标、悬停、内联提示、检查),请参阅 ../using-stability-analyzer-ide-plugin/SKILL.md。有关生成热力图所用 D/Recomposition 行的运行时插桩,请参阅 ../../measurement/tracing-recompositions-at-runtime/SKILL.md。有关在 CI 端针对稳定性回归进行门禁检查,请参阅 ../enforcing-stability-in-ci/SKILL.md。有关用于精简级联分支的上游类型修复,请参阅 ../stabilizing-compose-types/SKILL.md。有关如何延迟读取通过热力图发现的高频叶节点中的状态,请参阅 ../../recomposition/deferring-state-reads/SKILL.md。有关必须在关闭热力图的情况下运行的 Macrobenchmark 路径,请参阅 ../../measurement/generating-baseline-profiles/SKILL.md。