10、StateFlow与SharedFlow:StateFlow与LiveData对比、StateFlow的使用场景、SharedFlow的配置(replay、extraBufferCapacity)、SharedFlow与StateFlow的选择
好,咱们今天来聊聊 Flow 家族里两个非常重要的成员——StateFlow 和 SharedFlow。说实话,这两个东西刚出来的时候,我也纠结过一阵子。尤其是从 LiveData 迁移过来的那段时间,总觉得「嗯?这不就是 LiveData 换了个马甲吗?」。但用久了你会发现,它们俩的定位完全不同,选错了,坑可不小。
StateFlow 与 LiveData 的对比
先说说 StateFlow 和 LiveData 的区别。我个人习惯把 LiveData 看作是「Android 原生时代的产物」,而 StateFlow 是「协程时代的正规军」。为什么这么说?
| 对比维度 | LiveData | StateFlow |
|---|---|---|
| 生命周期感知 | 原生支持,自动取消订阅 | 需要配合 repeatOnLifecycle 或 collect |
| 线程安全 | 主线程更新,postValue 有丢数据风险 | 协程安全,支持并发更新 |
| 初始值 | 无强制要求 | 必须提供初始值 |
| 粘性事件 | 默认粘性 | StateFlow 粘性,SharedFlow 可配置 |
| 操作符支持 | 有限(map、switchMap 等) | 完整的 Flow 操作符链 |
| 单元测试 | 需要 Android 依赖 | 纯 Kotlin,测试友好 |
我在项目中遇到过这样一个场景:用 LiveData 做倒计时,结果 postValue 在短时间内多次调用时,中间的值全丢了。你想想看,用户看到倒计时从 10 直接跳到 7,体验多糟糕。换成 StateFlow 配合 MutableStateFlow,用协程的 delay 来更新,问题就解决了。
核心区别一句话:LiveData 是为 View 层设计的观察者模式工具,而 StateFlow 是协程生态中状态管理的标准方案。
StateFlow 的使用场景
StateFlow 说白了就是「带状态的热流」。它始终持有最新的值,新订阅者一进来就能拿到当前状态。我个人觉得它最适合以下场景:
- UI 状态管理:比如 ViewModel 中的界面状态,用 StateFlow 再合适不过
- 数据层缓存:比如用户信息、配置参数,需要保持最新值
- 跨页面共享状态:多个 Fragment 需要读取同一个状态时
// 一个典型的 ViewModel 用法
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(MainUiState())
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
fun updateName(name: String) {
_uiState.update { it.copy(userName = name) }
}
}
这里有个小细节:用 update 而不是直接 value =。为什么?因为 update 是原子操作,在高并发场景下不会出现竞态条件。我曾经在项目里直接用 value = 赋值,结果在多个协程同时更新时,状态出现了不一致。嗯,从那以后我就只用 update 了。
SharedFlow 的配置:replay 与 extraBufferCapacity
SharedFlow 比 StateFlow 灵活得多。它就像一个「可配置的广播站」,你可以决定:
- 新来的听众能听到之前几条消息(replay)
- 消息发太快时,缓存区能存多少条(extraBufferCapacity)
- 缓存满了怎么办(onBufferOverflow)
// SharedFlow 的典型配置
val sharedFlow = MutableSharedFlow<Int>(
replay = 1, // 新订阅者能收到最近 1 条历史消息
extraBufferCapacity = 5, // 额外缓存 5 条
onBufferOverflow = BufferOverflow.DROP_OLDEST // 满了就丢弃最旧的
)
这三个参数怎么配?我根据自己的经验总结一下:
| 参数 | 作用 | 推荐值 |
|---|---|---|
| replay | 新订阅者能收到多少条历史数据 | 事件类用 0,状态类用 1 |
| extraBufferCapacity | 缓存区大小,防止背压 | 根据生产速度定,一般 5-10 |
| onBufferOverflow | 缓存满时的策略 | 事件用 DROP_OLDEST,状态用 SUSPEND |
避坑指南:我曾经把 replay 设成 100,结果页面切回来时,用户看到了一堆过期的 Toast 提示。后来改成 replay = 0,配合 extraBufferCapacity = 5,才解决了问题。
SharedFlow 与 StateFlow 的选择
这个问题其实很简单,但很多人会纠结。我给大家一个判断标准:
- 需要「当前值」吗? 需要 → StateFlow;不需要 → SharedFlow
- 需要「多个独立事件」吗? 需要 → SharedFlow;不需要 → StateFlow
- 需要「配置粘性行为」吗? 需要 → SharedFlow;默认粘性 → StateFlow
说白了,StateFlow 是 SharedFlow 的一个特例——它相当于 replay = 1,extraBufferCapacity = 0,onBufferOverflow = DROP_OLDEST 的 SharedFlow。但 Google 把它单独拎出来,就是为了语义清晰:StateFlow 代表「状态」,SharedFlow 代表「事件」。
注意:不要把事件(如点击、导航、Toast)用 StateFlow 来发。因为 StateFlow 会保留最新值,用户旋转屏幕后重新订阅,会再次触发这个「事件」,导致重复弹 Toast。我见过有人踩这个坑,调试了半天才发现是 StateFlow 的粘性特性导致的。
知识体系图
下面这张图帮你理清 StateFlow 和 SharedFlow 的关系与选择逻辑:
最后说一句,选 StateFlow 还是 SharedFlow,没有绝对的对错。但如果你拿不准,就记住这个原则:状态用 StateFlow,事件用 SharedFlow。我在项目里一直这么用,从来没出过问题。
总结:StateFlow 适合需要「当前值」的场景,SharedFlow 适合「一次性事件」场景。配置 SharedFlow 时,replay 控制历史消息数,extraBufferCapacity 控制缓存大小,onBufferOverflow 控制溢出策略。选对了,你的代码会非常清爽;选错了,调试起来够你喝一壶的。
公众号:蓝海资料掘金营,微信deep3321