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

相关推荐
Android打工仔6 分钟前
一次 Android 拍照后卡顿的 Perfetto 定位与优化实践
android·性能优化
OpenFDE开源桌面14 分钟前
实操:如何将安卓 App与Linux系统应用级融合(附代码)
android·linux
xiangxiongfly91516 分钟前
Android VideoView总结
android·videoview
杉氧36 分钟前
从 Modifier 到 Flexbox:React Native 布局与样式设计哲学
android·前端·react native
Kapaseker1 小时前
我常用的 5 个 Kotlin 优化小技巧
android·kotlin
恋猫de小郭1 小时前
Flutter 多窗口支持类型和 API 介绍
android·前端·flutter
张风捷特烈1 小时前
当 AI 遇见 Flutter | 打造 500+ Widget 专属Logo
android·前端·flutter
mengge.cloud2 小时前
云计算&服务器基础小白教程
android·linux·运维·服务器·网络·云计算
天空之城--2 小时前
Android Flutter行业最新动态与实用参考(2026年8月第3周)
android·人工智能·flutter·ai编程
zh73144 小时前
typephp的编译核心phpx原理解析
android·java·开发语言