compose-performance-audit
41
总安装量
42
周安装量
#5128
全站排名
安装命令
npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill compose-performance-audit
Agent 安装分布
gemini-cli
32
claude-code
30
opencode
30
codex
28
antigravity
24
Skill 文档
Compose Performance Audit
Overview
Audit Jetpack Compose view performance end-to-end, from instrumentation and baselining to root-cause analysis and concrete remediation steps.
Workflow Decision Tree
- If the user provides code, start with “Code-First Review.”
- If the user only describes symptoms, ask for minimal code/context, then do “Code-First Review.”
- If code review is inconclusive, go to “Guide the User to Profile” and ask for Layout Inspector output or Perfetto traces.
1. Code-First Review
Collect:
- Target Composable code.
- Data flow: state, remember, derived state, ViewModel connections.
- Symptoms and reproduction steps.
Focus on:
- Recomposition storms from unstable parameters or broad state changes.
- Unstable keys in
LazyColumn/LazyRow(keychurn, missing keys). - Heavy work in composition (formatting, sorting, filtering, object allocation).
- Unnecessary recompositions (missing
remember, unstable classes, lambdas). - Large images without proper sizing or async loading.
- Layout thrash (deep nesting, intrinsic measurements,
SubcomposeLayoutmisuse).
Provide:
- Likely root causes with code references.
- Suggested fixes and refactors.
- If needed, a minimal repro or instrumentation suggestion.
2. Guide the User to Profile
Explain how to collect data:
- Use Layout Inspector in Android Studio to see recomposition counts.
- Enable Recomposition Highlights in Compose tooling.
- Use Perfetto or System Trace for frame timing analysis.
- Check Macrobenchmark results for startup/scroll metrics.
Ask for:
- Layout Inspector screenshot showing recomposition counts.
- Perfetto trace or System Trace export.
- Device/OS/build configuration (debug vs release).
Important: Ensure profiling is done on a release build with R8 enabled. Debug builds have significant overhead.
3. Analyze and Diagnose
Prioritize likely Compose culprits:
- Recomposition storms from unstable parameters or broad state changes.
- Unstable keys in lazy lists (
keychurn, index-based keys). - Heavy work in composition (formatting, sorting, object allocation).
- Missing
remembercausing recreations on every recomposition. - Large images without
Modifier.size()constraints. - Unnecessary state reads in wrong composition phases.
Summarize findings with evidence from traces/Layout Inspector.
4. Remediate
Apply targeted fixes:
- Stabilize parameters: Use
@Stableor@Immutableannotations on data classes. - Stabilize keys: Use stable, unique IDs for
LazyColumn/LazyRowitems. - Defer state reads: Use
derivedStateOf, lambda-based modifiers, orModifier.drawBehind. - Remember expensive computations: Wrap in
remember { }orremember(key) { }. - Skip recomposition: Extract stable composables, use
key()to control identity. - Async image loading: Use Coil/Glide with proper sizing constraints.
- Reduce layout complexity: Flatten hierarchies, avoid deep nesting.
Common Code Smells (and Fixes)
Unstable lambda captures
// 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) { ... }
Expensive work in composition
// 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) { ... } }
}
Missing keys in 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) }
}
Unstable data classes
// 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
)
Reading state too early
// 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
}) { ... }
}
Object allocation in composition
// 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)
Stability Checklist
| Type | Stable by Default? | Fix |
|---|---|---|
Primitives (Int, String, Boolean) |
Yes | N/A |
data class with stable fields |
Yes* | Ensure all fields are stable |
List, Map, Set |
No | Use ImmutableList from kotlinx |
Classes with var properties |
No | Use @Stable if externally stable |
| Lambdas | No | Use remember { } |
5. Verify
Ask the user to:
- Re-run Layout Inspector and compare recomposition counts.
- Run Macrobenchmark and compare frame timing.
- Test on a real device with release build.
Summarize the delta (recomposition count, frame drops, jank) if provided.
Outputs
Provide:
- A short metrics table (before/after if available).
- Top issues (ordered by impact).
- Proposed fixes with estimated effort.