Flow vs LiveData:选型指南与迁移实践
>一句话收益:彻底搞清 Flow 与 LiveData 的本质区别,掌握何时选用、如何迁移,杜绝"随机选一个"的尴尬。
>适用版本:Android API 21+,Kotlin 1.9+,Lifecycle 2.7+,Coroutines 1.8+
>阅读时长:约 18 分钟
---
1. 从一个真实 Bug 切入
你的项目里有段代码大致是这样:
// ViewModelval searchResults = MutableLiveData >()
fun search(query: String) {
viewModelScope.launch {
repository.search(query).collect { results ->
searchResults.value = results // 后台线程调用 .value 崩溃!
}
}
}
运行时偶发CalledFromWrongThreadException:Cannot invoke setValue on a background thread。
根本原因:Flow.collect默认在协程调度器(可能是IO)运行,LiveData.value只能在主线程赋值。开发者将两套数据流体系混用,导致线程语义冲突。
这个 Bug 揭示了一个深层问题:LiveData 和 Flow 分别为不同场景而生,混用时必须清楚各自的线程契约。
---
2. LiveData 与 Flow 全景
2.1 两者的核心定位
LiveData Flow (Kotlin Coroutines)────────────────────────────── ──────────────────────────────
生命周期感知数据容器 冷/热数据流抽象
专为 UI 层设计 通用响应式流(任意层可用)
主线程交付 调度器灵活
仅支持最新值(无背压) 支持背压(buffer/conflate)
观察者自动解绑 需手动管理 collect 生命周期
Java 友好 Kotlin 原生
2.2 冷流 vs 热流
Cold Flow (默认 flow {})每次 collect → 重新执行 block
Collector 1 ──collect──→ [producer runs] → emit(1) → emit(2)
Collector 2 ──collect──→ [producer runs again] → emit(1) → emit(2)
Hot Flow (SharedFlow / StateFlow)
独立生产,多消费者共享
Producer ──emit──→ [SharedFlow buffer]
├──→ Collector 1
└──→ Collector 2
LiveData
本质上是热的(setValue 随时可调)
但没有 buffer,只保留最新值
类似 StateFlow(replay=1) + 生命周期感知
2.3 StateFlow 与 LiveData 的相似性
StateFlow是最接近 LiveData 的 Flow 类型:| 特性 | LiveData | StateFlow |
|------|----------|-----------|
| 保存最新值 | ✅ | ✅ |
| 初始值 | 可为 null | 必须有初始值 |
| 生命周期感知 | ✅ 原生 | ❌ 需repeatOnLifecycle|
| 线程安全 | 主线程 setValue | 任意线程 value/emit |
| 去重 | ❌ | ✅(结构相等判断) |
| 测试 | 需InstantTaskExecutorRule| 原生协程测试 |
---
3. 核心原理
3.1 LiveData 的生命周期感知原理
LiveData通过LifecycleOwner注册LifecycleObserver,在DESTROYED时自动移除观察者(LiveData.ObserverWrapper.shouldBeActive):Activity/Fragment (LifecycleOwner)│
└─ observe(owner, observer)
│
└─ owner.lifecycle.addObserver(LifecycleBoundObserver)
│
└─ onStateChanged(DESTROYED) → removeObserver()
AOSP 路径:frameworks/support/lifecycle/lifecycle-livedata-core/src/main/java/androidx/lifecycle/LiveData.java
关键方法:LiveData#observe、LifecycleBoundObserver#onStateChanged
3.2 Flow 的 collect 与协程取消
flow {}是冷流,collect触发 block 执行。协程取消时(cancel()或作用域结束),collect内部通过CancellationException传播取消,上游emit会检查取消状态(ensureActive()):viewModelScope.launch { // 作用域flow { emit(1); emit(2) } // 冷流 block
.collect { value -> // 触发执行
// 协程取消 → CancellationException → block 停止
}
} // ViewModel 销毁 → viewModelScope 取消 → collect 停止
AOSP 路径:kotlinx-coroutines-core/common/src/flow/Flow.kt
关键方法:AbstractFlow#collect、FlowCollector#emit
3.3 repeatOnLifecycle:补齐 Flow 的生命周期感知
repeatOnLifecycle(Lifecycle.State.STARTED)会在生命周期进入 STARTED 时启动新协程,离开 STARTED 时取消,从而实现和 LiveData 等价的生命周期感知:ON_START → launch { stateFlow.collect { } }ON_STOP → cancel() // 协程取消,停止收集
ON_START → launch { stateFlow.collect { } } // 重新开始
ON_DESTROY → 外层协程取消
---
4. 代码示例
4.1 正确写法:ViewModel 使用 StateFlow
class SearchViewModel(private val repo: SearchRepository) : ViewModel() {private val _query = MutableStateFlow("")
// 对 UI 只暴露不可变 StateFlow
val searchResults: StateFlow > = _query
.debounce(300) // 防抖,300ms 内只处理最后一次
.filter { it.length >= 2 } // 过滤短查询
.flatMapLatest { query -> // 取消旧请求,执行新请求
if (query.isEmpty()) flowOf(emptyList())
else repo.search(query) // 返回 Flow >
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000), // UI 消失 5s 后停止
initialValue = emptyList()
)
fun onQueryChange(query: String) {
_query.value = query
}
}
// Fragment 中收集class SearchFragment : Fragment() {
private val vm: SearchViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// 使用 repeatOnLifecycle 保证前后台安全
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.searchResults.collect { items ->
adapter.submitList(items)
}
}
}
}
}
4.2 错误写法 → 问题 → 正确写法
错误写法 1:在 IO 线程给 LiveData.value 赋值// ❌ 错误:在协程 IO 调度器中调用 .valuefun loadData() {
viewModelScope.launch(Dispatchers.IO) {
val result = repo.fetchData()
_liveData.value = result // CalledFromWrongThreadException!
}
}
问题:LiveData.setValue()只能在主线程调用。
// ✅ 正确:使用 postValue 或切换到主线程fun loadData() {
viewModelScope.launch(Dispatchers.IO) {
val result = repo.fetchData()
_liveData.postValue(result) // 线程安全
}
}
// 或更好:直接在主线程协程中操作
fun loadData() {
viewModelScope.launch { // 默认 Main
val result = withContext(Dispatchers.IO) { repo.fetchData() }
_liveData.value = result
}
}
错误写法 2:在 Fragment onStart 中直接 launch collect,不用 repeatOnLifecycle// ❌ 错误:onStart 里 launch,会在 onStop 后继续收集override fun onStart() {
super.onStart()
lifecycleScope.launch {
vm.uiState.collect { render(it) }
// onStop 后 collect 还在运行,浪费资源,后台更新 UI 可能崩溃
}
}
问题:协程在lifecycleScope内,Fragment 销毁时才取消,但 onStop→onStart 之间仍在收集。
// ✅ 正确:repeatOnLifecycle 在 STARTED 时收集,STOPPED 时暂停override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.uiState.collect { render(it) }
}
}
}
错误写法 3:StateFlow 去重导致 UI 不刷新// ❌ 错误:修改同一个 list 引用val list = _state.value.items
list.add(newItem)
_state.value = _state.value.copy(items = list) // equals 可能为 true,不 emit
// ✅ 正确:创建新 list_state.value = _state.value.copy(
items = _state.value.items + newItem // + 创建新 List
)
---
5. 最佳实践
5.1 新项目:Repository 层用 Flow,ViewModel 层用 StateFlow/SharedFlow,UI 层用 collectWithLifecycle
做法:Repository 返回Flow,ViewModel 用stateIn转为StateFlow,Fragment/Activity 用repeatOnLifecycle或collectWithLifecycle收集。原因:Flow 在 Repository 层保持灵活(可背压、可组合操作符);StateFlow 在 ViewModel 给 UI 提供稳定的单一状态来源;repeatOnLifecycle保证后台不更新 UI。对比:若不这样做,直接在 Repository 用 LiveData,导致 LiveData 渗透到数据层,污染非 Android 模块,无法在纯 JVM 单元测试中使用。5.2 一次性事件用 SharedFlow(replay=0),不要用 LiveData
做法:导航跳转、Toast 显示等一次性事件,使用MutableSharedFlow (replay=0),UI 用repeatOnLifecycle(STARTED)收集。原因:LiveData 粘性特性(新观察者收到旧值)会导致一次性事件在屏幕旋转后重复触发。SharedFlow(replay=0)没有缓存,不会粘性重放。对比:用 LiveData + SingleLiveEvent 模式虽能解决,但 SingleLiveEvent 是社区方案,多观察者时仍有 bug;SharedFlow 是官方推荐方案。5.3 使用 SharingStarted.WhileSubscribed(5000) 代替 Eagerly
做法:stateIn的started参数使用SharingStarted.WhileSubscribed(5_000)(5 秒超时)。原因:UI 进入后台后 5 秒内无订阅者则停止上游 Flow,避免后台持续请求网络/数据库;5 秒宽限期应对屏幕旋转(旋转时 Activity 重建约 1-2 秒)。对比:Eagerly在 ViewModel 创建时立即启动并永不停止,后台仍消耗资源;Lazily第一次有订阅者后永不停止,无法节省资源。5.4 测试时用 Turbine 库替代手动 collect
做法:引入app.cash.turbine:turbine,用flow.test { assertThat(awaitItem()).isEqualTo(expected) }测试 Flow。原因:手动launch + collect + delay测试 Flow 容易出现竞争条件;Turbine 提供确定性的awaitItem(),不需要 delay。对比:不用 Turbine 则需要大量样板代码,且测试结果可能因调度时序不同而 flaky。5.5 避免在 ViewModel 中持有 LiveData observer
做法:ViewModel 中如需观察另一个 LiveData,转用 Flow(liveData.asFlow())并在viewModelScope中 collect,而非observeForever。原因:observeForever不自动解绑,需手动removeObserver,容易遗漏造成内存泄漏;Flow + 协程作用域会随 ViewModel 销毁自动取消。---
6. 常见坑点
坑点 1:StateFlow 去重导致 UI 无法刷新
现象:明明调用了_state.value = newState,UI 没有刷新。原因:StateFlow 使用结构相等(equals)去重,若新值与旧值==为 true,不会 emit。复现:data class 包含 List,修改 List 内元素但不创建新对象时equals仍为 true。解决:确保每次 emit 创建新对象(copy()),或对包含可变集合的 data class 重写equals。坑点 2:launchIn 遗忘 lifecycleScope,在后台继续收集
现象:App 进入后台后 CPU 仍有持续活动,内存不释放。原因:用flow.launchIn(viewModelScope)代替collect,但 ViewModel 的 scope 不感知生命周期。复现:在 Fragment 中写vm.flow.launchIn(lifecycleScope)但不用repeatOnLifecycle。解决:UI 层收集必须配合repeatOnLifecycle。// ✅ 正确lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.uiFlow.onEach { render(it) }.launchIn(this)
}
}
坑点 3:LiveData.observe 在 ViewPager2 + Fragment 中多次触发
现象:ViewPager2 切换 tab 后,旧 Fragment 的观察者仍在触发,出现重复处理。原因:observe(viewLifecycleOwner, observer)注册了多个 observer,LiveData 粘性特性使旧值立即触发所有 observer。复现:快速切换 ViewPager2 tab,打印 observer 回调次数。解决:迁移到StateFlow+repeatOnLifecycle;若保留 LiveData,确保只在onViewCreated中注册,且用viewLifecycleOwner。坑点 4:Room 返回 Flow 在主线程 collect 导致 ANR
现象:Room query 返回Flow >,在主线程 collect 时 ANR。原因:配置了allowMainThreadQueries()时,大量数据查询阻塞主线程。复现:allowMainThreadQueries()+ Room Flow + 大表。解决:移除allowMainThreadQueries(),Room Flow 自动在IO线程执行。---
7. 总结
1.LiveData 专为 UI 层设计,生命周期感知开箱即用,适合维护老代码或简单 ViewModel → UI 数据绑定。
2.StateFlow 是 LiveData 的现代替代,线程安全、去重、原生协程支持,配合repeatOnLifecycle可完全替代 LiveData。
3.Repository / 数据层统一用 Flow,避免 LiveData 渗透到非 Android 模块,提升可测试性。
4.一次性事件用 SharedFlow(replay=0),彻底解决 LiveData 粘性导致的事件重复问题。
5.SharingStarted.WhileSubscribed(5000)是最佳 stateIn 策略,平衡资源消耗与屏幕旋转体验。
>核心结论:新代码首选 StateFlow + Flow + repeatOnLifecycle;LiveData 留给遗留代码维护。
---
参考资料
- 官方文档:从 LiveData 迁移到 Kotlin Flow
- 官方文档:StateFlow 和 SharedFlow
- 官方文档:repeatOnLifecycle
- AOSP: LiveData.java
- AOSP: StateFlow.kt
- Turbine 测试库