持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
冷流与热流
Flow 与 SharedFlow、StateFlow 最大的区别在于 Flow 是冷流,而 SharedFlow、StateFlow 是热流。那冷流和热流又有什么区别?
冷流中的数据并不是一直存在内存中的,而且当收集的时候,才会产生,存储在内存中,等收集完后就会自动回收。
而热流中的数据是可以即使不收集的时候,也能产生数据,并把产生后就存储在内存中,等到收集完后,再把数据进行回收。
Flow
关于 Flow 的说明以及使用,可以参考「flow 操作符全解析」,这里就不过多进行说明了。
SharedFlow
在说明 SharedFlow 与 StateFlow 的区别前,我们可以先来了解 SharedFlow 是什么?
SharedFlow 就是观察者模式的一个具体实现,可以对观察者进行注册,当被观察者发生改变的时候,就会通知观察者,同时,SharedFlow 还可以存储之前的变化,即当 SharedFlow 变化后,才有观察者注册,这时观察者仍然能够收到之前的变化。
说了这么多,我们先来看看 SharedFlow 的简单使用吧,后续再详细说明。
首先,我们先创建 SharedFlow 对象:
private val sharedFlow = MutableSharedFlow<Int>()
复制代码
然后进行数据提交更改:
sharedFlow.emit(0)
sharedFlow.emit(1)
sharedFlow.emit(2)
复制代码
然后进行收集:
sharedFlow.collect { value ->
println("value -> $value")
}
复制代码
我们来看下日志输出:
复制代码
em???? 怎么啥都没有,这跟说好的不一样啊!
我们来看看 MutableSharedFlow 的构造参数:
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
复制代码
- replay:指的是观察者注册后,能够收到多少个之前的通知,默认是为 0。
- extraBufferCapacity:指的是当被观察者发送速度过快,或者观察者消化消息的速度过慢的时候,数据就会临时存储起来,而这里指的就是额外存储的个数,注意,是额外存储的个数,实际存储的个数是 replay + extraBufferCapacity。
- onBufferOverflow:由于 MutableSharedFlow 的存储空间是固定的,有可能会被超过限制,而 onBufferOverflow 则是标识当超过这个限制的时候,怎么进行处理,目前有三种选项:
- BufferOverflow.SUSPEND:挂起,默认实现。
- BufferOverflow.DROP_OLDEST:丢弃最老的值,保留最新的。
- BufferOverflow.DROP_LATEST:丢弃最新的值,保留最老的。
通过上面的参数的讲解,我们就能够很清楚为什么上面的例子收不到消息了,所以,下面我们通过一个例子,简单测验这些值的功能。
class MainActivity : AppCompatActivity() {
private val sharedFlow = MutableSharedFlow<Int>(1, 2, BufferOverflow.DROP_OLDEST)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
sharedFlow.emit(0)
sharedFlow.emit(1)
sharedFlow.emit(2)
sharedFlow.collect { value ->
delay(1000)
println("value -> $value")
}
}
GlobalScope.launch {
sharedFlow.emit(3)
sharedFlow.emit(4)
sharedFlow.emit(5)
sharedFlow.emit(6)
}
}
}
复制代码
日志输出为:
I/System.out: value -> 2
I/System.out: value -> 4
I/System.out: value -> 5
I/System.out: value -> 6
复制代码
我们来逐步分析下日志:
- 为什么有 2 ?
这是因为 replay 为 1,只会存储一位已发送的数据,所以,最开始发送的数据 0、1、2,只会保留最新的 2。
- 为什么有 4、5、6 ?
这是因为 MutableSharedFlow 实际的缓存个数为 replay + extraBufferCapacity,也就是 1 + 2 = 3,所以有三个缓存位置,又由于 onBufferOverflow 为 BufferOverflow.DROP_OLDEST,所以,3、4、5、6 中只会保留最新的 4、5、6。
特别说明
有一点需要特别注意的,就是当 collect 运行结束后,即使使用 emit 发送数据,collect 里面的代码也不会执行。
另外,在测验中,我还发现另外一个坑点,就是把 collect 和 emit 放到同一个协程中运行,特别是 emit 在 collect 之后,会发现没有任何输出,就像这样:
GlobalScope.launch {
sharedFlow.collect { value ->
delay(1000)
println("value -> $value")
}
sharedFlow.emit(0)
sharedFlow.emit(1)
sharedFlow.emit(2)
}
复制代码
其实,这也很容易理解,因为 delay 的时候,该协程都阻塞了,emit 是不会执行到,而当 delay 之后,collect 都执行完成,这时再 emit,自然也不会再执行 collect 里面的代码。
StateFlow
StateFlow 其实就是一个特殊的 SharedFlow,就可以理解为是:
MutableSharedFlow(1,0, BufferOverflow.SUSPEND)
复制代码
下面我们用个例子来看看是不是这个效果:
class MainActivity : AppCompatActivity() {
private val stateFlow = MutableStateFlow(0)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
stateFlow.emit(0)
stateFlow.emit(1)
stateFlow.emit(2)
stateFlow.collect { value ->
delay(1000)
println("value -> $value")
}
}
GlobalScope.launch {
stateFlow.emit(3)
stateFlow.emit(4)
stateFlow.emit(5)
stateFlow.emit(6)
}
}
}
复制代码
日志输出:
I/System.out: value -> 2
I/System.out: value -> 6
复制代码
大家可以根据我在上面的说明,看看能不能去理解为什么日志输出会是这样。
特别说明
- StateFlow 和 SharedFlow 一样,collect 结束后,再 emit 也不会再次调起 collect 里面的代码,除非重新 collect。
- StateFlow 除了可以使用 emit 提交更改,也可以使用这种方式进行更改:
stateFlow.value = 1