状态与 Snapshot · AndroidX 源码指南
AAndroidX 源码指南
Compose Runtime
Compose 底层 · androidx.compose.runtime

状态与 Snapshot1.13.0-alpha01

mutableStateOf 的 policy、derivedStateOf、snapshotFlow、produceState 与 rememberSaveable 的取舍。

最后更新 2026-08-01

可观察状态

var count by remember { mutableStateOf(0) }

Button(onClick = { count++ }) {
    Text("$count")
}

mutableStateOf 创建 Snapshot 状态。Composable 读取 count 后,后续写入让读取范围失效,在重组阶段重新执行。普通 var 没有通知链——UI 状态必须走可观察状态

SnapshotMutationPolicy:什么时候算”变了”

val a = mutableStateOf(listOf(1, 2))               // 默认:结构相等
val b = mutableStateOf(listOf(1, 2), structuralEqualityPolicy())
val c = mutableStateOf(obj, referentialEqualityPolicy())
val d = mutableStateOf("", neverEqualPolicy())
Policy判定适用
structuralEqualityPolicy()(默认)== 相等视为未变数据类、列表(内容相同不触发重组)
referentialEqualityPolicy()只有同一引用才视为未变大对象、懒加载的复杂值
neverEqualPolicy()每次写入都视为变化每帧都要更新的性能计数

关键坑:默认结构相等下,给同一个可变对象 list.add(x) 再赋回去——== 相等 → 不触发重组。要 list + x(新建实例)或改用不可变值。

derivedStateOf:缓存派生结果

val showTopButton by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
  • 计算块里读取的 Snapshot 状态变化时才重算,且结果不变时不通知下游
  • 适合”高频输入 → 低频输出”(滚动位置 → 是否显示按钮)。
  • 输入与输出同频时用它是浪费,直接读取即可。

snapshotFlow:把 Snapshot 读变成 Flow

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .distinctUntilChanged()
        .collect { index -> analytics.onVisibleIndexChanged(index) }
}

snapshotFlow 观察块内读取的状态并发射变化流。用途:

  • 接 Flow 操作符(debounce、distinctUntilChanged、map)
  • 驱动非 UI 副作用(埋点、日志)
  • conflate/collectLatest 组合控制节奏

不要用它替代 UI 直接读状态——UI 该用 by remember { mutableStateOf }

produceState:把一次性异步转成状态

val imageState by produceState<ImageState>(initialValue = Loading, url) {
    value = Loading
    value = try {
        Loaded(repository.loadImage(url))
    } catch (e: Exception) {
        Error(e)
    }
}
  • 每次 key(url)变化时,块内协程取消重启。
  • 适合”从外部数据源加载到本地状态”的封装。
  • 复杂场景(多个数据源、需要 ViewModel 级生命周期)应走 ViewModel。

rememberSaveable:跨配置变更/进程恢复

var text by rememberSaveable { mutableStateOf("") }       // 自动可存类型
var filter by rememberSaveable(stateSaver = ...) { ... }  // 自定义 Saver
选择生命周期
remember仅重组存活;配置变更/进程重建丢失
rememberSaveable配置变更恢复(Bundle);进程重建尽力恢复
ViewModel + StateFlow配置变更存活;进程重建由 SavedStateHandle 决定

决策:UI 瞬态(滚动位置、输入草稿)用 rememberSaveable;业务数据(列表、登录态)用 ViewModel。不要用 rememberSaveable 存大对象或网络状态。

复制即用

@Composable
fun StatePatterns() {
    // 1. 结构相等:内容相同不重组
    var text by remember { mutableStateOf("") }

    // 2. 派生:滚动决定按钮显隐
    val listState = rememberLazyListState()
    val showTop by remember {
        derivedStateOf { listState.firstVisibleItemIndex > 0 }
    }

    // 3. 一次性加载转状态
    val imageState by produceState<ImageState>(initialValue = ImageState.Loading, url) {
        value = ImageState.Loading
        value = try {
            ImageState.Loaded(loader.load(url))
        } catch (e: Exception) {
            ImageState.Error(e)
        }
    }

    // 4. 跨配置恢复
    var draft by rememberSaveable { mutableStateOf("") }
}

常见陷阱

  • 把业务状态塞 rememberSaveable:进程重建不保证,且 Bundle 容量有限。
  • 每帧创建新对象塞 State:无意义重组。
  • derivedStateOf 依赖不该观察的 State:把它当缓存而不当”派生值计算器”,会漏更新。
  • 在组合中读多个 State 拼字符串:用 derivedStateOf 减少读取次数。
  • 忘 StateFlow 初始值语义collectAsStateWithLifecycle 首次收集用 initialValue,后续用 Flow 值。

要点

  • 默认 policy 是结构相等;可变对象原地修改不会触发重组。
  • derivedStateOf 缓存派生结果,避免下游无意义重组。
  • snapshotFlow 把状态读变成可组合的 Flow,用于副作用。
  • produceState 封装一次性异步;rememberSaveable 只存瞬态。
  • 业务状态放 ViewModel,UI 瞬态放 rememberSaveable。

相关页面