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

StateFlow 与 SharedFlow:Google 为什么要设计两套 Flow?—— 从一次 tryEmit(false) 到 WindowLeaked,彻底理解 Flow 的设计思想

大家在学习 Kotlin Flow 的时候,经常会遇到两个类:

StateFlow SharedFlow

很多教程都会告诉你:

StateFlow 用于状态 SharedFlow 用于事件

但问题来了:

为什么 Google 不直接设计一个 Flow?

为什么非要拆成两套?

说实话 以前我也只是:会用

直到最近项目里踩了一个坑:

tryEmit() 返回 false

然后一路排查下去,

最后竟然牵出了:

SharedFlow StateFlow replay buffer Lifecycle WindowLeaked

最终让我真正理解了:

Google 为什么要设计两套 Flow。


一、一个奇怪的问题

项目中有这样一个事件流:

private val _tenantIdEvent = MutableSharedFlow<Int>() val tenantIdEvent = _tenantIdEvent.asSharedFlow()

获取租户失败时:

_tenantIdEvent.tryEmit(0)

结果日志打印:

false

更离谱的是:

UI 明明已经开始监听:

mViewModel.tenantIdEvent.collect { ... }

按理说:

有人收 为什么发不出去?

二、我最开始的理解是错的

很多人(包括我)会天然认为:

collect了 = 事件一定能收到

实际上:

collect存在 ≠ tryEmit一定成功

这才是问题的根源。


三、SharedFlow 到底是什么

先看看这段代码:

MutableSharedFlow<Int>()

很多人以为:

创建了一个事件流

实际上它等价于:

MutableSharedFlow<Int>( replay = 0, extraBufferCapacity = 0 )

即:

无缓存 无重放

四、emit 与 tryEmit 的本质区别

这是本次踩坑最大的收获之一。


emit

emit(value)

特点:

保证发送 必要时等待

可以理解为:

我必须把快递送到你手里

如果对方没准备好:

我等

tryEmit

tryEmit(value)

特点:

尝试发送 绝不等待 可能失败

可以理解为:

我敲一下门 能接收就接收 接不了我就走

所以:

tryEmit返回false 不是异常 而是发送失败

五、为什么 collect 了还会失败?

因为:

collect存在 ≠ 当前时刻能立即消费

而:

tryEmit()

又不愿意等待。

所以:

无缓冲SharedFlow + tryEmit = 极容易返回false

这也是很多人第一次接触 SharedFlow 时最容易踩的坑。


六、replay 与 buffer 到底有什么区别

很多人学 SharedFlow,

最容易混淆两个参数:

replay extraBufferCapacity

replay

作用:

给未来的新订阅者看

例如:

replay = 1

表示:

保存最近一次数据

新的 collector 进入时:

自动收到最近一次数据

因此:

replay决定粘性

extraBufferCapacity

作用:

给当前事件排队

例如:

extraBufferCapacity = 1

表示:

当前没人接 先暂存一下

因此:

buffer决定是否容易丢事件

七、我终于理解了 StateFlow

到这里我突然意识到:

Google 设计:

StateFlow SharedFlow

根本不是提供两个 API。

而是在解决两类完全不同的问题。


八、StateFlow 解决什么问题?

StateFlow 解决的是:

现在是什么状态?

例如:

loading pageState userInfo networkState

代码:

private val _isLoading = MutableStateFlow(false)

特点:

永远有当前值 永远保存最新状态

所以:

后来进入页面的人 也应该知道当前状态

九、为什么 StateFlow 天生有“粘性”?

因为:

状态本来就应该被记住

例如:

_isLoading.value = true

即使:

UI稍后才开始collect

仍然能够收到:

true

因为:

StateFlow保存的是状态

而不是事件。


十、SharedFlow 解决什么问题?

SharedFlow 解决的是:

刚刚发生了什么?

例如:

Toast Navigation ErrorEvent LoginSuccessEvent

这些东西本质都是:

一次性事件

例如:

登录成功

只发生一次。

不应该:

页面重建后再执行一次

十一、为什么 SharedFlow 默认不粘性?

想象一下:

Toast

如果重放:

旋转屏幕后 又弹一次

显然不合理。

再比如:

跳转页面

如果重放:

重新进入页面 又跳一次

直接出事故。

所以:

SharedFlow默认不粘性

这是设计使然。


十二、企业项目中的推荐配置

对于 UI Event:

private val _event = MutableSharedFlow<Event>( replay = 0, extraBufferCapacity = 1 )

原因:

不粘性 不容易丢事件

非常适合:

Toast Error Navigation LoginSuccess

十三、又踩了一个坑:WindowLeaked

就在以为问题结束的时候,

项目又报了:

WindowLeaked

日志显示:

Activity已经finish Dialog还活着

十四、真正的问题并不是 Dialog

最开始以为:

Dialog有Bug

后来发现:

真正的问题是顺序。

错误顺序:

请求完成 ↓ successBlock ↓ finish() ↓ loading=false ↓ dismissDialog

这时候:

Activity已经销毁 Dialog还没关闭

直接:

WindowLeaked

十五、正确顺序

应该是:

请求完成 ↓ loading=false ↓ dismissDialog ↓ successBlock ↓ finish()

这本质上是:

Lifecycle问题

而不是:

Flow问题

十六、Flow 背后真正的设计思想

到这里我终于明白了:

Google 其实是在引导开发者建立三个模型。


State(状态)

解决:

现在是什么

例如:

loading userInfo pageState

对应:

StateFlow

Event(事件)

解决:

刚刚发生了什么

例如:

Toast Navigation ErrorEvent

对应:

SharedFlow

Lifecycle(生命周期)

解决:

页面是否还活着

例如:

Dialog Activity Fragment

十七、总结

StateFlow

负责:

状态

例如:

loading pageState userInfo

特点:

有当前值 允许粘性 状态模型

SharedFlow

负责:

事件

例如:

Toast Navigation ErrorEvent LoginSuccessEvent

推荐:

MutableSharedFlow( replay = 0, extraBufferCapacity = 1 )

特点:

一次性事件 默认不粘性 事件模型

结语

以前我以为:

StateFlow 和 SharedFlow 只是两个不同的 API。

直到一次:

tryEmit(false) WindowLeaked

的排查过程,我才意识到:

Google 设计的从来不是两种 Flow。

而是在引导开发者区分:

  • 状态(State)
  • 事件(Event)
  • 生命周期(Lifecycle)

真正理解这三个模型,才算真正理解 Kotlin Flow。

tips:回顾思考:

以前我一直觉得 LiveData 已经够用了,StateFlow 和 SharedFlow 只是换了个 API。

直到这次踩坑,我才发现:

LiveData 更像是一个“观察数据变化”的工具。

而 Flow 则是在引导开发者建立State(状态)Event(事件)Lifecycle(生命周期)三种不同的思维模型。

真正理解 StateFlow 与 SharedFlow,并不是学会两个 API,而是在学习如何正确地表达状态与事件。

http://www.jsqmd.com/news/911735/

相关文章:

  • 面试官的提问与燕双非的回答:Java 技术栈在电商场景中的应用
  • 基于Arduino与MPU6050的模型火箭智能降落伞释放系统全解析
  • Arduino驱动RGB灯带:MOSFET选型、PWM调光与平滑色彩过渡实战
  • Aspose.Words for Java 实战:Word转PDF页码对不上?手把手教你排查和修复
  • 告别Eclipse插件!用Maven插件antlr4-maven-plugin搞定语法解析代码生成(附JDK8/11兼容方案)
  • 2026年5月最新|杭州全屋定制哪家好?本地源头工厂盘点,高性价比品牌选购指南 - 商业新知
  • Lindy财务自动化黄金窗口期仅剩47天:财政部新规倒逼Q3前完成自动化凭证链审计留痕
  • 基于ESP32与Node.js的物联网智能时钟:从架构设计到FreeRTOS任务调度
  • 终极指南:如何免费快速解码QQ音乐加密文件(qmcdump完整教程)
  • 别再手动调坐标了!OpenPnP导入Gerber/坐标文件后,用这3个Mark点搞定全板自动校正
  • Wallpaper Engine下载器:3步轻松获取Steam创意工坊动态壁纸的完整指南
  • 从PFD到VCO:手把手教你用TSMC 0.18um工艺仿真一个1.5GHz的电荷泵锁相环
  • Agent Skills 万千应用 · 第14篇_论文追踪 Skill:自动关注新论文,把资料变成判断
  • 高校学生选课系统原型设计
  • Aspose.Cells企业级应用实战:从License机制解析到合规批量处理方案设计
  • 构建安全合规的大规模健康研究平台:FAIR原则与隐私计算实践
  • 2026 海南注册公司营业执照代办排名:资质、速度、口碑全方位测评 - 企业推荐官【官方】
  • 告别CycleGAN循环一致性:用CUT的对比学习实现更自由的图像风格迁移(附PyTorch代码调试心得)
  • 别再乱并电容了!从MCU电源脚到DC-DC,手把手教你选对104和10uF(附实战案例)
  • 零基础入门网页开发:HTML与CSS核心概念与实践指南
  • 构建可信机器学习算法:从可解释性、公平性到鲁棒性的工程实践
  • 告别iOS开发噩梦:如何用Xcode开发者磁盘映像解决版本不匹配问题
  • 从知网到Word:文献管理小白用NoteExpress三步完成参考文献自动排版(以XX大学版为例)
  • 低资源多模态内容审核实战:CLIP+BGE-M3融合与动态门控机制解析
  • 从散乱收藏到秒级检索:技术写作素材管理实践
  • 2026 年联盟营销的 5 大关键变化:为什么“专属联盟”正在取代平台型分销?
  • 手把手教你用Redriver芯片搞定USB4/PCIe Gen4信号衰减问题(附电路设计要点)
  • 学术写作中文献引用的规范与实践:从原理到工具全解析
  • 从零打造复古智能手表:ESP32-S3与HCMS-2971的硬件开发全记录
  • ADI DSP开发者论坛实战:如何高效搜索SC589问题与获取官方支持(附中文关键词)