2、Flow基础:Flow的创建、Flow的发射与收集、冷流与热流的概念
好,咱们正式开始聊 Flow。说实话,我第一次接触 Kotlin Flow 的时候,脑子里全是 RxJava 的影子。但用了一段时间后,我发现 Flow 更「Kotlin」——它跟协程是天生一对。今天这一章,我会带你从零开始,把 Flow 的创建、发射、收集,以及冷流和热流这两个核心概念彻底搞明白。
2.1 Flow 是什么?
简单说,Flow 就是一个异步数据流。它可以按顺序发射多个值,而不是像 suspend 函数那样只返回一个结果。你想想看,在 Android 开发里,我们经常需要监听数据库变化、网络状态、用户输入……这些场景都是「多个值随时间到达」,Flow 就是为这个而生的。
核心要点:Flow 是冷流,默认情况下它不干活,只有被收集时才开始发射数据。
2.2 创建 Flow
创建 Flow 最常用的方式就是使用 flow { } 构建器。我在项目中几乎每天都在用这个。
val myFlow: Flow<Int> = flow {
// 发射数据
emit(1)
emit(2)
emit(3)
}
嗯,这里要注意:emit() 是一个挂起函数,它只能在 Flow 的构建器内部调用。你可能会问,为什么不能直接返回一个 List?因为 Flow 是「懒」的,它支持背压,而且可以异步发射。
除了 flow { },还有几种常见的创建方式:
| 方式 | 说明 | 示例 |
|---|---|---|
| flowOf | 发射固定值 | flowOf(1, 2, 3) |
| asFlow | 集合/序列转 Flow | listOf(1,2,3).asFlow() |
| callbackFlow | 回调转 Flow | 用于监听器、传感器等 |
我个人习惯用 flow { },因为它最灵活。比如在数据库查询时,我可以这样写:
fun getUserFlow(): Flow<User> = flow {
val user = userDao.getUser() // 挂起函数
emit(user)
}
2.3 发射与收集
发射用 emit(),收集用 collect()。这俩是 Flow 最核心的操作。
// 收集数据
myFlow.collect { value ->
println("收到: $value")
}
收集是一个终端操作,它会挂起当前协程,直到 Flow 完成。我在项目中遇到过一个问题:如果在 Activity 里直接 collect,忘记取消,就会导致内存泄漏。所以一定要配合 lifecycleScope 或者 repeatOnLifecycle 来使用。
避坑指南:我曾经在 Fragment 里直接 collect 一个 Flow,结果页面销毁了还在发射,导致崩溃。后来我改用 viewLifecycleOwner.lifecycleScope.launch 配合 repeatOnLifecycle,问题就解决了。
Flow 的发射是顺序执行的。什么意思?就是 emit(1) 之后,必须等 collect 处理完 1,才会发射 2。这跟 RxJava 的背压有点像,但 Flow 更简洁。
2.4 冷流与热流
这是 Flow 里最容易混淆的概念。我刚开始学的时候也绕了很久。说白了:
- 冷流:没有收集者就不干活。每个收集者都会从头开始接收数据。
- 热流:不管有没有收集者,数据都在发射。新来的收集者只能收到之后的数据。
咱们用代码感受一下:
// 冷流示例
val coldFlow = flow {
println("Flow 开始工作")
emit("A")
emit("B")
}
// 第一次收集
coldFlow.collect { println("收集者1: $it") }
// 第二次收集
coldFlow.collect { println("收集者2: $it") }
// 输出:
// Flow 开始工作
// 收集者1: A
// 收集者1: B
// Flow 开始工作 <-- 又执行了一次!
// 收集者2: A
// 收集者2: B
看到了吗?每次 collect 都会重新执行 flow 构建器。这就是冷流的典型特征。
那热流呢?Kotlin 提供了 StateFlow 和 SharedFlow。它们是有状态的,数据在多个收集者之间共享。
// 热流示例 - SharedFlow
val sharedFlow = MutableSharedFlow<String>()
// 发射数据(不依赖收集者)
scope.launch {
sharedFlow.emit("X")
sharedFlow.emit("Y")
}
// 收集者只能收到之后的数据
scope.launch {
sharedFlow.collect { println("收到: $it") }
}
我的经验:在 Android 里,StateFlow 最适合保存 UI 状态,比如 ViewModel 里的数据。SharedFlow 适合事件通知,比如一次性提示。我一般用 StateFlow 做状态管理,SharedFlow 做事件总线。
2.5 知识体系图
下面这张图帮你理清 Flow 的基础脉络:
2.6 实际项目中的选择
说了这么多,到底什么时候用冷流,什么时候用热流?我总结了一个简单的原则:
- 数据是一次性查询结果:用冷流。比如从数据库查一条记录,每次 collect 都重新查。
- 数据是持续变化的:用热流。比如用户登录状态、位置更新、通知计数。
- 需要多个观察者共享同一份数据:用热流。比如多个 Fragment 监听同一个 ViewModel 的状态。
记住:冷流是「按需生产」,热流是「广播通知」。选对类型,你的代码会干净很多。
好了,这一章的内容就到这里。Flow 的基础其实不复杂,关键是理解冷热流的区别。下一章我们会深入操作符,到时候你会发现 Flow 的真正威力。