第2章 MutableStateFlow 创建与基本使用:构造函数、初始值、value属性、emit方法

MutableStateFlow 是 Kotlin 协程中提供可变状态容器的基础组件,它同时暴露 读(通过 value 属性)和 写(通过 value 设值或 emit() 挂起函数)的能力。理解其创建与基本 API 是后续对比 SharedFlow 的基石。

核心原理

  • 热流:始终保持最新值,新订阅的收集者立即收到当前值(而不像 Flow 冷流需要触发生产)。
  • 状态容器:内部维护一个 value 属性,任何修改都会立即更新并通知所有活跃收集者。
  • 防抖(conflation):默认合并新值与旧值,若新值和旧值相等(通过 equals 比较)则跳过通知,避免无效重发。
  • 线程安全:所有读写操作(包括 emit 和 value)都是线程安全的,不需要额外同步。
关键区别:value 属性可用于任意线程(包括非协程环境),是同步写操作;emit() 只能在协程上下文中调用,且是挂起函数,适合在协程内部按顺序发射值。
MutableStateFlow 创建与基本操作流程 创建 MutableStateFlow 构造函数指定初始值 value 属性(同步读) value = 新值(同步写) emit(新值)(挂起) 收集者 .collect { ... } 说明:value 写操作可在任何线程执行,emit 只能在协程内调用。 两者都会触发收集者重新执行,但 value 直接赋值会立即更新状态。

构造函数与初始值

参数 类型 说明
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),即使内部数据改变,引用未变,也不会触发收集。推荐使用不可变数据类或每次赋新实例。