optimizing-lazy-layouts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOptimizing Lazy Layouts — Keys, contentType, and animateItem()
优化惰性布局 — Keys、contentType与animateItem()
Lazy layouts compose only what's visible, but two things still cost: re-composition of items that should have been reused (missing ), and per-item allocation that compounds with scroll velocity (missing , modifier chains created inside ). Both have a one-line fix. This skill teaches Claude how to apply that fix correctly and to validate that item composables are themselves skippable. Prefetch tuning is a separate concern — see .
keycontentTypeitems { }../configuring-lazy-prefetch/SKILL.md惰性布局仅组合可见内容,但仍存在两类性能开销:本应复用的条目发生重组(缺少),以及随滚动速度累积的每条目内存分配(缺少、在内部创建修饰符链)。这两类问题都只需一行代码即可修复。本技能将指导Claude如何正确应用修复方案,并验证条目可组合项是否可跳过重组。预取调优是独立的优化方向 — 请查看。
keycontentTypeitems { }../configuring-lazy-prefetch/SKILL.mdWhen to use this skill
何时使用此技能
- The developer reports scroll jank, dropped frames, or stutter on a ,
LazyColumn,LazyRow, orLazyVerticalGrid.LazyHorizontalGrid - Items lose scroll position, focus, or composition state on insert, remove, or reorder.
- A mixed-type feed (cards + headers + ads + carousels) feels sluggish even though each individual row is lightweight.
- was added but no animation runs on inserts or removals.
Modifier.animateItem() - The compiler report shows item composables as /non-skippable, or
unstableshows item composables recomposing on every scroll tick.@TraceRecomposition
- 开发者反馈、
LazyColumn、LazyRow或LazyVerticalGrid存在滚动卡顿、丢帧或抖动问题。LazyHorizontalGrid - 插入、删除或重排条目时,条目丢失滚动位置、焦点或组合状态。
- 混合类型信息流(卡片 + 标题 + 广告 + 轮播图)即使单个条目轻量化,整体运行仍迟缓。
- 添加了但插入或删除时无动画效果。
Modifier.animateItem() - 编译器报告显示条目可组合项为/不可跳过,或
unstable显示条目可组合项在每次滚动周期都发生重组。@TraceRecomposition
When NOT to use this skill
何时不使用此技能
- The bottleneck is the prefetch window (heavy items, high-velocity scroll, want a wider ahead/behind window) → use .
../configuring-lazy-prefetch/SKILL.md - The item composable itself takes an unstable parameter (,
List<Foo>, a domainFlow<Foo>) → first runvarand then../../stability/diagnosing-compose-stability/SKILL.md.../../stability/stabilizing-compose-types/SKILL.md - An animation inside an item reads in Composition phase, recomposing the row every frame → use
state.value.../../recomposition/deferring-state-reads/SKILL.md - Scroll position derivation (e.g. ) is the hot path → use
firstVisibleItemIndex == 0.../../recomposition/choosing-derivedstateof/SKILL.md
- 性能瓶颈在于预取窗口(条目过重、高速滚动、需要扩大前后预取范围)→ 使用。
../configuring-lazy-prefetch/SKILL.md - 条目可组合项本身接收不稳定参数(、
List<Foo>、可变域对象Flow<Foo>)→ 先运行var,再使用../../stability/diagnosing-compose-stability/SKILL.md。../../stability/stabilizing-compose-types/SKILL.md - 条目内部的动画在组合阶段读取,导致每行在每一帧都发生重组 → 使用
state.value。../../recomposition/deferring-state-reads/SKILL.md - 滚动位置推导(如)是性能热点 → 使用
firstVisibleItemIndex == 0。../../recomposition/choosing-derivedstateof/SKILL.md
Prerequisites
前置条件
- Compose Foundation 1.7+ for (the GA replacement for the experimental
Modifier.animateItem()).animateItemPlacement - Kotlin 2.0.0+ with applied. Strong Skipping is on by default; non-skippable item composables become amplified at scroll speed.
org.jetbrains.kotlin.plugin.compose - A real device + release build for measurement. Skydoves hot take #5: debug builds lie (Live Literals, interpreted mode). See when ready to measure.
../../measurement/generating-baseline-profiles/SKILL.md
- 用于的Compose Foundation 1.7+(实验性
Modifier.animateItem()的正式替代方案)。animateItemPlacement - 应用了的Kotlin 2.0.0+。强跳过重组默认开启;不可跳过的条目可组合项在滚动时性能问题会被放大。
org.jetbrains.kotlin.plugin.compose - 用于性能测量的真实设备 + Release构建。skydoves观点#5:Debug构建会误导结果(Live Literals、解释执行模式)。准备测量时请查看。
../../measurement/generating-baseline-profiles/SKILL.md
Workflow
工作流程
- 1. Audit every call. Walk every
items(...),LazyListScope.items(list),items(count), and theitemsIndexed(list)equivalents. For each, decide: does each element have a stable identity that outlives a single composition? If yes — and it almost always does — supplyLazyGridScopeusing a server-side stable ID. MUST NOT use the list index,key = { it.id }evaluated per emission, orUUID.randomUUID()of a mutable object.hashCode()
kotlin
// WRONG
LazyColumn { items(snacks) { snack -> SnackRow(snack) } }
// WRONG because: index-based identity → insert/remove discards composition state and breaks animateItem().kotlin
// RIGHT
LazyColumn {
items(
items = snacks,
key = { it.id },
contentType = { it::class },
) { snack ->
SnackRow(snack, Modifier.animateItem())
}
}-
2. Addfor heterogeneous lists. Lazy layouts maintain a per-type composition cache analogous to RecyclerView's view-type. When item N + 1 has the same
contentTypeas a recycled slot, the cached composition is reused; otherwise it is discarded and rebuilt. For homogeneous lists Compose infers a single content type andcontentTypeis optional. For mixed feeds (cards, headers, ads, carousels, dividers) MUST supply a stable type discriminator.contentType -
3. Validate item composable stability. Run. If the item composable accepts an
../../stability/diagnosing-compose-stability/SKILL.mdparameter, no amount ofunstable/keywork will help — the row recomposes on every scroll-driven snapshot tick anyway. Fix withcontentTypebefore tuning further.../../stability/stabilizing-compose-types/SKILL.md -
4. Hoist allocation-heavy values out of the items lambda. The items lambda runs once per item per scroll-driven (re)composition. Painters, color resolutions, shapes, andinstances built inside the lambda are reallocated each pass. Hoist constants and
BorderStroke-based caches above therememberor to the call site. Modifier chains are themselves cheap because Compose deduplicates them structurally — hoist aLazyColumnonly when profiling proves it matters.Modifier -
5. Addfor visual continuity. Pair with a stable
Modifier.animateItem(). The animation runs on inserts, removals, and reorders; withoutkeythe animation cannot bind to identity and silently no-ops. The default fade-in / fade-out / placement spring is usually correct; tune withkey,fadeInSpec,fadeOutSpeconly when the design system requires it.placementSpec -
6. Cache common painters / colors / shapes outside the items block.,
painterResource(...),MaterialTheme.colorScheme.surfaceresolutions on every item composition add up. Hoist to the screen-level composable and pass down, orRoundedCornerShape(...)once at therememberparent.LazyColumn -
7. Verify withand Layout Inspector. During a controlled scroll, expect each item composable to recompose at most once per real state change — not per scroll tick. Layout Inspector → Recomposition Counts column should plateau, not climb monotonically.
@TraceRecomposition
- 1. 审核所有调用。检查每个
items(...)、LazyListScope.items(list)、items(count)以及itemsIndexed(list)的等效方法。对于每个调用,判断:每个元素是否拥有独立于单次组合的稳定标识?如果是(几乎所有场景都是),使用服务端稳定ID设置LazyGridScope。严禁使用列表索引、每次发射时生成的key = { it.id }或可变对象的UUID.randomUUID()。hashCode()
kotlin
// 错误
LazyColumn { items(snacks) { snack -> SnackRow(snack) } }
// 错误原因:基于索引的标识 → 插入/删除操作会丢弃组合状态并破坏animateItem()的功能。kotlin
// 正确
LazyColumn {
items(
items = snacks,
key = { it.id },
contentType = { it::class },
) { snack ->
SnackRow(snack, Modifier.animateItem())
}
}-
2. 为异构列表添加。惰性布局维护基于类型的组合缓存,类似RecyclerView的视图类型。当第N+1个条目的
contentType与回收槽的类型相同时,缓存的组合会被复用;否则会被丢弃并重新构建。对于同构列表,Compose会自动推断单一内容类型,contentType为可选参数。对于混合信息流(卡片、标题、广告、轮播图、分隔符)必须提供稳定的类型判别器。contentType -
3. 验证条目可组合项的稳定性。运行。如果条目可组合项接收不稳定参数,无论
../../stability/diagnosing-compose-stability/SKILL.md/key设置得多么完善都无济于事 — 每行仍会在每次滚动驱动的快照周期发生重组。在进一步调优前,请使用contentType修复稳定性问题。../../stability/stabilizing-compose-types/SKILL.md -
4. 将内存分配密集型值从items lambda中提升。items lambda会在每次滚动驱动的(重)组合时为每个条目执行一次。在lambda内部创建的Painter、颜色解析结果、形状和实例会在每次执行时重新分配。将常量和基于
BorderStroke的缓存提升到remember上方或调用方。修饰符链本身开销很低,因为Compose会进行结构去重 — 只有当性能分析证明其是瓶颈时才需要提升LazyColumn。Modifier -
5. 添加以保证视觉连续性。搭配稳定的
Modifier.animateItem()使用。动画会在插入、删除和重排时运行;如果没有key,动画无法绑定到条目标识,会静默失效。默认的淡入/淡出/位移动画通常已满足需求;只有当设计系统有要求时,才需要通过key、fadeInSpec、fadeOutSpec进行调优。placementSpec -
6. 在items块外部缓存通用Painter/颜色/形状。每次条目组合时解析、
painterResource(...)、MaterialTheme.colorScheme.surface会累积性能开销。将这些提升到屏幕级可组合项并向下传递,或在RoundedCornerShape(...)父级通过LazyColumn缓存一次。remember -
7. 使用和布局检查器验证。在受控滚动过程中,每个条目可组合项应最多在真实状态变化时重组一次 — 而非每次滚动周期。布局检查器 → 重组次数列应趋于平稳,而非持续增长。
@TraceRecomposition
Patterns
常见模式
Pattern: missing key
key模式:缺少key
keykotlin
// WRONG
LazyColumn {
items(snacks) { snack -> SnackRow(snack) }
}
// WRONG because: items default to index-based identity. On insert/remove/reorder, every position past the change point has a different "identity", composition state and scroll-restoration are lost, and Modifier.animateItem() has nothing to animate from.kotlin
// RIGHT
LazyColumn {
items(snacks, key = { it.id }) { snack -> SnackRow(snack) }
}kotlin
// 错误
LazyColumn {
items(snacks) { snack -> SnackRow(snack) }
}
// 错误原因:条目默认使用基于索引的标识。插入/删除/重排时,变更点之后的所有位置的"标识"都会改变,组合状态和滚动恢复会丢失,Modifier.animateItem()也没有可动画的对象。kotlin
// 正确
LazyColumn {
items(snacks, key = { it.id }) { snack -> SnackRow(snack) }
}Pattern: random or unstable key
模式:随机或不稳定的key
kotlin
// WRONG
items(snacks, key = { UUID.randomUUID() }) { snack -> SnackRow(snack) }
// WRONG because: a fresh key on every recomposition guarantees the cached composition is discarded every time — strictly worse than no key.kotlin
// WRONG
items(snacks, key = { it.hashCode() }) { snack -> SnackRow(snack) }
// WRONG because: hashCode() of a mutable type changes when fields mutate, breaking identity continuity for the same logical item.kotlin
// RIGHT
items(snacks, key = { it.id }) { snack -> SnackRow(snack) }kotlin
// 错误
items(snacks, key = { UUID.randomUUID() }) { snack -> SnackRow(snack) }
// 错误原因:每次重组生成新的键会导致缓存的组合被频繁丢弃 — 这比不设置键的情况更糟。kotlin
// 错误
items(snacks, key = { it.hashCode() }) { snack -> SnackRow(snack) }
// 错误原因:可变类型的hashCode()会随字段变化而改变,破坏同一逻辑条目的标识连续性。kotlin
// 正确
items(snacks, key = { it.id }) { snack -> SnackRow(snack) }Pattern: mixed feed without contentType
contentType模式:混合信息流未设置contentType
contentTypekotlin
// WRONG
items(feed, key = { it.id }) { item ->
when (item) {
is FeedItem.Card -> CardRow(item)
is FeedItem.Ad -> AdRow(item)
is FeedItem.Header -> HeaderRow(item)
}
}
// WRONG because: cached compositions of one type are discarded when scrolled into a different type's slot — every row crossing a type boundary is a fresh build instead of a recycled update.kotlin
// RIGHT
items(
items = feed,
key = { it.id },
contentType = { it::class },
) { item ->
when (item) {
is FeedItem.Card -> CardRow(item)
is FeedItem.Ad -> AdRow(item)
is FeedItem.Header -> HeaderRow(item)
}
}kotlin
// 错误
items(feed, key = { it.id }) { item ->
when (item) {
is FeedItem.Card -> CardRow(item)
is FeedItem.Ad -> AdRow(item)
is FeedItem.Header -> HeaderRow(item)
}
}
// 错误原因:当不同类型的条目滚动到同一回收槽时,原类型的缓存组合会被丢弃 — 每个跨类型边界的条目都需要重新构建,而非复用更新。kotlin
// 正确
items(
items = feed,
key = { it.id },
contentType = { it::class },
) { item ->
when (item) {
is FeedItem.Card -> CardRow(item)
is FeedItem.Ad -> AdRow(item)
is FeedItem.Header -> HeaderRow(item)
}
}Pattern: Modifier.animateItem()
without a stable key
Modifier.animateItem()模式:Modifier.animateItem()
未搭配稳定key
Modifier.animateItem()kotlin
// WRONG
items(snacks) { snack ->
SnackRow(snack, Modifier.animateItem())
}
// WRONG because: animateItem() binds animation state to the item's key. With no key, identity is index-based, so an insert at position 0 looks like every-row-changed and nothing animates correctly.kotlin
// RIGHT
items(snacks, key = { it.id }) { snack ->
SnackRow(snack, Modifier.animateItem())
}kotlin
// 错误
items(snacks) { snack ->
SnackRow(snack, Modifier.animateItem())
}
// 错误原因:animateItem()将动画状态绑定到条目的key。没有key时,标识基于索引,所以在位置0插入条目会被识别为所有行都发生变化,无法正确执行动画。kotlin
// 正确
items(snacks, key = { it.id }) { snack ->
SnackRow(snack, Modifier.animateItem())
}Pattern: allocation inside the items lambda
模式:items lambda内部存在内存分配
kotlin
// WRONG
items(snacks, key = { it.id }) { snack ->
val placeholder = painterResource(R.drawable.snack_placeholder)
val border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
Card(border = border) {
AsyncImage(snack.imageUrl, placeholder = placeholder)
}
}
// WRONG because: painterResource resolution and BorderStroke allocation happen on every item composition; at high scroll velocity these compound into measurable allocation pressure.kotlin
// RIGHT
@Composable
fun SnackList(snacks: ImmutableList<Snack>) {
val placeholder = painterResource(R.drawable.snack_placeholder)
val border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
LazyColumn {
items(snacks, key = { it.id }, contentType = { it::class }) { snack ->
Card(border = border) {
AsyncImage(snack.imageUrl, placeholder = placeholder)
}
}
}
}Note: Compose deduplicates structurally-equal chains internally, so reallocating per item is a micro-optimization. Hoist a Modifier only when profiling identifies it as the bottleneck — premature adds noise without measurable benefit.
ModifierModifier.fillMaxWidth().padding(16.dp)remember { Modifier.… }kotlin
// 错误
items(snacks, key = { it.id }) { snack ->
val placeholder = painterResource(R.drawable.snack_placeholder)
val border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
Card(border = border) {
AsyncImage(snack.imageUrl, placeholder = placeholder)
}
}
// 错误原因:painterResource解析和BorderStroke分配会在每次条目组合时执行;高速滚动时这些操作会累积成可测量的内存分配压力。kotlin
// 正确
@Composable
fun SnackList(snacks: ImmutableList<Snack>) {
val placeholder = painterResource(R.drawable.snack_placeholder)
val border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
LazyColumn {
items(snacks, key = { it.id }, contentType = { it::class }) { snack ->
Card(border = border) {
AsyncImage(snack.imageUrl, placeholder = placeholder)
}
}
}
}注意:Compose会在内部对结构相同的链进行去重,因此为每个条目重新创建属于微优化。只有当性能分析确定其为瓶颈时才需要提升Modifier — 过早使用只会增加代码复杂度,而无明显性能收益。
ModifierModifier.fillMaxWidth().padding(16.dp)remember { Modifier.… }Pattern: unstable item composable swallows all gains
模式:不稳定的条目可组合项抵消所有优化效果
kotlin
// WRONG
@Composable
fun SnackRow(snack: Snack, tags: List<String>) { /* ... */ }
// Caller:
items(snacks, key = { it.id }) { snack ->
SnackRow(snack, tags = snack.tags)
}
// WRONG because: List<String> is an unstable parameter under inference; every scroll-driven recomposition recomposes the row body even though the snack didn't change.kotlin
// RIGHT
@Immutable
data class Snack(val id: Long, val name: String, val tags: ImmutableList<String>)
@Composable
fun SnackRow(snack: Snack) { /* ... */ }
items(snacks, key = { it.id }, contentType = { it::class }) { snack ->
SnackRow(snack)
}Cross-reference: .
../../stability/stabilizing-compose-types/SKILL.mdkotlin
// 错误
@Composable
fun SnackRow(snack: Snack, tags: List<String>) { /* ... */ }
// 调用方:
items(snacks, key = { it.id }) { snack ->
SnackRow(snack, tags = snack.tags)
}
// 错误原因:List<String>在默认推断下是不稳定参数;即使snack未变化,每次滚动驱动的重组仍会重新执行行体。kotlin
// 正确
@Immutable
data class Snack(val id: Long, val name: String, val tags: ImmutableList<String>)
@Composable
fun SnackRow(snack: Snack) { /* ... */ }
items(snacks, key = { it.id }, contentType = { it::class }) { snack ->
SnackRow(snack)
}交叉参考:。
../../stability/stabilizing-compose-types/SKILL.mdPattern: LazyVerticalGrid
with mixed spans
LazyVerticalGrid模式:包含混合跨度的LazyVerticalGrid
LazyVerticalGridkotlin
// RIGHT — keys + contentType apply to grids identically
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
items(
items = feed,
key = { it.id },
contentType = { it::class },
span = { item -> if (item is FeedItem.Header) GridItemSpan(maxLineSpan) else GridItemSpan(1) },
) { item ->
when (item) {
is FeedItem.Header -> HeaderRow(item, Modifier.animateItem())
is FeedItem.Card -> CardCell(item, Modifier.animateItem())
}
}
}kotlin
// 正确 — keys + contentType在网格布局中的应用方式完全相同
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
items(
items = feed,
key = { it.id },
contentType = { it::class },
span = { item -> if (item is FeedItem.Header) GridItemSpan(maxLineSpan) else GridItemSpan(1) },
) { item ->
when (item) {
is FeedItem.Header -> HeaderRow(item, Modifier.animateItem())
is FeedItem.Card -> CardCell(item, Modifier.animateItem())
}
}
}Mandatory rules
强制规则
- MUST specify a for every
keyblock where item identity outlives a single composition (effectively: every list backed by domain objects).items(...) - MUST use server-side stable IDs as keys. MUST NOT use the list index, MUST NOT use evaluated per emission, MUST NOT use
UUID.randomUUID()of a mutable object.hashCode() - MUST specify for heterogeneous lists (cards + headers + ads, etc.). Use a stable type discriminator such as
contentTypeor a sealedit::class.enum - MUST NOT use without a stable
Modifier.animateItem()— the animation silently no-ops.key - MUST validate item composable stability with before blaming the lazy layout. An unstable item parameter cancels every gain from
../../stability/diagnosing-compose-stability/SKILL.md/key.contentType - MUST NOT wrap in extra inline composable wrappers (
items { }) hoping to "force" skippability —Row { items { } }/Row/Columnare NOT restartable/skippable to begin with (skydoves hot take #3).Box - PREFERRED: combine with for high-velocity scroll surfaces only after item-level fixes are in place.
../configuring-lazy-prefetch/SKILL.md - PREFERRED: measure in release + R8 + on a real device (skydoves hot take #5) before declaring a fix complete.
- 必须为每个块指定
items(...),只要条目标识独立于单次组合(实际上:所有基于域对象的列表)。key - 必须使用服务端稳定ID作为key。严禁使用列表索引,严禁使用每次发射时生成的,严禁使用可变对象的
UUID.randomUUID()。hashCode() - 必须为异构列表(卡片 + 标题 + 广告等)指定。使用稳定的类型判别器,如
contentType或密封it::class。enum - 严禁在未设置稳定的情况下使用
key— 动画会静默失效。Modifier.animateItem() - 必须在质疑惰性布局性能前,使用验证条目可组合项的稳定性。不稳定的条目参数会抵消
../../stability/diagnosing-compose-stability/SKILL.md/key带来的所有优化效果。contentType - 严禁将包裹在额外的内联可组合包装器中(
items { })以试图"强制"跳过重组 —Row { items { } }/Row/Column本身不支持重启/跳过重组(skydoves观点#3)。Box - 推荐:仅在完成条目级优化后,针对高速滚动界面结合使用。
../configuring-lazy-prefetch/SKILL.md - 推荐:在声明修复完成前,在Release + R8 + 真实设备上进行性能测量(skydoves观点#5)。
Verification
验证标准
- Reproduce the original scroll jank on a release build on a real device, then re-record after the fix; the dropped-frame rate measurably decreases.
- Insert / remove / reorder operations preserve scroll position and per-item state (focus, expansion, scrubbed video position).
- runs the expected fade and placement animation on inserts and removals.
Modifier.animateItem() - Layout Inspector → Recomposition Counts column on item composables plateaus during steady scroll instead of climbing monotonically.
- on the item composable shows recompositions only on real state changes, not on every scroll-driven invalidation.
@TraceRecomposition - The compiler report () shows the item composable as
composables.txtwith all parametersrestartable skippableorstable.runtime
- 在真实设备的Release构建上重现原始滚动卡顿问题,修复后重新记录;丢帧率应显著降低。
- 插入/删除/重排操作保留滚动位置和每条目状态(焦点、展开状态、视频播放进度)。
- 在插入和删除时运行预期的淡入淡出和位移动画。
Modifier.animateItem() - 布局检查器 → 条目可组合项的重组次数列在稳定滚动时趋于平稳,而非持续增长。
- 条目可组合项上的显示仅在真实状态变化时发生重组,而非每次滚动驱动的失效时。
@TraceRecomposition - 编译器报告()显示条目可组合项为
composables.txt,且所有参数为restartable skippable或stable。runtime
References
参考资料
- Android Developers — Lists and grids: https://developer.android.com/develop/ui/compose/lists
- Android Developers — Performance overview: https://developer.android.com/develop/ui/compose/performance
- Android Developers — Practical performance codelab: https://developer.android.com/codelabs/jetpack-compose-performance
- Android Developers — What's new in Jetpack Compose (April 2025, 1.8): https://android-developers.googleblog.com/2025/04/whats-new-in-jetpack-compose-april-25.html
- Ben Trengrove — Debugging recomposition: https://medium.com/androiddevelopers/jetpack-compose-debugging-recomposition-bfcf4a6f8d37
- Chris Banes — Compose performance tag: https://chrisbanes.me/tags/jetpack-compose-performance/
- skydoves — 6 Jetpack Compose Guidelines: https://medium.com/proandroiddev/6-jetpack-compose-guidelines-to-optimize-your-app-performance-be18533721f9
- skydoves — compose-performance hub: https://github.com/skydoves/compose-performance
- Android Developers — 列表与网格:https://developer.android.com/develop/ui/compose/lists
- Android Developers — 性能概述:https://developer.android.com/develop/ui/compose/performance
- Android Developers — 实用性能代码实验室:https://developer.android.com/codelabs/jetpack-compose-performance
- Android Developers — Jetpack Compose新特性(2025年4月,1.8版本):https://android-developers.googleblog.com/2025/04/whats-new-in-jetpack-compose-april-25.html
- Ben Trengrove — 调试重组:https://medium.com/androiddevelopers/jetpack-compose-debugging-recomposition-bfcf4a6f8d37
- Chris Banes — Compose性能标签:https://chrisbanes.me/tags/jetpack-compose-performance/
- skydoves — 6条Jetpack Compose性能优化指南:https://medium.com/proandroiddev/6-jetpack-compose-guidelines-to-optimize-your-app-performance-be18533721f9
- skydoves — compose-performance hub:https://github.com/skydoves/compose-performance