flow&channel区别

334 阅读8分钟

liveData会存储状态值,并且是粘性的,第一次observer时会收到初始值,不防抖,如果发送相同的数据则会收到重复的数据。

LiveData 是 Android 生态中一个的简单的生命周期感知型容器。简单即是它的优势,也是它的局限,当然这些局限性不应该算 LiveData 的缺点,因为 LiveData 的设计初衷就是一个简单的数据容器,需要具体问题具体分析。对于简单的数据流场景,使用 LiveData 完全没有问题。

  • 1、LiveData 只能在主线程更新数据: 只能在主线程 setValue,即使 postValue 内部也是切换到主线程执行;
  • 2、LiveData 数据重放问题: 注册新的订阅者,会重新收到 LiveData 存储的数据,这在有些情况下不符合预期;
  • 3、LiveData 不防抖问题: 重复 setValue 相同的值,订阅者会收到多次 onChanged() 回调(可以使用 distinctUntilChanged() 优化);注意使用distinctUntilChanged时,在某些场景下可能会存在问题,比如bool变量在onStop前为true,在onStop时更新为false,再更新为true,最终回到前台时通过distinct的observer可能不会收到更新值,因为onStop时livedata(参考第5点)的更新不会同步给distinct,然后再回到前台时收到更新true,此时distinc之前值也为true,导致不会发出变更通知。
  • 4、LiveData 丢失数据问题: 在数据生产速度 > 数据消费速度时,LiveData 无法观察者能够接收到全部数据。比如在子线程大量 postValue 数据但主线程消费跟不上时,中间就会有一部分数据被忽略。
  • 5、LiveData 自带生命周期变更问题: 某些场景比如根据前后台来切换逻辑,如果通过LiveData来通知切换前后台逻辑可能不会成功,因为当监听到onStop时去更新LiveData时,LiveData的observer可能不会收到变更的通知,因为LiveData内部自带生命周期逻辑,在onStop后不会通知observer,所以此种场景不宜用LiveData。

stateFlow有去重功能,发射之前会对当前值和上一次发射的值进行对比,如果相同,则直接过滤掉,不会发射相同的值

sharedFlow无去重功能,不关心具体发射的值,也可通过distinctUnitChanged()实现去重。

stateFlow是一种特殊的sharedFlow,继承自sharedFlow,sharedFlow可配置如下参数:

  • 参数replay主要用于调整新加入的订阅者的可回放缓存的数量,主要作用于订阅者,对订阅者有影响而对于发射者没有意义。

  • 参数extraBufferCapacity用于调整在参数replay基础上的额外缓存的数量,主要作用于发射者,对发射者有影响,容量大小可用于控制背压。如果没有新的订阅者加入,则不用区分replay和extraBufferCapacity,这两个参数的和为总缓存数量。

  • 参数onBufferOverflow用于调整当缓存将要溢出时的缓存策略。默认值为SUSPEND,至少有一个订阅者时缓存溢出策略才会生效。如果没有任何订阅者则仅存储最新的replay值,且该buffer overflow策略不会触发,也没有任何作用。

注意:SharedFlow禁止在缓冲区总值为零时使用onBufferOverflow = BufferOverflow.SUSPEND以外的任何策略,也即必须总buffer > 0才可以用除SUSPEND以外的其他策略。因为tryEmit(value: T)不是suspend,如果你用默认的replay和extraBufferCapacity值来使用它(即无缓冲空间),它就不会工作。 换句话说,用tryEmit(value: T)发射事件的唯一方法是,至少要有一个总缓冲区,否则将会始终返回false,并直接丢弃发射值。

  • emit:suspend函数,对sharedflow发送一条数据。如果buffer满了,则需同时采用了SUSPEND策略且有subscribers时,emit方法才会被挂起。如果没有subscribers,则会被直接丢弃,buffer不会启用。如果配置有一个replay cache,则最新的值会被存储在replay中(内部实现是通过一个index移动来实现),取代旧的值,或者直接丢弃如果没有配置replay的话。线程安全。

  • tryEmit:非suspend函数,如果发送成功了,返回true。如果返回false,则代表emit调用将会suspend(即代表buffer已经满了),直到有可用的buffer空间,同时此次tryEmit值将会被丢弃。注意:当策略不采用suspend时,tryEmit永远返回true。只有当采用SUSPEND策略,且有订阅者的时候,采用tryEmit才可能返回false。如果无订阅者,则buffer不会启用。如果配置有一个replay cache,则最新的值会被存储在replay中,取代旧的值或者直接丢弃如果没有配置replay的话。如果当前缓冲区有值且已满,且采用SUSPEND策略同时有订阅者,此时tryEmit返回false且值直接被丢弃。 tryEmit极易丢弃数据,慎用,线程安全。

  • 有多个订阅者时,每个订阅者的流是独立的,互不影响,各走各的值,后加入的订阅者者会根据最新的repaly index往后走,不影响前面的订阅者的流。

flow emit和connect默认在同一个协程中,可以用flowOn控制发送的线程,接收端的线程为collect时所处的线程环境相同, flow emit是线程安全的。 flow 是冷流,但stateFlow和sharedFlow是热流。

channel可以在不同协程中操作,而且是线程安全的,热流。channel可实现send后挂起直到有receive端接收(默认Channel构造函数即可),也即可以实现先发射再有订阅者的情况,且不会丢数据。channel为一对一,不支持一对多,不要用broadcastchannel, 该类已被废弃,用sharedflow代替。

对于想要在ViewModel层发射必须执行且只能执行一次的事件让View层执行时,不要再通过向LiveData postValue让View层监听实现。推荐使用Channel或者是通过Channel.receiveAsFlow方法创建的Flow来实现ViewModel层的事件发送,此Flow只能有一个接收者,不管有几个接收者,只要其中一个处理,其他就收不到了。

注意对于通过channelFlow{}来创建的flow来说,它比Channel更冷(collect之后才触发生产)比Flow更热(生产消费可以并行执行),即必须有collect之后,才会执行{}中具体的实现代码,生产端不必等到消费者执行完再执行,即生产端和消费端是各自独立的执行逻辑。生产端本身是一个capacity为BUFFERED和onBufferOverflow为SUSPEND策略的Channel。也即它会按生产的逻辑一直运行然后send数据,等容量达到之后就SUSPEND发送操作。channelFlow等同于Channel(Channel.BUFFERED) + Channel.receiveAsFlow。

我们想要向ViewModel发送事件,并让所有依赖它的组件接收到事件。比如在FragmentA点击按键触发事件A,其宿主Activity、相同宿主的FragmentB和FragmentA其本身都需要响应该事件。

有点像广播,且具有两个特性:

  1. 支持一对多,即一条消息支持被多个订阅者消费
  2. 具有时效性,过期的消息没有意义且不应该被延迟消费。

看起来EventBus是一种实现方法,但是已经有了ViewModel作为媒介再使用显然有些浪费,EventBus还是更适合跨页面、跨组件的通信。对比前面分析的几种模型的使用,发现SharedFlow在这个场景下非常有用武之地。

  1. SharedFlow类似BroadcastChannel,支持多个订阅者,一次发送多处消费。
  2. SharedFlow配置灵活,如默认配置 capacity = 0, replay = 0,意味着新订阅者不会收到类似LiveData的回放。无订阅者时会直接丢弃,正符合上述时效性事件的特点。

总结使用如下

  • UiState可用于表征UI状态,用StateFlow承载(通常用于代替LiveData),StateFlow会保存历史值,必须有初始值,有粘性,可直接取值及设置值,会去重。

  • UiEvent可用于表示交互事件的Intent,用SharedFlow承载,默认不会保存历史值,因此可能会丢数据,用于一次性事件,不能直接取值,可一对多,默认不去重。

  • UiEffect可用于表示一对一且必须消费事件,用channelFlow承载,热流,send会挂起直到有receive消费,即事件不会丢。

参考:

当,Kotlin Flow与Channel相逢

有小伙伴说看不懂 LiveData、Flow、Channel,跟我走

Android平台的Kotlin协程-Flow和Channel的那些事

用Kotlin Flow解决Android开发中的痛点问题

xuyisheng.top/sharedflowh…

SharedFlow的参数分析

遍历全网Android-MVI架构,学习总结一波

SharedFlow、StateFlow、LiveData 各自的高频场景使用

不做跟风党,LiveData,StateFlow,SharedFlow 使用场景对比

Jetpack MVVM 七宗罪之四: 使用 LiveData/StateFlow 发送 Events

终于懂了~ 图解 SharedFlow 缓存系统

Kotlin Coroutine 最佳实践

Kotlin Flow与ChannelFlow