大家在学习 Kotlin Flow 的时候经常会遇到两个类StateFlow SharedFlow很多教程都会告诉你StateFlow 用于状态 SharedFlow 用于事件但问题来了为什么 Google 不直接设计一个 Flow为什么非要拆成两套说实话 以前我也只是会用直到最近项目里踩了一个坑tryEmit() 返回 false然后一路排查下去最后竟然牵出了SharedFlow StateFlow replay buffer Lifecycle WindowLeaked最终让我真正理解了Google 为什么要设计两套 Flow。一、一个奇怪的问题项目中有这样一个事件流private val _tenantIdEvent MutableSharedFlowInt() val tenantIdEvent _tenantIdEvent.asSharedFlow()获取租户失败时_tenantIdEvent.tryEmit(0)结果日志打印false更离谱的是UI 明明已经开始监听mViewModel.tenantIdEvent.collect { ... }按理说有人收 为什么发不出去二、我最开始的理解是错的很多人包括我会天然认为collect了 事件一定能收到实际上collect存在 ≠ tryEmit一定成功这才是问题的根源。三、SharedFlow 到底是什么先看看这段代码MutableSharedFlowInt()很多人以为创建了一个事件流实际上它等价于MutableSharedFlowInt( replay 0, extraBufferCapacity 0 )即无缓存 无重放四、emit 与 tryEmit 的本质区别这是本次踩坑最大的收获之一。emitemit(value)特点保证发送 必要时等待可以理解为我必须把快递送到你手里如果对方没准备好我等tryEmittryEmit(value)特点尝试发送 绝不等待 可能失败可以理解为我敲一下门 能接收就接收 接不了我就走所以tryEmit返回false 不是异常 而是发送失败五、为什么 collect 了还会失败因为collect存在 ≠ 当前时刻能立即消费而tryEmit()又不愿意等待。所以无缓冲SharedFlow tryEmit 极容易返回false这也是很多人第一次接触 SharedFlow 时最容易踩的坑。六、replay 与 buffer 到底有什么区别很多人学 SharedFlow最容易混淆两个参数replay extraBufferCapacityreplay作用给未来的新订阅者看例如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 Eventprivate val _event MutableSharedFlowEvent( replay 0, extraBufferCapacity 1 )原因不粘性 不容易丢事件非常适合Toast Error Navigation LoginSuccess十三、又踩了一个坑WindowLeaked就在以为问题结束的时候项目又报了WindowLeaked日志显示Activity已经finish Dialog还活着十四、真正的问题并不是 Dialog最开始以为Dialog有Bug后来发现真正的问题是顺序。错误顺序请求完成 ↓ successBlock ↓ finish() ↓ loadingfalse ↓ dismissDialog这时候Activity已经销毁 Dialog还没关闭直接WindowLeaked十五、正确顺序应该是请求完成 ↓ loadingfalse ↓ dismissDialog ↓ successBlock ↓ finish()这本质上是Lifecycle问题而不是Flow问题十六、Flow 背后真正的设计思想到这里我终于明白了Google 其实是在引导开发者建立三个模型。State状态解决现在是什么例如loading userInfo pageState对应StateFlowEvent事件解决刚刚发生了什么例如Toast Navigation ErrorEvent对应SharedFlowLifecycle生命周期解决页面是否还活着例如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而是在学习如何正确地表达状态与事件。
StateFlow 与 SharedFlow:Google 为什么要设计两套 Flow?—— 从一次 tryEmit(false) 到 WindowLeaked,彻底理解 Flow 的设计思想
发布时间:2026/6/4 9:39:50
大家在学习 Kotlin Flow 的时候经常会遇到两个类StateFlow SharedFlow很多教程都会告诉你StateFlow 用于状态 SharedFlow 用于事件但问题来了为什么 Google 不直接设计一个 Flow为什么非要拆成两套说实话 以前我也只是会用直到最近项目里踩了一个坑tryEmit() 返回 false然后一路排查下去最后竟然牵出了SharedFlow StateFlow replay buffer Lifecycle WindowLeaked最终让我真正理解了Google 为什么要设计两套 Flow。一、一个奇怪的问题项目中有这样一个事件流private val _tenantIdEvent MutableSharedFlowInt() val tenantIdEvent _tenantIdEvent.asSharedFlow()获取租户失败时_tenantIdEvent.tryEmit(0)结果日志打印false更离谱的是UI 明明已经开始监听mViewModel.tenantIdEvent.collect { ... }按理说有人收 为什么发不出去二、我最开始的理解是错的很多人包括我会天然认为collect了 事件一定能收到实际上collect存在 ≠ tryEmit一定成功这才是问题的根源。三、SharedFlow 到底是什么先看看这段代码MutableSharedFlowInt()很多人以为创建了一个事件流实际上它等价于MutableSharedFlowInt( replay 0, extraBufferCapacity 0 )即无缓存 无重放四、emit 与 tryEmit 的本质区别这是本次踩坑最大的收获之一。emitemit(value)特点保证发送 必要时等待可以理解为我必须把快递送到你手里如果对方没准备好我等tryEmittryEmit(value)特点尝试发送 绝不等待 可能失败可以理解为我敲一下门 能接收就接收 接不了我就走所以tryEmit返回false 不是异常 而是发送失败五、为什么 collect 了还会失败因为collect存在 ≠ 当前时刻能立即消费而tryEmit()又不愿意等待。所以无缓冲SharedFlow tryEmit 极容易返回false这也是很多人第一次接触 SharedFlow 时最容易踩的坑。六、replay 与 buffer 到底有什么区别很多人学 SharedFlow最容易混淆两个参数replay extraBufferCapacityreplay作用给未来的新订阅者看例如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 Eventprivate val _event MutableSharedFlowEvent( replay 0, extraBufferCapacity 1 )原因不粘性 不容易丢事件非常适合Toast Error Navigation LoginSuccess十三、又踩了一个坑WindowLeaked就在以为问题结束的时候项目又报了WindowLeaked日志显示Activity已经finish Dialog还活着十四、真正的问题并不是 Dialog最开始以为Dialog有Bug后来发现真正的问题是顺序。错误顺序请求完成 ↓ successBlock ↓ finish() ↓ loadingfalse ↓ dismissDialog这时候Activity已经销毁 Dialog还没关闭直接WindowLeaked十五、正确顺序应该是请求完成 ↓ loadingfalse ↓ dismissDialog ↓ successBlock ↓ finish()这本质上是Lifecycle问题而不是Flow问题十六、Flow 背后真正的设计思想到这里我终于明白了Google 其实是在引导开发者建立三个模型。State状态解决现在是什么例如loading userInfo pageState对应StateFlowEvent事件解决刚刚发生了什么例如Toast Navigation ErrorEvent对应SharedFlowLifecycle生命周期解决页面是否还活着例如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而是在学习如何正确地表达状态与事件。