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 有个整体认知,我画了一张结构图:

Kotlin Flow 核心知识体系 Flow 冷流设计 协程原生 操作符丰富 无收集者不执行 结构化并发 map/filter/combine 节省资源 自动取消 背压处理 适合复杂异步数据流场景

从这张图可以看出来,Flow 的三大特性是相互支撑的。冷流设计保证了资源不会浪费,协程原生让它能无缝融入现有的协程代码,而丰富的操作符则是它真正的杀手锏。

什么时候用 Flow?什么时候用 LiveData?

我个人的经验是:

  • 用 LiveData:简单的 UI 状态绑定,比如显示/隐藏加载动画、展示一个网络请求的结果。这种场景下 LiveData 的简洁性无可替代。
  • 用 Flow:需要链式操作、合并多个数据源、处理背压、或者需要更细粒度的线程控制。比如搜索框的防抖、数据库的实时查询、WebSocket 的消息流。

注意:不要为了用 Flow 而用 Flow。如果你的场景只需要一个简单的 setValue,用 LiveData 就够了。过度设计是很多开发者的通病——我以前也犯过。

举个例子,我之前接手过一个项目,里面用 Flow 实现了一个计数器——每次点击按钮就 emit 一下。代码写了三四十行,其实用 LiveData 三行就能搞定。这种场景下,Flow 反而增加了复杂度。

所以我的建议是:先想清楚你的数据是「一次性」的还是「流式」的。一次性数据用 LiveData,流式数据用 Flow。这样选型,基本不会出错。

好了,这一章我们聊了 Flow 是什么、为什么需要它、以及它和 LiveData 的区别。下一章我们会深入 Flow 的创建和收集,到时候我会分享一些我在项目中踩过的坑——特别是关于 flowOnlaunchIn 的那些事。


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