2. Kotlin Flow 基础回顾:Flow、StateFlow、SharedFlow 的特点与选择
在开始监听权限状态之前,必须先理解 Kotlin 协程生态中三种数据流的概念差异。Flow、StateFlow 与 SharedFlow 分别服务于不同的数据传播场景,选择错误会导致状态丢失、资源浪费或 UI 与业务层耦合。本章从冷/热流本质出发,对比三者的核心特性,并给出基于具体场景的选择策略。
2.1 Flow —— 冷流的标准实现
Flow 是协程中处理异步数据流的基本接口。它是一个 冷流:每次调用 collect 时,生产者的代码块都会从头执行。同一个 Flow 被多个收集者收集时,每个收集者得到独立的数据序列。
- 典型创建方式:
flow { ... }、flowOf(...)、集合扩展.asFlow()。 - 终止操作:
collect、toList、first等均为挂起函数。 - 适用场景:一次性数据获取(如网络请求结果流),或者每个收集者需要独立计算的任务。
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i) // 发射数据
}
}
// 每次 collect 都会打印 1,2,3
scope.launch { simpleFlow().collect { println(it) } }
2.2 StateFlow —— 状态持有者
StateFlow 是热流,继承自 SharedFlow,专门用于管理状态。它必须有一个初始值,并且始终缓存最新值——新订阅者会立即收到当前状态,不会重复之前的所有发射。
- 不可变性:外部只暴露
StateFlow(只读),内部使用MutableStateFlow更新。 - 去重:默认使用
equals比较,只有值变化时才通知订阅者。 - 典型用途:替代
LiveData作为 ViewModel 的 UI 状态。
private val _permissionState = MutableStateFlow<String>("unknown")
val permissionState: StateFlow<String> = _permissionState
// 更新状态
_permissionState.value = "granted" // 自动通知所有活跃订阅者
2.3 SharedFlow —— 可配置的事件总线
SharedFlow 是热流,相比 StateFlow 它没有初始值,也不强制只保留最后一个值。通过 replay 参数可以指定新订阅者收到之前发射的多少个值,extraBufferCapacity 控制缓冲区大小以处理背压。
- 无初始值:适合发送一次性事件(如导航、Toast)。
- 可自定义重放:
replay = 0则新订阅者不会收到历史事件。 - 常用场景:事件总线、通知、权限请求回调。
// 创建没有重放、缓冲的事件流
val eventFlow = MutableSharedFlow<PermissionEvent>(replay = 0)
// 发射事件
eventFlow.emit(PermissionEvent.Granted)
// 收集(仅当订阅后发射的事件才会收到)
scope.launch { eventFlow.collect { event -> handleEvent(event) } }
2.4 三者特性对比
| 特性 | Flow | StateFlow | SharedFlow |
|---|---|---|---|
| 冷流 / 热流 | 冷 | 热 | 热 |
| 是否有初始值 | 无 | 有(必填) | 无(可空) |
| 是否缓存值 | 不缓存 | 始终缓存最新值 | 可配置 replay |
| 是否去重 | 否 | 是(equals 比较) | 否 |
| 背压处理 | 默认挂起生产者 | 丢旧值(conflated) | 缓冲 + 挂起策略 |
| 订阅者数量 | 每个订阅者独立 | 共享同一实例 | 共享同一实例 |
| 常用场景 | 一次性/独立计算流 | UI 状态(ViewModel) | 事件 / 通知 / 消息 |
2.5 选择策略
- UI 状态更新 → 使用
StateFlow。它天然支持最新值、去重,与collectAsState()配合完美。 - 一次性事件(如 Snackbar、导航、权限结果) → 使用
SharedFlow并设replay = 0。确保事件只被一个新订阅者消费一次。 - 独立计算或网络请求流 → 使用
Flow。每次收集都需要重新触发生产者。
常见误区:不要用
StateFlow 发送一次性事件,因为它会立即重复最新值给新订阅者,导致事件重复处理。反之也不要用 SharedFlow 管理状态,因为它没有初始值且不会自动去重。
调试建议:运行
flowOn(Dispatchers.IO) 改变上游上下文时,务必理解 Flow 的上下文保留规则;StateFlow 和 SharedFlow 的发射应该发生在同一个协程中,避免竞态。
2.6 性能与稳定性
- 状态流去重:
StateFlow内部使用distinctUntilChanged过滤相等值,减少不必要的 UI 刷新。 - 缓冲区大小:
SharedFlow的extraBufferCapacity默认 0,若生产者快于消费者且无缓冲,会导致挂起;根据事件频率适当增大(如 64)。 - 避免泄漏:所有流都必须在 ViewModel 或可感知生命周期的作用域中收集,使用
viewModelScope或Lifecycle.repeatOnLifecycle。