一、LiveData 的局限与 StateFlow 的崛起
说实话,LiveData 用了这么多年,我一直觉得它是个好孩子。简单、可靠、不会泄漏。但好孩子也有短板。
我在项目中遇到过这样一个场景:一个复杂的搜索页面,用户输入关键词后,需要同时更新 UI、触发网络请求、还要记录日志。用 LiveData 写出来,代码就像一团乱麻。为什么?因为 LiveData 缺少一些关键能力。
LiveData 的三大痛点
- 不支持线程切换:LiveData 只能在主线程观察,子线程发数据?你得手动 postValue。我见过不少同事因为忘记 postValue 导致 UI 不更新,排查半天。
- 缺少操作符:map、flatMap、combine 这些?LiveData 没有。你想合并两个数据源?要么自己写 MediatorLiveData,要么引入第三方库。
- 粘性事件问题:这个坑我踩过。页面重建后,LiveData 会把旧数据再发一遍。如果你用它做一次性事件(比如弹 Toast),那画面太美我不敢看。
核心结论:LiveData 适合简单场景,但复杂状态管理需要更强大的工具。
二、StateFlow:LiveData 的现代替代者
StateFlow 是 Kotlin 协程家族的一员。它本质上是一个带状态的热流。说白了,它就是一个可以随时读取当前值、并且支持协程的数据容器。
StateFlow 的核心特性
| 特性 | LiveData | StateFlow |
|---|---|---|
| 线程安全 | 需手动 postValue | 天然支持协程 |
| 操作符 | 无 | 丰富(map、filter、combine 等) |
| 粘性事件 | 默认粘性 | 可控制 |
| 生命周期感知 | 内置 | 需配合 repeatOnLifecycle |
你可能会问:那 StateFlow 怎么用?我给你看个例子。
// ViewModel 中使用 StateFlow
class SearchViewModel : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
private val _searchResults = MutableStateFlow<List<Result>>(emptyList())
val searchResults: StateFlow<List<Result>> = _searchResults.asStateFlow()
fun onQueryChanged(query: String) {
_searchQuery.value = query
// 自动触发搜索
viewModelScope.launch {
_searchResults.value = repository.search(query)
}
}
}
// Fragment 中观察
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.searchResults.collect { results ->
adapter.submitList(results)
}
}
}
我的习惯:在 ViewModel 中,我永远用 MutableStateFlow 私有,对外暴露 StateFlow。这样能保证数据源只有一个入口,避免外部乱改。
三、SharedFlow:一次性事件的正确打开方式
还记得 LiveData 的粘性事件问题吗?SharedFlow 就是来解决这个的。
SharedFlow 和 StateFlow 的区别很简单:StateFlow 保存最新值,新订阅者立刻收到;SharedFlow 可以配置是否重放历史数据。对于弹 Toast、导航跳转这类一次性事件,SharedFlow 是完美选择。
SharedFlow 的配置参数
// 一次性事件,不重放
val events = MutableSharedFlow<Event>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// 发送事件
viewModelScope.launch {
events.emit(Event.ShowToast("搜索完成"))
}
// 接收事件
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.events.collect { event ->
when (event) {
is Event.ShowToast -> Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
}
}
}
}
我曾经踩过的坑:SharedFlow 的 replay 参数设得太大,导致页面重建后收到一堆旧事件。后来我统一用 replay=0,只在需要缓存历史数据时才调整。
四、迁移指南:从 LiveData 到 StateFlow
迁移不是一蹴而就的事。我建议分三步走。
第一步:评估现有代码
- 哪些 LiveData 是纯 UI 状态?→ 用 StateFlow 替换
- 哪些 LiveData 是一次性事件?→ 用 SharedFlow 替换
- 哪些 LiveData 涉及复杂数据转换?→ 优先迁移
第二步:逐步替换
别想着一天改完。我习惯从最底层的 Repository 开始,逐步向上替换。每替换一个模块,跑一遍测试。
第三步:统一团队规范
新项目直接用 StateFlow + SharedFlow。老项目按模块逐步迁移。我见过团队因为混用两种方案导致维护成本翻倍,嗯,这个坑咱们别踩。
五、未来趋势:Flow 生态的全面落地
Google 官方已经在 Jetpack 中全面拥抱 Flow。Room、DataStore、Retrofit 都原生支持 Flow。我个人判断,未来两年内,LiveData 会逐渐退居二线,成为「兼容性方案」。
但别急着删掉所有 LiveData。如果你的项目还在用 Java,或者团队对协程不熟悉,LiveData 依然是稳妥的选择。技术选型没有银弹,只有最适合当前团队的方案。
我的建议:新项目用 StateFlow,老项目逐步迁移。别为了炫技而迁移,也别因为怕麻烦而固守旧方案。平衡好技术债务和开发效率,才是架构师该做的事。
六、知识体系总览
这张图把整个知识体系串起来了。从 LiveData 的局限出发,经过迁移策略,最终到达 StateFlow 和 SharedFlow。你想想看,是不是比单纯记知识点清晰多了?
最后说一句:技术永远在演进。别怕学新东西,也别盲目追新。理解每个方案的适用场景,做出合理的技术决策,这才是架构师的核心能力。
公众号:蓝海资料掘金营,微信 deep3321