首页 / Android 入门教程 / Compose 性能优化

Android 入门教程

Compose 性能优化

本教程共 100 篇 · 第 56 篇 · 更新于 2026-07-28 · 约 8 分钟阅读

AndroidAndroid 入门教程Jetpack Compose性能优化重组derivedStateOfstable

56. Compose 性能优化

本节目标:理解重组的性能影响,学会用稳定类型、derivedStateOf、key 等手段减少不必要的重组,写出高性能 Compose 界面。

重组是性能的关键

Compose 的性能核心在于少重组。每次重组都要重新执行函数、比较参数、更新界面树。重组太频繁会导致卡顿掉帧。

Compose 会做智能跳过:如果函数的参数没变,就跳过重组。但前提是—参数能被正确比较。

稳定类型(Stable)

Compose 判断参数是否变化,依赖类型的「稳定性」:

  • 稳定类型String、基本类型(Int、Boolean 等)、@Stable 注解的类。Compose 能确定两个实例相等时内容相同,可以安全跳过。
  • 不稳定类型ListMap、普通 data class 包含集合字段等。Compose 不确定内容是否变了,只能保守地重组。
// 稳定:基本类型
@Composable
fun NameText(name: String) {  // String 是稳定的
    Text(name)
}

// 不稳定:List 参数
@Composable
fun ItemList(items: List<String>) {  // List 不稳定,每次都可能重组
    items.forEach { Text(it) }
}
Warning
  • List<String> 是不稳定类型。即使列表内容没变,Compose 也可能重组这个函数。这是性能问题的常见来源。

用 ImmutableList 解决

// 用 @Immutable 注解标记不可变集合
@Immutable
data class ItemList(val items: List<String>)

@Composable
fun DisplayItems(itemList: ItemList) {
    // 现在可以安全跳过了
    itemList.items.forEach { Text(it) }
}
// 或用 Kotlinx Collections Immtable
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7")

@Composable
fun DisplayItems(items: ImmutableList<String>) {
    items.forEach { Text(it) }
}
Tip
  • @Immutable 是你向 Compose 的承诺:这个类的内容不会变。Compose 信任你,参数不变就跳过。但如果你的类其实可变却标了 @Immutable,会导致界面不更新—别乱标。

derivedStateOf 减少重组

当状态频繁变化但派生结果很少变化时,用 derivedStateOf

// 坏例子:滚动位置每帧都变,导致 if 判断每帧重组
@Composable
fun ScrollButton(scrollState: ScrollState) {
    if (scrollState.value > 1000) {  // 每帧都重新评估
        Button(onClick = { }) { Text("回顶") }
    }
}

// 好例子:只有跨过阈值时才重组
@Composable
fun ScrollButton(scrollState: ScrollState) {
    val showButton by remember {
        derivedStateOf { scrollState.value > 1000 }
    }
    if (showButton) {  // 只在 true/false 切换时重组
        Button(onClick = { }) { Text("回顶") }
    }
}
Note
  • 滚动时 scrollState.value 每帧从 0 变到 2000,但 > 1000 只有在跨过 1000 时才从 false 变 true。derivedStateOf 让只有布尔值变化时才触发重组,而不是每帧。

列表加 key

第 45 章提过,LazyColumnitems 一定要加 key

LazyColumn {
    items(items = list, key = { it.id }) { item ->
        ItemRow(item)
    }
}

没有 key,列表数据变化时 Compose 按位置匹配,可能把错误的 item 内容显示在错误的位置。加了 key 才能正确追踪每一项,避免不必要的重组。

避免在重组中创建对象

// 坏例子:每次重组都创建新的 lambda
@Composable
fun BadExample(onClick: () -> Unit) {
    Button(onClick = { onClick() }) {  // 每次重组都创建新 lambda
        Text("点击")
    }
}

// 好例子:lambda 不依赖状态时提到外面
@Composable
fun GoodExample(onClick: () -> Unit) {
    Button(onClick = onClick) {  // 直接用外部 lambda
        Text("点击")
    }
}

延迟读取状态

状态读取越晚越好,读取范围越小越好:

// 坏例子:在父函数读状态,整个函数都重组
@Composable
fun BadList(items: List<Item>, viewModel: ViewModel) {
    val selectedId by viewModel.selectedId.collectAsState()  // 这里读
    LazyColumn {
        items(items) { item ->
            // 即使只有选中项变了,整个 BadList 都重组
            ItemRow(item, isSelected = item.id == selectedId)
        }
    }
}

// 好例子:在子函数读状态,只重组变化的项
@Composable
fun GoodList(items: List<Item>, viewModel: ViewModel) {
    LazyColumn {
        items(items, key = { it.id }) { item ->
            ItemRow(item, viewModel)  // 把 viewModel 传下去
        }
    }
}

@Composable
fun ItemRow(item: Item, viewModel: ViewModel) {
    val selectedId by viewModel.selectedId.collectAsState()  // 在这里读
    ItemContent(item, isSelected = item.id == selectedId)
}
Tip
  • 原则是「状态读取尽量靠近使用它的地方」。这样状态变化时只重组读它的那个小组件,而不是整个大组件。这是 Compose 性能优化最重要的一条。

remember 缓存计算结果

@Composable
fun DateText(timestamp: Long) {
    val dateText = remember(timestamp) {
        SimpleDateFormat("yyyy-MM-dd").format(Date(timestamp))
    }
    Text(dateText)
}

remember(timestamp) 只在 timestamp 变化时才重新格式化。不变时直接用缓存值,避免每次重组都做日期格式化。

性能分析工具

Layout Inspector

Android Studio 的 Layout Inspector 可以查看 Compose 的重组情况:

  1. 打开 View > Tool Windows > Layout Inspector
  2. 连接设备,选择应用
  3. 切换到 Composition 选项卡
  4. 开启「Show Recomposition Counts」

重组次数多的组件会标红,帮你定位性能热点。

Compose Compiler Metrics

gradle.properties 里开启编译器报告:

compose.compiler.metrics=true
compose.compiler.reports=true

构建后会在 build/compose-metrics/ 生成报告,显示哪些函数是 restartableskippable,哪些类是 stableunstable

Note
  • 一个理想的可组合函数应该是 skippable(参数不变时可跳过)+ restartable(可重组)。报告里标 unstable 的参数就是优化重点。

常见性能陷阱

陷阱后果解决
List 参数不稳定不必要重组@ImmutableImmutableList
没加 key 的列表数据变化时错乱key = { it.id }
在顶层读状态大范围重组状态读取靠近使用处
重组中创建对象频繁 GCremember 缓存
derivedStateOf 缺失频繁状态触发重组derivedStateOf 包裹派生计算

小结

Compose 性能优化的核心是「减少不必要的重组」。用稳定类型让 Compose 能安全跳过,用 derivedStateOf 减少派生状态的重组,用 key 让列表正确追踪项,把状态读取延迟到最靠近使用的位置,用 remember 缓存计算结果。配合 Layout Inspector 和编译器报告定位热点。下一部分我们回到传统 View 体系的学习。