我通过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 猜测,而应该提供更准确的信息。

相关推荐
应用市场1 小时前
Android分区表深度解析:GPT、各分区作用与布局实战
android·gpt
应用市场2 小时前
Android Recovery 模式工作原理与定制实战
android
应用市场5 小时前
eMMC 与 UFS 存储原理及在 Android 中的应用
android
随遇丿而安5 小时前
第4周:ImageView 最怕的不是不会显示图片,而是显示得“不对劲”
android
Mart!nHu5 小时前
Android10 添加以太网网络共享功能
android·以太网共享
修炼者7 小时前
bitmap和drawable的互相转换
android
美狐美颜SDK开放平台7 小时前
美颜SDK接入流程详解:Android、iOS、鸿蒙兼容方案解析
android·人工智能·ios·华为·harmonyos·美颜sdk·视频美颜sdk
笔夏9 小时前
【安卓学习之FloatingActionButton】按钮太小
android·学习
XD74297163610 小时前
科技早报晚报|2026年5月15日:无摄像头空间感知、Android 设备实验室与视频检索代理,今天更值得跟进的 3 个技术机会
android·科技·音视频·开源项目·边缘ai·开发者工具