前言:
第一次完整的看完了协程的源码,并写成了一个专栏。虽然效果不是很理想,但是对我个人来说收益还是很大的。总结了一些在写协程有关知识失败的地方,希望在Flow
的文章上能够带来一些突破。注重基础知识的讲解,而且一定要严谨。如果对协程感兴趣的同学,可以看看以下几篇文章。
1.流的概念
Kotlin Flow
是 Kotlin 提供的一种用于异步数据流处理的库。它类似于 RxJava
中的 Observable
,可以用于处理异步数据流,并支持在数据流中进行操作、转换和组合。使用 Kotlin Flow
可以更方便地处理异步操作,避免回调地狱等问题。您可以使用 Kotlin Flow
来处理诸如网络请求、数据库查询、UI
事件等异步操作的数据流。
2.冷流和热流的区别
- 冷流: 冷流在被订阅前不会开始执行数据的生成。 它是惰性的,只有当观察者订阅时才会开始收集和发射数据。 每个订阅者都会收到独立的数据流,即每个订阅者触发的数据收集过程是独立的。
- 热流: 热流一旦开始执行,就会持续不断地产生数据,不论是否有订阅者。 数据的产生不是基于订阅者的存在,而是基于某种外部事件或条件。 所有订阅者会共享同一数据流,即一旦数据开始产生,所有订阅者都能接收到相同的数据。 在
Kotlin
的Flow API
中,可以通过shareIn
或stateIn
等操作符将冷流转换为热流。
3.冷流的创建方式
冷流的创建方式有很多种。最常见的创建方式,是使用顶层函数flow()
或者flowOf()
来创建。如下代码示列:
从输出结果不难看出,冷流是自下而上触发数据的发送。只有当我们调用末端操作符
collect()
时,flow()
函数中的Lambda
实例才会被调用,从而触发emit()
。 其中flowOf()
被定义在协程库kotlinx.coroutines.flow
包下的Builder.kt
文件中,内部已经帮我做了emit()
操作,所以我们无需再重复调用emit()
函数。
该文件中还为我们提供了迭代器、序列、挂起函数类型的顶层扩展函数
asFlow()
,我们可以很方便的将这些对象转化为一个Flow
对象。

scss
fun main() {
runBlocking {
listOf(1, 2, 3).asFlow().collect { value ->
print("receiver: value = $value\n")
}
arrayOf(1, 2, 3).asFlow()
mapOf(1 to 1, 2 to 2, 3 to 3).asIterable().asFlow()
}
}
// 输出
receiver: value = 1
receiver: value = 2
receiver: value = 3
4.MutableSharedFlow
Kotlin flow为开发者提供了两种创建热流的顶层函数:MutableSharedFlow()和MutableStateFlow()。在函数式编程中相信大家都已经司空见惯了,我们经常定义一个顶层函数来伪装成同名类型的构造器。
- 顶层函数MutableSharedFlow()
主要有三个参数:
- replay: 新订阅者 collect 时,发送 replay 个历史数据给它,默认新订阅者不会获取以前的数据。
- extraBufferCapacity: MutableSharedFlow 缓存的数据个数为 replay + extraBufferCapacity; 缓存一方面用于粘性事件的发送,另一方面也为了处理背压问题,既下游的消费者的消费速度低于上游生产者的生产速度时,数据会被放在缓存中。
- onBufferOverflow: 背压处理策略,缓存区满后怎么处理(挂起或丢弃数据),默认挂起。注意,当没有订阅者时,只有最近 replay 个数的数据会存入缓存区,不会触发 onBufferOverflow 策略。以下是对BufferOverflow定义的几种类型的具体说明。
csharp
public enum class BufferOverflow {
/**
* 缓冲区溢出时挂起
*/
SUSPEND,
/**
* 在溢出时删除缓冲区中最早的值,将新值添加到缓冲区,不挂起
*/
DROP_OLDEST,
/**
* 在缓冲区溢出时删除当前添加到缓冲区的最新值(以便缓冲区的内容不变),不要挂起。
*/
DROP_LATEST
}
简单示例:
java
fun main() {
val sharedFlow = MutableSharedFlow<String>(replay = 2)
scope.launch {
for (value in 0 until 10) {
delay(100)
sharedFlow.emit("$value")
println("emit value = $value")
}
}
scope.launch {
sharedFlow.collect { value ->
// 消费速度慢于生产速度
delay(200)
println("receiver value = $value")
}
}
runBlocking { delay(10000) }
}
// 输出
emit value = 0
emit value = 1
receiver value = 0
emit value = 2
emit value = 3
receiver value = 1
emit value = 4
receiver value = 2
emit value = 5
receiver value = 3
emit value = 6
receiver value = 4
emit value = 7
receiver value = 5
emit value = 8
receiver value = 6
emit value = 9
receiver value = 7
receiver value = 8
receiver value = 9
2.扩展函数shareIn
kotlin
public fun <T> Flow<T>.shareIn(
scope: CoroutineScope,
started: SharingStarted,
replay: Int = 0
): SharedFlow<T> {
val config = configureSharing(replay)
val shared = MutableSharedFlow<T>(
replay = replay,
extraBufferCapacity = config.extraBufferCapacity,
onBufferOverflow = config.onBufferOverflow
)
@Suppress("UNCHECKED_CAST")
val job = scope.launchSharing(config.context, config.upstream, shared, started, NO_VALUE as T)
return ReadonlySharedFlow(shared, job)
}
shareIn是Flow的一个扩展函数,主要有3个参数:
- scope: CoroutineScope,定义了 Flow 的生命周期。
- started: SharingStarted,定义了 Flow 开始共享的条件。有以下几种选项:
- Eagerly: 立即启动 Flow,即使没有订阅者。
- Lazily: 当存在首个订阅者时启动
- WhileSubscribed(replayExpiration: Duration = Duration.INFINITE, stopTimeout: Duration = 0.seconds): 当有订阅者时启动 Flow,并且在最后一个订阅者取消订阅后,等待 stopTimeout 时间后停止 Flow。replayExpiration 定义了重新播放缓存值的时间。
- replay: 缓存的元素数量,当新的订阅者加入时,可以重放这些缓存的值
实际开发中,我们可以根据需求择优选择设置started的类型
简单示例:
scss
fun main() = runBlocking {
val flow = (1..3).asFlow().onEach { delay(1000) } // 模拟耗时操作
val sharedFlow = flow.shareIn(this, SharingStarted.Lazily, 1)
launch {
sharedFlow.collect { value ->
println("Collector 1: $value")
}
}
launch {
delay(500) // 确保第一个收集器已经开始
sharedFlow.collect { value ->
println("Collector 2: $value")
}
}
delay(4000) // 等待所有收集完成
}
// 输出
Collector 1: 1
Collector 2: 1
Collector 1: 2
Collector 2: 2
Collector 1: 3
Collector 2: 3
5. 顶层函数MutableStateFlow()
1.StateFlow是粘性的,而SharedFlow是非粘性的。
粘性事件/状态 :即当新订阅者开始监听时,会立即收到最后一次发射的值。
非粘性事件/状态:新订阅者只会在有新值发射时收到更新,不会立即收到历史值。 理解了粘性和非粘性的概念后,下面我们使用一下代码示例来验证两者的区别:
kotlin
private val scope = CoroutineScope(Dispatchers.Default)
fun main() {
val stateFlow = MutableStateFlow("-1")
val sharedFlow = MutableSharedFlow<String>()
scope.launch {
for(value in 0 until 3) {
stateFlow.emit("$value")
sharedFlow.emit("$value")
println("emit value = $value")
}
}
scope.launch {
sharedFlow.collect { value ->
println("sharedFlow collect value = $value")
}
}
scope.launch {
stateFlow.collect { value ->
println("stateFlow collect value = $value")
}
}
runBlocking { delay(500) }
}
// 输出
emit value = 0
emit value = 1
emit value = 2
stateFlow collect value = 2
从输出结果来看,sharedFlow并没有输出,在emit操作执行完成后,在后续的订阅中并没有收到历史值。而上文中我们又说到顶层函数MutableSharedFlow()它有一个replay参数。(replay: 新订阅者 collect 时,发送 replay 个历史数据给它,默认新订阅者不会获取以前的数据。)也就是说如果我们在SharedFlow创建时将replay初始值设为1,那么我们就可以将SharedFlow当做StateFlow来使用。我们只需将上述示列中创建SharedFlow的代码做如下改动:
ini
val sharedFlow = MutableSharedFlow<String>()
val sharedFlow = MutableSharedFlow<String>(replay = 1)
运行上述代码,我们就可以得到以下结果:
ini
emit value = 0
emit value = 1
emit value = 2
sharedFlow collect value = 2
stateFlow collect value = 2
2.扩展函数stateIn
kotlin
public fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T> {
val config = configureSharing(replay = 1)
val state = MutableStateFlow(initialValue)
val job = scope.launchSharing(config.context, config.upstream, state, started, initialValue)
return ReadonlyStateFlow(state, job)
}
从stateIn的实现,我们可以看出它和扩展函数shareIn很相似。不同的是第三个参数initiaValue,stateIn方法需要提供一个初始数据值,并在函数内部将可接受的历史数据replay设置了为1。
6.总结
到这里关于Flow的基础知识我们就介绍完了,关于Flow的底层实现还是有些复杂的,这里就不展开描述了。下篇文章,我们将介绍Flow的一些基础操作符,感谢您的观看~