我通过3个小改动将Compose重组减少了78%

起初,屏幕看起来很正常。没有崩溃。没有 ANR(应用程序无响应)。也没有明显的帧丢失。但是,每次我滚动、输入或更改滤镜时,我总感觉有些不对劲------就像 UI 在做更多的工作。我打开 Compose 布局检查器,期望找到一个坏的 Composable 组件。但实际上,我发现整个屏幕一直在悄悄地重新组合,这都是因为三个微小的错误,在代码审查中看起来完全无害。

核心要点

本文介绍了在 Jetpack Compose 中不必要的重组现象,以及如何通过使用稳定模型、 rememberderivedStateOf 来减少重组。

读者将学到什么

  • 为什么重组本身不是坏事
  • 不稳定的参数如何导致不必要的重组
  • 为什么在可组合项中创建对象可能是昂贵的
  • 何时使用 remember
  • 何时使用 derivedStateOf
  • 如何思考在可组合项中传递 lambda

第一部分:问题

在 Compose 中,UI 是状态的一个函数。当状态发生变化时,Compose 会重新运行受影响的 composable 函数,这是正常的。问题出现在当不相关的 UI 也重新组合时,因为 Compose 无法证明你的输入是稳定或未改变的。

示例情况:

  • 一个产品列表屏幕有一个搜索框
  • 输入一个字符更新查询
  • 即使项目数据没有变化,完整的项目列表 UI 也会重新组合
  • 排序/筛选会反复创建新的列表对象
  • Lambda 函数在每次重新组合时都会被重新创建

第二节:错误 1------传递不稳定的模型

优化之前

c 复制代码
data class ProductUiModel(
  val id: String,
  val title: String,
  val price: Double,
  val tags: List<String>
)

@Composable
fun ProductCard(product: ProductUiModel) {
  Column {
     Text(product.title)
    Text("$${product.price}")
  }
}

这样看起来没问题,但 List可能会被 Compose 视为不稳定,因为它无法始终保证列表不会发生变更。

优化之后

c 复制代码
import androidx.compose.runtime.Immutable

@Immutable
data class ProductUiModel(
  val id: String,
  val title: String,
  val price: Double,
  val tags: ImmutableList<String>
)

尽可能使用不可变 UI 模型。当 Compose 能够推理稳定性时,它可以跳过更多工作。避免将可变集合传递给深层嵌套的可组合项。

第 3 节:错误 2------在重组期间创建昂贵的对象

优化之前

c 复制代码
@Composable
fun ProductList(products: List<ProductUiModel>, query: String) {
    val filteredProducts = products.filter {
        it.title.contains(query, ignoreCase = true)
    }

    LazyColumn {
        items(filteredProducts) { product ->
            ProductCard(product)
        }
    }
}

每次可组合项重新组合时,都会重新计算过滤后的列表。

优化之后

c 复制代码
@Composable
fun ProductList(products: List<ProductUiModel>, query: String) {
    val filteredProducts = remember(products, query) {
        products.filter {
            it.title.contains(query, ignoreCase = true)
        }
    }

    LazyColumn {
        items(
            items = filteredProducts,
            key = { it.id }
        ) { product ->
            ProductCard(product)
        }
    }
}

详细说明

remember 会在多次重组之间存储结果,直到其中一个键发生变化。在这里,过滤操作只在产品或查询变化时运行。

同时也要注意这个关键key。稳定的item key有助于 Compose 在列表中保持项的标识。

第 4 节:错误 3------直接计算派生状态

优化之前

c 复制代码
@Composable
fun CartSummary(items: List<CartItem>) {
    val total = items.sumOf { it.price * it.quantity }
    val hasDiscount = total > 1000

    Text("Total: $total")
    if (hasDiscount) {
        Text("Discount unlocked")
    }
}

优化之后

c 复制代码
@Composable
fun CartSummary(items: List<CartItem>) {
    val total by remember(items) {
        derivedStateOf {
            items.sumOf { it.price * it.quantity }
        }
    }

    val hasDiscount by remember {
        derivedStateOf { total > 1000 }
    }

    Text("Total: $total")
    if (hasDiscount) {
        Text("Discount unlocked")
    }
}

详细说明

当某个值由其他状态计算得出,且你希望 Compose 能更高效地观察变化时,derivedStateOf 很有用。

不要在所有地方使用它。 应该在以下情况下使用:

  • 这个计算结果来源于状态
  • 输入经常变化
  • 输出变化频率更低
  • 重组的成本很高

第五部分:附加内容------避免在列表组件中使用内联 lambda

优化之前

c 复制代码
@Composable
fun ProductScreen(
    products: List<ProductUiModel>,
    onProductClick: (String) -> Unit
) {
    LazyColumn {
        items(products) { product ->
            ProductCard(
                product = product,
                onClick = { onProductClick(product.id) }
            )
        }
    }
}

优化之后

c 复制代码
@Composable
fun ProductScreen(
    products: List<ProductUiModel>,
    onProductClick: (String) -> Unit
) {
    LazyColumn {
        items(
            items = products,
            key = { it.id }
        ) { product ->
            ProductCard(
                product = product,
                onClick = onProductClick
            )
        }
    }
}

@Composable
fun ProductCard(
    product: ProductUiModel,
    onClick: (String) -> Unit
) {
    Card(onClick = { onClick(product.id) }) {
        Text(product.title)
    }
}

最大的教训不是"避免重组"。重组是 Compose 在正常工作。真正的教训是:让你的状态保持稳定,让你的计算有目的性,不要强迫 Compose 猜测,而应该提供更准确的信息。

相关推荐
松仔log4 小时前
Java中级——组合和继承
android·java·开发语言
我命由我123457 小时前
Jetpack Compose - MaterialExpressiveTheme 与 MaterialTheme、ColorScheme
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime
lilian2338 小时前
Harmony os 技术实战|拼豆制图06:收藏 ID、生成记录与重启恢复怎么不打架
android·java·数据库·harmonyos
2601_955759628 小时前
ClaudeAPI成本中心与业务标签设计指南
android·java·数据库
余衫马8 小时前
2026最新版!Android Studio 极速安装与配置指南
android·ide·android studio
AFinalStone9 小时前
Android 7系统异常问题排查(五)Framework层(下)—System Server崩溃
android·tombstone·系统异常
AFinalStone9 小时前
Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断
android·系统异常
极客猴子12 小时前
Android录音转写频繁卡顿?多款APP长时间会议场景稳定性实测
android·人工智能·智能手机·飞书
Kapaseker12 小时前
Boolean 变量到底该怎么命名?
android·kotlin
AFinalStone12 小时前
Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解
android·系统异常