Flow 基础概念与生命周期归纳总结
说实话,我刚开始接触 Kotlin Flow 的时候,第一反应是:「这不就是 RxJava 换了个马甲吗?」后来踩了几个坑才明白,Flow 在设计上比 RxJava 更懂 Android 开发者的痛点——尤其是生命周期管理。
这一章我们先打基础。把 Flow 到底是什么、它的生命周期长什么样说清楚。后面所有泄漏排查的技巧,都基于这些基础概念。
Flow 是什么?
Flow 是 Kotlin 协程库提供的一种冷数据流(Cold Stream)。说白了,它就像一根水管:有人打开水龙头(收集)时,水才开始流;没人接水,水管就是空的,不会浪费一滴水。
我个人习惯把 Flow 理解成一个「带协程的生产者-消费者模型」:
- 生产者:emit 发出数据
- 消费者:collect 收集数据
- 协程作用域:决定了这条水管什么时候该关
我在项目中遇到过有人把 Flow 当成 LiveData 的替代品,直接往 globalScope 里塞。结果页面退出了,后台还在 emit,最终内存泄漏。切记:Flow 不是 LiveData,它没有自带的生命周期感知能力。
Flow 的两种基本类型
| 类型 | 特点 | 典型场景 |
|---|---|---|
| 冷流(Cold Flow) | 每个 collect 都独立执行 producer,无共享 | 网络请求、数据库查一次 |
| 热流(Hot Flow) | 多个 collect 共享生产者,独立消费 | 状态管理、事件总线 |
为什么这个区分对 Fragment 里的泄漏排查很重要?你想想看:热流如果被多个 Fragment 实例共享,而其中一个 Fragment 销毁时没取消收集,整个热流的生产者还会继续往这个已销毁的 Fragment 里传数据——这不就泄漏了么?
Flow 的生命周期三要素
要避免泄漏,首先得搞清楚 Flow 的生命周期到底受什么控制。我把它总结为三个要素:
- 协程作用域(CoroutineScope)——Flow 的收集必须跑在一个作用域里,作用域取消,收集就停止。
- 终端操作(Terminal Operator)——比如 collect、toList、first,这些触发了实际的订阅。
- 协程上下文(CoroutineContext)——Dispatchers 决定了生产者跑在哪个线程,但生命周期本身不受线程影响。
核心公式:Flow 的生命周期 = 收集它的协程作用域的生命周期
换句话说:你在哪个 scope 里 collect,Flow 就在哪个 scope 里活。
Fragment 里常见的 Flow 订阅模式
我排查过的泄漏案例里,90% 都源自下面这种「无意识」的订阅:
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ❌ 典型错误:使用 viewLifecycleOwner.lifecycleScope 却忘了管理
viewLifecycleOwner.lifecycleScope.launch {
viewModel.someFlow.collect { value ->
textView.text = value
}
}
}
}
这段代码的问题在哪?lifecycleScope 确实会在 Fragment 的 onDestroy 时取消,但如果你用的是 Lifecycle.State.STARTED 以上的收集(比如 repeatOnLifecycle),并且 Flow 是热流,就有可能出问题。
我建议:在 Fragment 里订阅 Flow,永远使用 repeatOnLifecycle。这是官方推荐的写法:
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ✅ 正确:生命周期感知,自动重启
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.someFlow.collect { value ->
textView.text = value
}
}
}
}
}
⚠️ 注意:我曾经见过团队把 repeatOnLifecycle 和 Flow.flowWithLifecycle 混用导致重复订阅。简单记:repeatOnLifecycle 作用于收集的 外部协程作用域,flowWithLifecycle 是给 Flow 本身加生命周期限定符。建议只用一个模式,全团队统一。
Flow 生命周期与 Fragment 生命周期的映射
把两者放在一起看,就一目了然了:
你需要在 Fragment 里记住的生命周期关键点
- onCreate → onDestroy:这是 Fragment 实例的生命周期。在这期间创建或绑定的协程,必须在 onDestroy 时取消,否则引用泄露。
- onStart → onStop:这是与用户交互的生命周期。Flow 收集最好只在这段时间里活跃,因为 UI 更新只有在可见时才有意义。
- onResume → onPause:如果 ui 对实时性要求高(比如倒计时),才需要在 resumed 时收集。
我个人习惯在项目里定一条铁律:所有 Fragment 里的 Flow 订阅,必须绑定到 viewLifecycleOwner.lifecycleScope 并且使用 repeatOnLifecycle(STARTED)。这样即使有热流、有共享,至少不会因为 Fragment 销毁而泄漏。
总结
Flow 是冷的,但它的收集行为是由协程作用域决定的。Fragment 的生命周期天然会销毁协程作用域,关键是你要正确地绑定它们。