一、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 迁移知识体系 LiveData 简单可靠,但有局限 迁移策略 分三步,逐步替换 StateFlow 状态管理,替代 LiveData SharedFlow 一次性事件,解决粘性问题 线程安全 LiveData:需 postValue StateFlow:天然协程支持 ✅ StateFlow 胜出 操作符支持 LiveData:无 StateFlow:map/filter/combine ✅ StateFlow 胜出 粘性事件 LiveData:默认粘性 SharedFlow:可配置 ✅ SharedFlow 胜出 未来趋势:Flow 生态全面落地 Room、DataStore、Retrofit 原生支持 Flow LiveData 将退居二线,成为兼容性方案

这张图把整个知识体系串起来了。从 LiveData 的局限出发,经过迁移策略,最终到达 StateFlow 和 SharedFlow。你想想看,是不是比单纯记知识点清晰多了?

最后说一句:技术永远在演进。别怕学新东西,也别盲目追新。理解每个方案的适用场景,做出合理的技术决策,这才是架构师的核心能力。


公众号:蓝海资料掘金营,微信 deep3321