1、Flow初探:什么是Flow?为什么需要Flow?Flow与LiveData的对比
说实话,我第一次接触 Kotlin Flow 的时候,心里是有点抵触的。
那时候项目里 LiveData 用得正顺手,ViewModel + LiveData 的组合拳打得飞起。突然冒出来个 Flow,还要学什么冷流热流、协程作用域……我当时就想:这玩意儿是不是又是个「为了新而新」的轮子?
后来在重构一个搜索模块的时候,我彻底改观了。那个模块需要监听输入框变化、去抖、请求网络、再更新 UI——用 LiveData 写出来,代码又臭又长,还容易漏掉取消订阅。换成 Flow 之后,整个链路清晰得像流水线。嗯,从那以后,我再也没回头写过 LiveData 的链式操作。
什么是 Flow?
Flow 是 Kotlin 协程库提供的一个冷数据流。说白了,它就是一条「管道」,数据像水流一样从上游流到下游。你可以在这条管道上做各种处理——过滤、映射、合并、异常处理……
我习惯把它理解成「序列的异步版本」。普通的 Sequence 是同步的,每次取一个值都会阻塞线程。而 Flow 是挂起式的,不会阻塞线程,还能自动处理背压。
看个最简单的例子:
// 创建一个 Flow
fun getNumbers(): Flow<Int> = flow {
for (i in 1..5) {
delay(100) // 模拟异步操作
emit(i) // 发射数据
}
}
// 收集 Flow
fun main() = runBlocking {
getNumbers().collect { value ->
println("收到: $value")
}
}
这里有个关键点:flow { ... } 里的代码不会立即执行。只有调用了 collect,数据才开始流动。这就是所谓的「冷流」——没人收集,就不干活。
为什么需要 Flow?
你可能会问:LiveData 也能做类似的事啊,为什么还要学 Flow?
我遇到过这样一个场景:App 里有个实时汇率页面,需要同时监听三个数据源——本地缓存、WebSocket 推送、定时轮询。用 LiveData 的话,我得写三个 LiveData,然后在 ViewModel 里用 MediatorLiveData 合并。代码量翻倍不说,合并逻辑还容易出 bug。
Flow 解决的就是这类痛点。它提供了丰富的操作符,让你能用声明式的方式处理复杂的数据流。
Flow 的核心优势:
- 冷流设计:没有收集者就不执行,节省资源
- 丰富的操作符:map、filter、flatMapConcat、combine……应有尽有
- 协程原生:天然支持结构化并发,取消自动清理
- 背压处理:通过 buffer、conflate 等操作符控制流速
- 异常处理:catch、retry 等操作符让错误处理更优雅
说白了,LiveData 是为「观察单一数据」而生的,而 Flow 是为「处理数据流」而生的。如果你的数据只是一次性赋值,LiveData 完全够用。但如果你要处理一连串的异步事件——比如搜索建议、位置更新、数据库查询——Flow 才是正确的选择。
Flow 与 LiveData 的对比
我整理了一张对比表,方便你快速理解两者的区别:
| 维度 | LiveData | Flow |
|---|---|---|
| 数据模型 | 热流(始终活跃) | 冷流(按需激活) |
| 操作符 | 有限(map、switchMap) | 丰富(100+ 操作符) |
| 协程支持 | 需要额外适配 | 原生支持 |
| 背压处理 | 不支持 | 原生支持 |
| 生命周期感知 | 内置 | 需要 repeatOnLifecycle |
| 线程切换 | postValue/setValue | flowOn 操作符 |
| 异常处理 | Observer.onChanged 中 try-catch | catch 操作符 |
| 适用场景 | 简单 UI 状态绑定 | 复杂异步数据流 |
这里我想重点说一下「生命周期感知」。LiveData 最大的卖点就是自动感知生命周期——Activity 销毁时自动取消订阅。Flow 本身没有这个能力,但 Google 官方提供了 repeatOnLifecycle 这个扩展函数,用起来也很方便:
// 在 Fragment 中收集 Flow
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.myFlow.collect { value ->
// 更新 UI
}
}
}
这样写,Flow 就拥有了和 LiveData 一样的生命周期感知能力。而且更灵活——你可以精确控制是在 STARTED 还是 RESUMED 状态下收集。
我的建议:
新项目直接用 Flow 吧。老项目如果 LiveData 用得顺手,也没必要强行迁移。但一旦遇到需要链式操作、合并数据流、或者处理背压的场景,果断上 Flow。
Flow 的核心知识体系
为了让你对 Flow 有个整体认知,我画了一张结构图:
从这张图可以看出来,Flow 的三大特性是相互支撑的。冷流设计保证了资源不会浪费,协程原生让它能无缝融入现有的协程代码,而丰富的操作符则是它真正的杀手锏。
什么时候用 Flow?什么时候用 LiveData?
我个人的经验是:
- 用 LiveData:简单的 UI 状态绑定,比如显示/隐藏加载动画、展示一个网络请求的结果。这种场景下 LiveData 的简洁性无可替代。
- 用 Flow:需要链式操作、合并多个数据源、处理背压、或者需要更细粒度的线程控制。比如搜索框的防抖、数据库的实时查询、WebSocket 的消息流。
注意:不要为了用 Flow 而用 Flow。如果你的场景只需要一个简单的 setValue,用 LiveData 就够了。过度设计是很多开发者的通病——我以前也犯过。
举个例子,我之前接手过一个项目,里面用 Flow 实现了一个计数器——每次点击按钮就 emit 一下。代码写了三四十行,其实用 LiveData 三行就能搞定。这种场景下,Flow 反而增加了复杂度。
所以我的建议是:先想清楚你的数据是「一次性」的还是「流式」的。一次性数据用 LiveData,流式数据用 Flow。这样选型,基本不会出错。
好了,这一章我们聊了 Flow 是什么、为什么需要它、以及它和 LiveData 的区别。下一章我们会深入 Flow 的创建和收集,到时候我会分享一些我在项目中踩过的坑——特别是关于 flowOn 和 launchIn 的那些事。