mcpField
C
compose-performance-audit

compose-performance-audit

github.com/new-silvermoon测试与质量992570 次安装13 周前更新
代码仓库

关于 compose-performance-audit

compose-performance-audit 是面向 Android 开发者的 Jetpack Compose 性能审计技能,覆盖代码检查、性能数据分析和定向修复。它会根据代码或性能症状选择审查路径,并在代码证据不足时引导采集 Layout Inspector 或 Perfetto 数据。

主要功能

  1. 01审查重组、状态与参数稳定性
  2. 02检查惰性列表键和组合阶段工作
  3. 03分析 Layout Inspector 与 Perfetto 数据
  4. 04提供针对性修复建议
  5. 05比较基准测试与帧时表现

适用场景

  1. 01排查 Compose 界面卡顿
  2. 02检查 LazyColumn 项目键导致的重组
  3. 03分析组合阶段的高开销计算
  4. 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