Administrator
发布于 2026-08-25 / 25 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 16 篇:Jetpack Compose 进阶:状态、副作用与重组原理

上一篇聊到 Compose 性能调优,很多同学反馈:

“重组到底怎么触发的?为什么 remember 有时不生效?”

这其实不是性能问题,而是状态管理问题。Compose 的 UI 更新完全依赖状态,状态写不对,性能调优就是无本之木。


一、状态驱动 UI 的三个层次

@Composable
fun Counter() {
    var count by remember { mutableIntStateOf(0) }  // ① 状态
    Button(onClick = { count++ }) {               // ② 事件
        Text("Count: $count")                      // ③ UI
    }
}

Compose 的核心公式:State → UI。事件修改状态,状态变化触发重组。

三个层次要分清:

  • State:mutableStateOf 包装的可观察数据

  • StateHolder:ViewModel 或 remember 层,负责状态的存活周期

  • UI:根据状态描述当前界面


二、remember 不是缓存,是“按调用位置记忆”

很多人误以为 remember 是按 key 缓存,其实它按 Composable 在调用树中的位置记忆。

@Composable
fun Demo(flag: Boolean) {
    if (flag) {
        val name by remember { mutableStateOf("A") }
        Text(name)
    } else {
        val name by remember { mutableStateOf("B") }
        Text(name)
    }
}

切换 flag 时,两个 remember 位于不同调用位置,状态不会共享。如果 flag 变化导致分支切换,旧状态会丢失。

正确做法是用稳定的 key:

val name by remember(flag) { mutableStateOf(if (flag) "A" else "B") }

三、副作用 API 的使用边界

Compose 有 5 个常用的副作用 API,用错很容易出 Bug:

API

用途

注意点

LaunchedEffect

在组合进入时启动协程

key 为空则每次重组都取消重启

SideEffect

每次成功重组后回调

只读状态,不要修改状态

DisposableEffect

需要清理的资源

离开组合时触发 onDispose

derivedStateOf

从已有状态派生新状态

内部状态必须稳定

snapshotFlow

把 State 转成 Flow

在协程中使用

典型错误:

LaunchedEffect(Unit) {
    while (true) {
        count++
        delay(1000)
    }
}

如果 count 是 Compose 状态,修改后触发重组,重组后 LaunchedEffect(Unit) 不会重启——这没问题。但如果把 count 作为 key,就会每秒取消重启一次,白白浪费。


四、重组的原理:只有读取了状态才会参与重组

@Composable
fun Parent() {
    val state by viewModel.state.collectAsStateWithLifecycle()
    ChildA(state.name)
    ChildB(state.age)
}

当 name 变化时,只有 ChildA 会重组;ChildB 因为没读 name,会被智能跳过。

这就是 Compose 的细粒度重组。它依赖两个条件:

  1. 状态必须是 State 类型,普通变量观察不到变化

  2. 子 Composable 参数必须稳定(@Stable 或不可变数据类)


五、状态提升的正确姿势

官方建议“状态向下流动,事件向上传递”。简单说:

  • 可复用的 UI 组件不要直接持有 ViewModel

  • 状态尽量通过参数传入,事件通过 lambda 传出

@Composable
fun LoginForm(
    phone: String,
    onPhoneChange: (String) -> Unit,
    onSubmit: () -> Unit
)

这样 LoginForm 既能在 ViewModel 场景用,也能在 Preview 里独立测试。


小结

Compose 的状态管理不难,关键是理解:

  1. remember 按调用位置记忆,不是按 key 全局缓存

  2. 副作用 API 各有边界,别用 LaunchedEffect 做普通回调

  3. 只有读取了 State 的 Composable 才会参与重组

  4. 状态向下走,事件向上走,组件才容易复用

下一篇,我们离开声明式 UI,回到 View 体系,聊聊自定义 View 与 Canvas 绘制。


参考阅读


评论