3. StateFlow 与 MutableStateFlow 的关系:数据源与只读接口、更新方式

3.1 核心概念

StateFlow 是一个只读的响应式状态容器,它继承自 Flow,对外暴露一个不可变的 value 属性。调用方只能订阅(collect)状态的变更,不能直接修改状态值。

MutableStateFlow 是 StateFlow 的可变实现,它除了继承 StateFlow 的所有只读能力外,额外提供了 value 的 setter 以及 update 函数,允许在协程作用域内安全地修改状态。

一句话总结: MutableStateFlow 是数据的“源头”,StateFlow 是数据的“窗口”——外部只能透过窗口观察,源头才能改变数据。

3.2 原理图解

下图展示了 ViewModel 中典型的设计模式:内部持有 MutableStateFlow,但对外只暴露 StateFlow 接口,确保数据流向单一、可控。

MutableStateFlow ↔ StateFlow 关系模型 外部观察者 (UI / 其他组件) StateFlow<T> (只读接口) expose as: val state: StateFlow<T> MutableStateFlow<T> (数据源) 内部持有:private val _state = MutableStateFlow(initial) collect / 订阅 委托获取 value ❌ 不可直接修改 _state.value = newValue 箭头方向:数据流动方向;虚线表示“不允许”的操作;绿色实线表示合法的内部更新路径。

3.3 类比说明

概念 类比 解释
MutableStateFlow 邮局的内部后台数据库 只有内部人员(ViewModel/数据层)可以修改包裹状态
StateFlow 面向用户的物流跟踪页面 外部(UI)只能查看当前状态,不能点击“修改为已签收”
.collect() 用户刷新物流页面 每次状态变化时自动获得最新快照

3.4 更新方式对比

方法 使用场景 示例 线程安全
value = newValue 简单赋值,无需引用旧值 _state.value = "已加载" 否(需配合协程上下文)
update { old -> new } 基于当前值计算新值(原子操作) _state.update { it + 1 } ✅ 是(内部使用 CAS)
tryEmit / emit 在协程中发送值(一般不用于状态管理) _state.emit(newValue) ⚠️ 视调用上下文而定

3.5 常见误区

  • 误区 1: 认为 StateFlow 的 value 可以直接赋值。
    真相: StateFlow 没有 setter,只能通过 MutableStateFlow 修改。
  • 误区 2: 把 MutableStateFlow 直接暴露给 UI,而非用 StateFlow 封装。
    后果: 外部可随意篡改状态,破坏单向数据流。
  • 误区 3: 在多协程中直接用 _state.value = x 而不使用 update 可能导致数据竞争。
    建议: 当修改依赖旧值时始终使用 update。
  • 误区 4: 混淆 StateFlow 与 LiveData——认为 StateFlow 会自动处理生命周期。
    注意: StateFlow 本身不感知生命周期,需配合 repeatOnLifecycle 或 collectLatest 使用。

3.6 练习题

  1. 判断对错: 以下代码能否通过编译?
    val state: StateFlow<Int> = MutableStateFlow(0)
    state.value = 1
    答案:不能。因为 StateFlow 的 value 没有 setter。
  2. 选择: 下列哪个写法能安全地递增计数器的值?
    // 选项 A
    _state.value = _state.value + 1
    
    // 选项 B
    _state.update { it + 1 }
    答案:B。update 是原子操作,避免多协程并发读写导致的数据丢失。
  3. 补全: 在 ViewModel 中,通常将 MutableStateFlow 声明为 private,然后对外暴露 _____ 类型。
    答案:StateFlow。

3.7 应用边界

  • 适用场景:
    • ViewModel 向 UI 层暴露状态(必须用 StateFlow 对外,内部用 MutableStateFlow)。
    • 数据层向 ViewModel 提供只读状态。
    • 跨组件共享状态但禁止外部修改。
  • 不适用场景:
    • 需要双向绑定或直接用户交互修改(使用 MutableStateFlow 并配合 UI 事件回调)。
    • 需要多播历史事件(考虑使用 SharedFlow)。
    • 协程取消时仍需保留最新值(StateFlow 适合,但仍需注意取消订阅后不再接收更新)。
实践提示: 在 build.gradle 中依赖 Kotlin 协程库时,确保版本 ≥ 1.3.0 以使用 StateFlow。推荐在 ViewModel 中通过 viewModelScope 启动协程来更新 MutableStateFlow。