关于 compose-performance-audit
compose-performance-audit 是面向 Android 开发者的 Jetpack Compose 性能审计技能,覆盖代码检查、性能数据分析和定向修复。它会根据代码或性能症状选择审查路径,并在代码证据不足时引导采集 Layout Inspector 或 Perfetto 数据。
主要功能
- 01审查重组、状态与参数稳定性
- 02检查惰性列表键和组合阶段工作
- 03分析 Layout Inspector 与 Perfetto 数据
- 04提供针对性修复建议
- 05比较基准测试与帧时表现
适用场景
- 01排查 Compose 界面卡顿
- 02检查 LazyColumn 项目键导致的重组
- 03分析组合阶段的高开销计算
- 04通过性能追踪验证优化结果
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ new-silvermoon/ awesome-android-agent-skills --skill compose-performance-audit安装后在 Claude Code 中输入/compose-performance-audit确认已加载。
详细介绍
Compose 性能审计
概述
端到端审计 Jetpack Compose 视图性能,涵盖从性能检测和基线测试到根因分析与具体修复步骤的全过程。
工作流决策树
- 如果用户提供了代码,请从“代码优先审查”开始。
- 如果用户只描述了症状,请索取最少量的代码和上下文,然后进行“代码优先审查”。
- 如果代码审查无法得出结论,请进入“指导用户进行性能分析”,并索取 Layout Inspector 输出或 Perfetto 跟踪记录。
1. 代码优先审查
收集:
- 目标 Composable 代码。
- 数据流:状态、remember、派生状态、ViewModel 连接。
- 症状和复现步骤。
重点关注:
- 参数不稳定或状态变更范围过大导致的重组风暴。
LazyColumn/LazyRow中的不稳定键(key频繁变化、缺少键)。- 组合期间的繁重工作(格式化、排序、筛选、对象分配)。
- 不必要的重组(缺少
remember、类不稳定、lambda)。 - 未正确设置尺寸或未异步加载的大型图片。
- 布局抖动(嵌套过深、固有尺寸测量、误用
SubcomposeLayout)。
提供:
- 结合代码引用指出可能的根因。
- 建议修复方案和重构方式。
- 如有需要,提供最小复现示例或性能检测建议。
2. 指导用户进行性能分析
说明如何收集数据:
- 使用 Android Studio 中的 Layout Inspector 查看重组次数。
- 在 Compose 工具中启用 Recomposition Highlights。
- 使用 Perfetto 或 System Trace 分析帧耗时。
- 检查 Macrobenchmark 的启动/滚动指标结果。
索取:
- 显示重组次数的 Layout Inspector 截图。
- Perfetto 跟踪记录或 System Trace 导出文件。
- 设备/OS/构建配置(调试版还是发布版)。
重要:确保在启用 R8 的发布版构建上进行性能分析。调试版开销很大。
3. 分析与诊断
优先检查可能导致问题的 Compose 因素:
- 参数不稳定或状态变更范围过大导致的重组风暴。
- 懒加载列表中的不稳定键(
key频繁变化、基于索引的键)。 - 组合期间的繁重工作(格式化、排序、对象分配)。
- 缺少
remember,导致每次重组都重新创建对象。 - 未使用
Modifier.size()约束的大型图片。 - 在错误的组合阶段进行不必要的状态读取。
结合跟踪记录/Layout Inspector 中的证据总结调查结果。
4. 修复
应用有针对性的修复:
- 稳定参数:为数据类添加
@Stable或@Immutable注解。 - 稳定键:为
LazyColumn/LazyRow项使用稳定且唯一的 ID。 - 延迟读取状态:使用
derivedStateOf、基于 lambda 的修饰符或Modifier.drawBehind。 - 记住耗时计算:使用
remember { }或remember(key) { }包装。 - 跳过重组:提取稳定的 composable,使用
key()控制身份。 - 异步加载图片:使用 Coil/Glide,并设置适当的尺寸约束。
- 降低布局复杂度:扁平化层级,避免深层嵌套。
常见代码异味(及修复方法)
不稳定的 lambda 捕获
// BAD: New lambda instance every recomposition
Button(onClick = { viewModel.doSomething(item) }) { ... }
// GOOD: Use remember or method reference
val onClick = remember(item) { { viewModel.doSomething(item) } }
Button(onClick = onClick) { ... }
组合期间的耗时工作
// BAD: Sorting on every recomposition
@Composable
fun ItemList(items: List<Item>) {
val sorted = items.sortedBy { it.name } // Runs every recomposition
LazyColumn { items(sorted) { ... } }
}
// GOOD: Use remember with key
@Composable
fun ItemList(items: List<Item>) {
val sorted = remember(items) { items.sortedBy { it.name } }
LazyColumn { items(sorted) { ... } }
}
LazyColumn 中缺少键
// BAD: Index-based identity (causes recomposition on list changes)
LazyColumn {
items(items) { item -> ItemRow(item) }
}
// GOOD: Stable key-based identity
LazyColumn {
items(items, key = { it.id }) { item -> ItemRow(item) }
}
不稳定的数据类
// BAD: Unstable (contains List, which is not stable)
data class UiState(
val items: List<Item>,
val isLoading: Boolean
)
// GOOD: Mark as Immutable if truly immutable
@Immutable
data class UiState(
val items: ImmutableList<Item>, // kotlinx.collections.immutable
val isLoading: Boolean
)
过早读取状态
// BAD: State read during composition (recomposes whole tree)
@Composable
fun AnimatedBox(scrollState: ScrollState) {
val offset = scrollState.value // Recomposes on every scroll
Box(modifier = Modifier.offset(y = offset.dp)) { ... }
}
// GOOD: Defer state read to layout/draw phase
@Composable
fun AnimatedBox(scrollState: ScrollState) {
Box(modifier = Modifier.offset {
IntOffset(0, scrollState.value) // Read in layout phase
}) { ... }
}
组合期间的对象分配
// BAD: Creates new Modifier chain every recomposition
Box(modifier = Modifier.padding(16.dp).background(Color.Red))
// GOOD for dynamic modifiers: Remember the modifier
val modifier = remember { Modifier.padding(16.dp).background(Color.Red) }
Box(modifier = modifier)
稳定性检查清单
| 类型 | 默认是否稳定? | 修复方法 |
|---|---|---|
基本类型(Int、String、Boolean) | 是 | 不适用 |
包含稳定字段的 data class | 是* | 确保所有字段均稳定 |
List、Map、Set | 否 | 使用 kotlinx 中的 ImmutableList |
带有 var 属性的类 | 否 | 如果从外部看是稳定的,则使用 @Stable |
| Lambda | 否 | 使用 remember { } |
5. 验证
请用户:
- 重新运行 Layout Inspector,并比较重组次数。
- 运行 Macrobenchmark,并比较帧耗时。
- 在真实设备上使用 release 构建版本进行测试。
如果用户提供了数据,请汇总变化情况(重组次数、掉帧、卡顿)。
输出
提供:
- 简短的指标表(如有数据,列出优化前/后)。
- 按影响程度排序的主要问题。
- 拟议的修复方案及预估工作量。
参考资料
评论
支持 Markdown