当前位置: 首页 > news >正文

第一篇:为什么多个 Flow collect 必须 launch?——一篇讲透 Android 协程生命周期

引言

很多 Android 开发在刚接触 Kotlin 协程 + Flow 时,都会写出类似代码:

lifecycleScope.launch { mViewModel.loadingFlow.collect { // 更新 loading } mViewModel.errorFlow.collect { // 处理错误 } }

然后发现:第二个 collect 根本不执行。

很多人第一反应:

是不是协程有问题? 是不是 Flow 有 bug?

实际上:

这恰恰说明你还没有真正理解:

  • collect 本质
  • Flow 生命周期
  • 协程结构化并发
  • lifecycleScope
  • repeatOnLifecycle

今天这篇文章 我们就从工程视角,真正讲透:

Android 中 Flow 为什么不能乱 collect。


一、collect 本质到底是什么?

很多人误以为:

collect {}

类似:

list.forEach {}

执行完就结束。实际上:

collect 通常是长期挂起任务

例如:

mViewModel.loadingFlow.collect { isLoading -> }

本质是:

一直监听 loadingFlow

只要数据变化
就回调 collect

也就是说:

collect 默认不会自动结束。

它会一直挂起等待新数据。


二、为什么第二个 collect 不执行?

来看错误示例:

lifecycleScope.launch { mViewModel.loadingFlow.collect { Log.d("TAG", "loading") } mViewModel.errorFlow.collect { Log.d("TAG", "error") } }

问题就在这里:

loadingFlow.collect {}

一直挂起

所以:后面的代码根本执行不到。

即:

第一个 collect 永远不结束 ↓ 第二个 collect 永远没机会执行

三、正确写法:多个 collect 必须 launch

正确写法:

lifecycleScope.launch { launch { mViewModel.loadingFlow.collect { Log.d("TAG", "loading") } } launch { mViewModel.errorFlow.collect { Log.d("TAG", "error") } } }

为什么这样可以?

因为:

launch {}

会创建:子协程。

结构变成:

父协程 ├── 子协程A:collect loading └── 子协程B:collect error

这样:

两个 collect 并发执行 互不阻塞

四、lifecycleScope 到底是什么?

很多人觉得:

lifecycleScope.launch {}

只是:

开个协程

其实不是。

lifecycleScope 本身就是 CoroutineScope。

它内部已经绑定:

  • Lifecycle
  • Job
  • Main Dispatcher

所以:

lifecycleScope.launch {}

本质是:

在 Activity 生命周期范围内 创建一个子协程

五、lifecycleScope 到底解决了什么?

核心:

Activity 销毁时自动 cancel 协程。

即:

Activity finish ↓ lifecycleScope cancel ↓ 所有子协程 cancel

因此:

lifecycleScope.launch { }

不用担心:

页面销毁后协程泄漏

六、但 lifecycleScope 不解决一个问题

很多人以为:

lifecycleScope.launch {}

已经彻底解决生命周期问题。

实际上:不是。

因为:

页面进入后台时 Activity 不一定销毁

例如:

  • 按 Home 键
  • 打开别的页面
  • 锁屏

此时:

lifecycleScope 还活着 collect 还在继续

这就会导致:

  • 后台继续收集数据
  • 后台继续更新 UI
  • 后台继续弹 Toast
  • 后台继续执行逻辑

显然不合理。


七、repeatOnLifecycle 真正解决了什么?

正确写法:

lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { mViewModel.loadingFlow.collect { } } } }

它的核心:

页面可见时开始 collect。

页面不可见时停止 collect。

即:

进入前台 ↓ 开始收集 进入后台 ↓ 停止收集 再次回到前台 ↓ 重新收集

八、企业级推荐写法

很多人会这样写:

lifecycleScope.launch { repeatOnLifecycle { collect A } } lifecycleScope.launch { repeatOnLifecycle { collect B } }

其实:不是错。

完全能跑。

但问题是:

生命周期入口过多 结构分散 维护困难

九、真正推荐的结构化写法

推荐:

private fun observeUiState() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { mViewModel.loadingFlow.collect { isLoading -> } } launch { mViewModel.errorFlow.collect { error -> } } launch { mViewModel.loginFlow.collect { user -> } } } } }

结构变成:

lifecycleScope └── ObserverRoot ├── loadingFlow ├── errorFlow └── loginFlow

优势:

  • 生命周期入口统一
  • 所有 UI 状态统一管理
  • 更符合结构化并发思想
  • 更适合大型项目维护

十、协程真正核心:结构化并发

很多人觉得:

launch {}

只是:

开线程

实际上:

Kotlin 协程核心是:Structured Concurrency(结构化并发)

即:

所有协程都应该有父协程。

因此:

launch {}

本质是:

在当前 CoroutineScope 下 创建一个子协程节点

所以:

lifecycleScope.launch { launch {} launch {} }

本质结构:

lifecycleScope └── Parent ├── ChildA └── ChildB

十一、Job 是什么?

val job = launch { }

返回:

Job

本质:协程控制器

可以:

job.cancel()

取消协程。


十二、为什么 collect 可以 cancel?

因为:

Flow collect 是可取消挂起函数。

当:

job.cancel()

时:

collect 收到取消信号 ↓ 停止监听 ↓ 协程结束

十三、最后总结

这一轮真正要理解的核心:

协程不是“开线程”。

而是:

在协程树中 创建一个受生命周期与父 Job 管理的任务节点。

而:

Flow collect

本质是:

长期挂起监听任务。

所以:

多个 collect 必须并发 launch。

最终企业级推荐写法:

lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { mViewModel.loadingFlow.collect { } } launch { mViewModel.errorFlow.collect { } } launch { mViewModel.loginFlow.collect { } } } }

下篇预告

下一篇我们继续:

《StateFlow 与 SharedFlow 到底怎么选?——一篇讲透 Android 热流设计》

深入讲透:

  • StateFlow 为什么必须有初始值
  • SharedFlow 为什么适合事件
  • Toast 为什么不能用 StateFlow
  • 页面旋转为什么会重复消费
  • replay 到底是什么
  • emit 与 tryEmit 的真正区别
  • 热流与冷流本质区别
  • 企业级 UI 状态设计方案
http://www.jsqmd.com/news/899854/

相关文章:

  • SRT除法器性能优化:Skip-Zero策略的原理、实现与Chisel实践
  • 迭代扰动粒子滤波:突破重采样瓶颈,实现并行化贝叶斯状态估计
  • AIBOX-1684X系统固件升级入门教程
  • ChatGPT产品描述生成失效真相(90%团队踩中的5个认知陷阱)
  • 哪家发动机缸盖工厂专业?2026年5月推荐TOP5对比砂眼控制评测适用场景特点 - 品牌推荐
  • 2026年南宁钢塑管供应市场深度解析:聚焦广西水之龙建材有限公司 - 2026年企业资讯
  • 如何用Python命令行工具突破百度网盘下载限速:完整实战指南
  • 高光谱与农业(一)从叶片光谱到作物表型:漫反射的测量挑战与早期探索
  • ngx_http_request_finalizer
  • 移动端开发:React Native跨平台实战
  • Azure云服务智能工具与数据库定价优化实战指南
  • 2026年5月AGV叉车厂家推荐:十大排名专业评测性价比高价格注意事项 - 品牌推荐
  • ASP 简介
  • 多速率信号处理源码深度剖析
  • CAPL脚本自动化测试进阶 ———— 活用Test Step函数提升测试报告可读性与精准度
  • 2026年北京鸿博志远教育深度解析:军队文职培训赛道竞争加剧与用户选择痛点 - 品牌推荐
  • LeetCode 189 · 轮转数组:三次翻转,原地搞定的神仙操作
  • 2026年论文怎么降低AI率?学长教你3招免费降AI,亲测5款AIGC降重工具 - 降AI实验室
  • 软件定义汽车安全新范式:SHIFTGUARD任务迁移技术深度解析
  • 数据库技术:Redis缓存与分布式锁
  • CUDA编程:Shared Memory Bank Conflict 与 Padding 优化
  • 为内部知识库问答系统接入Taotoken提供多模型后备支持
  • 2026年 工业热电偶十大品牌推荐榜单:铠装/K型/装配式/手持式/铂铑热电偶源头厂家与高精度测温方案深度解析 - 品牌企业推荐师(官方)
  • 终极免费文档下载脚本指南:如何一键获取百度文库等30+平台资源
  • 从数据手册到实战:剖析74HC4052模拟开关的选型与电路设计
  • 2026年 背景板/气球/桁架/注水旗租赁服务排行榜:快展搭建与舞台活动的专业口碑精选 - 品牌企业推荐师(官方)
  • 如何用Python自动化COMSOL仿真:MPh完整指南
  • 技术写作:如何写出高质量技术文章
  • 使用taotoken聚合api为个人项目构建智能问答助手
  • 融合聚焦深度与单目深度估计:测试时优化提升度量深度精度