compose-stability-diagnostics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCompose stability diagnostics
Compose稳定性诊断
Core principle
核心原则
Compose performance problems from parameters are about whether inputs compare cheaply and predictably across recompositions. With Kotlin 2.0.20+ strong skipping is enabled by default, so unstable parameters no longer automatically make restartable composables non-skippable. That does not make stability irrelevant: unstable parameters are compared by instance identity (), stable parameters by equality (), and churny instances can still defeat skipping.
===equalsFirst identify the compiler mode you are on, then read reports in that context.
Compose中由参数引发的性能问题,核心在于跨重组时输入是否能低成本且可预测地进行比较。在Kotlin 2.0.20+版本中,强跳过默认启用,因此不稳定参数不再会自动导致可重启的Composable不可跳过。但这并不意味着稳定性无关紧要:不稳定参数通过实例身份()进行比较,稳定参数通过相等性()比较,而频繁变化的实例仍会破坏跳过机制。
===equals首先确定你使用的编译器模式,再结合该上下文解读报告。
When to use this skill
何时使用此技能
- A composable or screen recomposes more than expected and parameter churn is suspected.
- A UI-state/model class is passed to composables and contains ,
List,Set, ranges, Java time/money types, or third-party types.Map - /
composables.txtshows unstable parameters or non-skippable composables.classes.txt - A project uses Kotlin < 2.0.20, disables strong skipping, or has old Compose compiler report guidance.
- Composable或界面的重组次数超出预期,且怀疑是参数频繁变化导致。
- UI状态/模型类被传递给Composable,且包含、
List、Set、范围、Java时间/货币类型或第三方类型。Map - /
composables.txt显示存在不稳定参数或不可跳过的Composable。classes.txt - 项目使用Kotlin < 2.0.20版本、禁用强跳过,或遵循旧版Compose编译器报告指南。
1. Start with strong skipping
1. 从强跳过开始
On Kotlin 2.0.20+, strong skipping is enabled by default. In that mode:
- Restartable composables are skippable even when parameters are unstable, unless explicitly opted out.
- Stable parameters compare with .
equals - Unstable parameters compare with instance equality ().
=== - Lambdas inside composables are automatically remembered based on captures.
That means the question changes from "is this composable skippable at all?" to "will these parameters compare the way I expect, and are callers creating new unstable instances every frame?"
For older compiler setups or strong skipping disabled, the legacy rule still matters: a restartable composable with unstable parameters may be restartable but not skippable.
在Kotlin 2.0.20+版本中,强跳过默认启用。在此模式下:
- 即使参数不稳定,可重启的Composable仍可跳过,除非显式选择退出。
- 稳定参数通过进行比较。
equals - 不稳定参数通过实例相等性()进行比较。
=== - Composable内部的Lambda会基于捕获的内容自动被remember。
这意味着问题从“这个Composable是否完全可跳过?”转变为“这些参数是否按预期方式比较,调用方是否在每一帧都创建新的不稳定实例?”
对于旧版编译器配置或禁用强跳过的情况,旧规则仍然适用:带有不稳定参数的可重启Composable可能是可重启的,但不可跳过。
2. Generate compiler reports
2. 生成编译器报告
With Kotlin 2.0+ the Compose Compiler is configured through the Kotlin Gradle plugin:
kotlin
plugins {
alias(libs.plugins.android.application) // or android.library / jvm
alias(libs.plugins.kotlin.android) // or kotlin.multiplatform / kotlin.jvm
alias(libs.plugins.compose.compiler)
}
if (providers.gradleProperty("composeReports").orNull == "true") {
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
}
}Then build the variant whose compiler configuration you care about, for example:
bash
./gradlew :app:assembleRelease -PcomposeReports=trueUse release/non-debuggable builds for runtime profiling. Compiler reports are build-time outputs, so the important thing is matching the variant and compiler flags you ship.
Key files:
| File | What it tells you |
|---|---|
| Stability of classes and properties |
| Restartable/skippable status and parameter stability |
| Same data in sortable form |
| Aggregate metrics |
在Kotlin 2.0+版本中,Compose编译器通过Kotlin Gradle插件进行配置:
kotlin
plugins {
alias(libs.plugins.android.application) // 或 android.library / jvm
alias(libs.plugins.kotlin.android) // 或 kotlin.multiplatform / kotlin.jvm
alias(libs.plugins.compose.compiler)
}
if (providers.gradleProperty("composeReports").orNull == "true") {
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
}
}然后构建你关注其编译器配置的变体,例如:
bash
./gradlew :app:assembleRelease -PcomposeReports=true使用发布版/非可调试版本进行运行时分析。编译器报告是构建时输出,因此重要的是匹配你发布时使用的变体和编译器标志。
关键文件:
| 文件 | 说明 |
|---|---|
| 类和属性的稳定性 |
| 可重启/可跳过状态及参数稳定性 |
| 相同数据的可排序格式 |
| 聚合指标 |
3. Fix stability where semantics need it
3. 在语义需要的地方修复稳定性
Pick the lightest fix that makes the type's immutability or equality semantics true.
选择最轻量的修复方案,使类型的不可变性或相等性语义成立。
Immutable collections
不可变集合
kotlin.collections.Listkotlinx.collections.immutablekotlin
// Before: unstable collection interfaces
data class UiState(val items: List<Item>, val tags: Set<String>)
// After: immutable collection contracts
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
data class UiState(val items: ImmutableList<Item>, val tags: ImmutableSet<String>)Producers convert once at the boundary with / .
.toImmutableList().toImmutableSet()kotlin.collections.Listkotlinx.collections.immutablekotlin
// 之前:不稳定的集合接口
data class UiState(val items: List<Item>, val tags: Set<String>)
// 之后:不可变集合契约
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
data class UiState(val items: ImmutableList<Item>, val tags: ImmutableSet<String>)生产者在边界处通过 / 进行一次转换。
.toImmutableList().toImmutableSet()@Immutable
/ @Stable
@Immutable@Stable@Immutable
/ @Stable
@Immutable@Stable- Use when every property is effectively immutable and equality describes all observable state.
@Immutable - Use for types whose mutable state is observable by Compose, typically via
@Stable.MutableState
Do not annotate to silence a report. A false stability promise can produce stale UI.
- 当每个属性实际上都是不可变的,且相等性描述了所有可观察状态时,使用。
@Immutable - 对于其可变状态可被Compose观察到的类型(通常通过),使用
MutableState。@Stable
不要为了消除报告而添加注解。错误的稳定性承诺会导致UI过时。
Third-party immutable types
第三方不可变类型
For types you cannot annotate, use :
stabilityConfigurationFileskotlin
composeCompiler {
stabilityConfigurationFiles.add(
rootProject.layout.projectDirectory.file("compose_stability.conf"),
)
}text
java.math.BigDecimal
java.math.BigInteger
java.time.*
kotlinx.datetime.*Only list types you are willing to promise are immutable. Do not list mutable types such as .
java.util.Date对于无法添加注解的类型,使用:
stabilityConfigurationFileskotlin
composeCompiler {
stabilityConfigurationFiles.add(
rootProject.layout.projectDirectory.file("compose_stability.conf"),
)
}text
java.math.BigDecimal
java.math.BigInteger
java.time.*
kotlinx.datetime.*仅列出你能保证是不可变的类型。不要列出可变类型,如。
java.util.DateQuick reference
快速参考
| Symptom | Diagnosis | Fix |
|---|---|---|
| Kotlin 2.0.20+ but old docs say unstable means non-skippable | Strong skipping changed the default | Check comparison semantics and instance churn instead |
| Interface collection | Use |
| External immutable type | Add to stability config |
| False promise | Fix the model or remove the annotation |
| Composable skips poorly despite strong skipping | New unstable instance each recomposition | Remember, hoist, or make the type stable/equality-based |
| Reports not generated | Compose compiler plugin missing or flag not set | Apply |
| 症状 | 诊断 | 修复方案 |
|---|---|---|
| 使用Kotlin 2.0.20+但旧文档称不稳定意味着不可跳过 | 强跳过改变了默认行为 | 改为检查比较语义和实例频繁变化情况 |
| 接口集合 | 使用 |
| 外部不可变类型 | 添加到稳定性配置中 |
对内部可变的类型使用 | 错误的承诺 | 修复模型或移除注解 |
| 尽管启用了强跳过,Composable的跳过效果仍很差 | 每次重组都创建新的不稳定实例 | 使用remember、提升状态,或使类型稳定/基于相等性 |
| 未生成报告 | 缺少Compose编译器插件或未设置标志 | 应用 |
When NOT to apply
何时不适用
- The issue is a fast-changing read in composition, such as scroll or animation. Use
State.compose-state-deferred-reads - The recomposition count matches real data changes.
- The bug is wrong data or stale state, not excess work.
- The code is test-only and readability is more important than report cleanliness.
- 问题是组合中快速变化的读取,例如滚动或动画。请使用
State。compose-state-deferred-reads - 重组次数与实际数据变化匹配。
- 错误是数据错误或过时状态,而非额外的工作。
- 代码仅用于测试,可读性比报告整洁度更重要。
Related
相关技能
- - frame-rate state should often be read in layout/draw rather than composition.
compose-state-deferred-reads - - entry point when you are not sure which recomposition axis is involved.
compose-recomposition-performance
- - 帧率相关状态通常应在布局/绘制阶段读取,而非组合阶段。
compose-state-deferred-reads - - 当你不确定涉及哪个重组维度时的入口点。
compose-recomposition-performance