第2章 MutableStateFlow 创建与基本使用:构造函数、初始值、value属性、emit方法
MutableStateFlow 是 Kotlin 协程中提供可变状态容器的基础组件,它同时暴露 读(通过 value 属性)和 写(通过 value 设值或 emit() 挂起函数)的能力。理解其创建与基本 API 是后续对比 SharedFlow 的基石。
核心原理
- 热流:始终保持最新值,新订阅的收集者立即收到当前值(而不像
Flow冷流需要触发生产)。 - 状态容器:内部维护一个
value属性,任何修改都会立即更新并通知所有活跃收集者。 - 防抖(conflation):默认合并新值与旧值,若新值和旧值相等(通过
equals比较)则跳过通知,避免无效重发。 - 线程安全:所有读写操作(包括
emit和value)都是线程安全的,不需要额外同步。
关键区别:
value 属性可用于任意线程(包括非协程环境),是同步写操作;emit() 只能在协程上下文中调用,且是挂起函数,适合在协程内部按顺序发射值。
构造函数与初始值
| 参数 | 类型 | 说明 |
|---|---|---|
value |
T |
必填,设置初始状态。类型由泛型推断。 |
bufferSize |
Int |
默认 0(不缓存历史值),只在 SharedFlow 中有效。 |
创建示例:
// 基本创建
val counter = MutableStateFlow(0) // 初始值为 Int 类型 0
val name = MutableStateFlow("Kotlin") // 初始值为字符串
val list = MutableStateFlow(emptyList<Int>()) // 初始值为空列表
value 属性:同步读写
value 是一个可读可写的属性,读取立即返回当前最新值,写入直接更新状态并通知收集者。
// 读取
val current = counter.value // 0
// 写入
counter.value = 10
counter.value = 20
// 线程安全:可以在任意线程调用
thread {
counter.value = 99
}
技巧:
value 写入是非挂起的,适合在 onClick 回调、LiveData 绑定等非协程场景中使用。
emit() 方法:挂起写入
emit() 是 StateFlow 接口定义的方法,需要挂起协程。其行为与 value 赋值完全一致(都调用内部 setValue),但必须运行在协程中。
// 只能在协程内调用
viewModelScope.launch {
counter.emit(30)
}
// 等价于
viewModelScope.launch {
counter.value = 30
}
为什么需要 emit()? 为了保持 Flow 接口通用性,emit() 是 FlowCollector 的标准发射方法,当把 MutableStateFlow 当作 FlowCollector 传递时,必须使用 emit()。
常见错误与调试
- 初始值 null 问题:如果状态可能为空,应显式使用
MutableStateFlow<Type?>(null),否则类型推断会出问题。 - 忽略重复值过滤:
StateFlow默认使用equals()判断值是否变化。对于自定义类,务必正确实现equals()和hashCode(),否则即使内容一致也会重复触发收集。 - 在非协程中调用 emit:IDE 会有编译错误(
emit是挂起函数),必须用value赋值代替。 - 并发写入竞态:虽然
value和emit是线程安全的,但多个协程同时写入可能导致最终值与预期不符。推荐使用update { }原子更新(参见后续章节)。
// 错误示例:在 onClick 中用 emit
button.setOnClickListener {
// 编译错误:Suspend function 'emit' should be called only from a coroutine
counter.emit(1)
}
// 正确:使用 value
button.setOnClickListener {
counter.value = 1
}
性能与稳定性优化
| 场景 | 建议 | 原因 |
|---|---|---|
| 频繁更新状态 | 使用 value 批量赋值,减少收集次数 |
每次 value 设置都会触发所有观察者 |
| 值未变化 | 先比较再赋值:if (counter.value != newValue) counter.value = newValue |
避免冗余的通知 |
| 大量收集者 | 考虑使用 SharedFlow 并配置重放策略 |
StateFlow 为每个收集者保留最新值,适合 1 对多但数量可控 |
| 需要原子更新 | 使用 update { current -> current + 1 } |
保证读-改-写操作的原子性 |
调试提示:可以在 collect 中打印日志检查发射频率:
lifecycleScope.launch {
counter.collect { value ->
Log.d("StateFlow", "新值: $value")
}
}
注意:
StateFlow 默认会合并并跳过重复值,但 equals 比较是针对引用或值类型的 equals() 方法。对于可变对象(如 ArrayList),即使内部数据改变,引用未变,也不会触发收集。推荐使用不可变数据类或每次赋新实例。