从flow+collectLatest改为SharedFlow的生效原因及原理解析
原问题与旧代码问题分析
之前使用普通Flow搭配collectLatest时,数据无法被及时收集,函数返回空列表。尝试用async-await解决,但会阻塞主线程,效率低下。
旧代码示例
suspend fun getList(): List<Items> { CoroutineScope(Dispatchers.Main).launch { async { flow.collectLatest { myItems = it } }.await() } return myItems }
这段代码的核心问题:
- 在
suspend函数内手动创建CoroutineScope并调用launch,新协程和外部函数的协程上下文完全分离。await仅等待内部async完成,但collectLatest是持续收集操作,外部函数不会等待收集完成就直接返回myItems,大概率此时数据还未被收集,所以返回空列表。 - 即便
async-await能让函数等待数据收集,本质也是通过阻塞主线程实现的,违背了协程非阻塞的设计初衷,效率极低。
当前SharedFlow方案解析
改用SharedFlow后问题解决,核心代码如下:
当前代码示例
suspend fun getList(): List<Items> { val sharedFlow = flow.conflate().shareIn( coroutineScopeIO, replay = 1, started = SharingStarted.WhileSubscribed() ) return sharedFlow.first() }
针对你提出的疑问逐一解答:
1. conflate是否会创建单独协程发射数据?
根据官方定义翻译:
通过合并通道合并流的发射,并在单独的协程中运行收集器。这样做的效果是,发射方不会因为收集器缓慢而挂起,而收集器总能获取到最新的发射值。
conflate的作用是让收集器运行在单独协程,而非发射方。流的发射协程由shareIn指定的coroutineScopeIO管理,和收集器协程相互独立,避免发射方被慢收集器阻塞。
2. shareIn().first()是否能达到async-await的效果但不阻塞主线程?
是的。first()是挂起函数,它会挂起当前协程(即getList所在的协程),直到SharedFlow发射第一个值(或因replay=1保留的最新值)后恢复协程并返回该值。
- 和
async-await不同,它不会启动新协程等待,只是暂停当前协程执行,主线程可继续处理其他任务,完全是非阻塞的,效率远高于阻塞主线程的async-await方案。
3. SharingStarted.WhileSubscribed()的作用是什么?
SharingStarted.WhileSubscribed()表示:
- 只有当存在活跃订阅者时,才启动流的发射逻辑;
- 当所有订阅者都取消订阅后,会在默认超时(5秒)后停止流的发射,避免无意义的资源消耗。
简单来说,就是仅在有订阅时才启动数据发射,没有订阅时流处于休眠状态,更节省资源。
为什么SharedFlow能解决问题?
- SharedFlow的共享特性:
shareIn将普通Flow转为SharedFlow,支持多订阅者共享同一份数据流,replay=1会保留最新的一个数据,确保新订阅者(比如这里的first())能立即拿到最近的结果,不会出现空值。 - 正确的挂起等待:
first()作为挂起函数,正确等待数据到来后再返回,避免了旧代码中未等待就返回空值的问题。 - 非阻塞的协程调度:用
coroutineScopeIO管理发射协程,耗时操作在IO线程执行,first()挂起当前协程而非阻塞主线程,完全符合协程的非阻塞设计。 conflate的优化:针对数据更新频繁的场景,conflate确保发射方不被阻塞,同时收集器始终拿到最新值,平衡了性能和数据时效性。
内容的提问来源于stack exchange,提问作者chrisChris
相关产品推荐
相关产品推荐

