1、Flow 初探:什么是响应式编程?Flow 解决了什么问题?Flow 与 LiveData 和 RxJava 的对比

大家好,欢迎来到《Kotlin Flow 响应式编程完全指南》的第一章。

说实话,每次开始一个新系列,我都有点小兴奋。因为 Flow 这个东西,真的是我近几年在 Android 开发中用得最爽的工具之一。它不是什么新概念,但 Kotlin 把它做得特别优雅。

今天我们就来聊聊,响应式编程到底是个啥?Flow 为什么值得学?以及它跟 LiveData、RxJava 这些老面孔比,到底强在哪?

1.1 什么是响应式编程?

先别急着看定义。咱们从一个实际场景说起。

你写了一个搜索框,用户每输入一个字,你就得去后台查一次数据。怎么做?

传统写法:监听输入变化,然后手动调用接口。但这里有个坑——用户打字很快,你总不能每敲一个字母就发一次请求吧?你得做防抖、做线程切换、做结果合并……代码很快就变得又臭又长。

响应式编程,说白了就是换个思路:把数据当成一条流动的河,你只需要描述「当数据流过来时,我要做什么」。至于什么时候流、怎么流、流得快还是慢,这些细节交给框架去处理。

核心思想: 数据是流(Stream),观察者订阅流,流的变化自动通知观察者。

我个人习惯把响应式编程理解为「声明式的异步处理」。你不需要写一堆回调嵌套,只需要声明数据之间的转换关系。嗯,有点像函数式编程的味道。

1.2 Flow 解决了什么问题?

在 Flow 出现之前,Android 上做异步数据流处理,主要有两套方案:LiveData 和 RxJava。但它们各有各的痛点。

LiveData 的问题:

  • 只能在 Android 主线程上观察数据变化
  • 不支持线程切换,你得自己写协程或 AsyncTask
  • 操作符太少,想做 map、filter、debounce 这些基本操作?对不起,没有
  • 数据发射是粘性的,有时候会收到旧数据,容易引发 bug

RxJava 的问题:

  • 学习曲线陡峭,Observable、Flowable、Single、Completable……光类型就够你喝一壶
  • 操作符太多,而且命名不够直观(flatMap、switchMap、concatMap 有什么区别?我刚开始学的时候也懵)
  • 背压策略复杂,Flowable 和 Observable 的选择就是个坑
  • 与 Kotlin 协程的集成不够自然

Flow 就是冲着这些问题来的。它基于 Kotlin 协程,天生支持结构化并发。什么意思呢?就是你的数据流会随着协程的生命周期自动取消,再也不用担心内存泄漏了。

我的经验: 以前用 RxJava 做网络请求,最怕的就是订阅了忘记取消。后来用 Flow + ViewModelScope,这个问题彻底消失了。协程一取消,Flow 自动停,干净利落。

Flow 还解决了另一个痛点:冷流与热流的区分。冷流就是每次订阅都重新执行数据生产逻辑,热流则是多个订阅者共享同一个数据源。这个设计让 Flow 在 UI 层和数据层之间有了更清晰的边界。

1.3 Flow 与 LiveData 和 RxJava 的对比

咱们直接上表格,一目了然。

特性 LiveData RxJava Flow
学习曲线
线程切换 不支持(需手动) 支持(subscribeOn/observeOn) 支持(flowOn)
操作符丰富度 极少 极多 丰富(且持续增加)
背压支持 不支持 支持(Flowable) 支持(buffer/conflate)
生命周期感知 原生支持 需额外库 通过协程支持
与协程集成 部分支持 需适配 原生支持
冷流/热流 仅热流 两者都有 两者都有(StateFlow/SharedFlow)

你看,Flow 在功能上几乎覆盖了 RxJava 的绝大部分能力,但学习成本低得多。而且它跟 Kotlin 协程是「亲兄弟」,用起来特别顺手。

注意: 不是说 LiveData 就该被淘汰。如果你的场景很简单——比如只是观察一个数据库里的单条数据变化,LiveData 依然够用。但一旦涉及到复杂的异步数据流处理,Flow 的优势就体现出来了。

1.4 一个简单的 Flow 示例

光说不练假把式。咱们写个最简单的 Flow 感受一下。

// 创建一个 Flow,发射 1 到 3 的数字
fun simpleFlow(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100) // 模拟异步操作
        emit(i)    // 发射数据
    }
}

// 在 ViewModel 中收集
class MyViewModel : ViewModel() {
    fun startCollecting() {
        viewModelScope.launch {
            simpleFlow().collect { value ->
                Log.d("FlowDemo", "收到: $value")
            }
        }
    }
}

这段代码干了什么?

  • flow { } 构建了一个冷流,每次收集都会重新执行里面的代码
  • emit() 发射数据,collect() 接收数据
  • delay(100) 模拟耗时操作,但不会阻塞主线程
  • viewModelScope.launch 确保 Flow 在 ViewModel 销毁时自动取消

是不是很简洁?没有回调,没有订阅/取消订阅的烦恼。你想想看,如果用 LiveData 实现同样的效果,你得写多少代码?

避坑指南: 我曾经在项目里直接用 flow { } 做网络请求,结果发现每次配置变化(比如屏幕旋转)都会重新发起请求。后来才意识到,冷流就是这样的——每次收集都重新执行。解决方案是用 stateIn 把它转成热流,或者配合 repeatOnLifecycle 使用。

1.5 什么时候该用 Flow?

我个人的经验是,以下场景 Flow 特别合适:

  • 连续的数据流:比如定位更新、传感器数据、WebSocket 消息
  • 复杂的异步操作链:多个接口调用需要组合、合并、过滤
  • 需要线程切换的场景:Room 数据库查询、网络请求、文件读写
  • UI 状态管理:用 StateFlow 替代 LiveData,配合 Jetpack Compose 效果拔群

但也不是所有地方都要用 Flow。比如一个简单的按钮点击事件,用回调就够了,没必要上 Flow。记住:工具是为人服务的,别为了用而用

1.6 本章小结

这一章我们聊了:

  • 响应式编程的本质:数据流 + 声明式处理
  • Flow 解决了 LiveData 操作符不足和 RxJava 学习成本高的问题
  • Flow 与 LiveData、RxJava 的对比,各有优劣,但 Flow 在 Kotlin 生态中优势明显
  • 一个简单的 Flow 示例,感受它的简洁与强大

下一章,我们会深入 Flow 的核心概念——冷流与热流。这是理解 Flow 的关键,也是很多初学者容易搞混的地方。到时候我会用实际项目中的例子,帮你彻底搞明白。

好,今天就到这里。如果你对 Flow 有任何疑问,欢迎留言讨论。咱们下章见。


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