3. StateFlow 与 MutableStateFlow 的关系:数据源与只读接口、更新方式
3.1 核心概念
StateFlow 是一个只读的响应式状态容器,它继承自 Flow,对外暴露一个不可变的 value 属性。调用方只能订阅(collect)状态的变更,不能直接修改状态值。
MutableStateFlow 是 StateFlow 的可变实现,它除了继承 StateFlow 的所有只读能力外,额外提供了 value 的 setter 以及 update 函数,允许在协程作用域内安全地修改状态。
一句话总结: MutableStateFlow 是数据的“源头”,StateFlow 是数据的“窗口”——外部只能透过窗口观察,源头才能改变数据。
3.2 原理图解
下图展示了 ViewModel 中典型的设计模式:内部持有 MutableStateFlow,但对外只暴露 StateFlow 接口,确保数据流向单一、可控。
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 练习题
-
判断对错: 以下代码能否通过编译?
答案:不能。因为val state: StateFlow<Int> = MutableStateFlow(0) state.value = 1StateFlow的 value 没有 setter。 -
选择: 下列哪个写法能安全地递增计数器的值?
答案:B。update 是原子操作,避免多协程并发读写导致的数据丢失。// 选项 A _state.value = _state.value + 1 // 选项 B _state.update { it + 1 } -
补全: 在 ViewModel 中,通常将
MutableStateFlow声明为private,然后对外暴露_____类型。
答案:StateFlow。
3.7 应用边界
- 适用场景:
- ViewModel 向 UI 层暴露状态(必须用
StateFlow对外,内部用MutableStateFlow)。 - 数据层向 ViewModel 提供只读状态。
- 跨组件共享状态但禁止外部修改。
- ViewModel 向 UI 层暴露状态(必须用
- 不适用场景:
- 需要双向绑定或直接用户交互修改(使用
MutableStateFlow并配合 UI 事件回调)。 - 需要多播历史事件(考虑使用
SharedFlow)。 - 协程取消时仍需保留最新值(
StateFlow适合,但仍需注意取消订阅后不再接收更新)。
- 需要双向绑定或直接用户交互修改(使用
实践提示: 在
build.gradle 中依赖 Kotlin 协程库时,确保版本 ≥ 1.3.0 以使用 StateFlow。推荐在 ViewModel 中通过 viewModelScope 启动协程来更新 MutableStateFlow。