一、简介
上篇文章:Kotlin中 Flow、SharedFlow与StateFlow区别:主要讲解了Kotlin中 Flow、SharedFlow与StateFlow的使用与区别,感兴趣的同学可以去看看。
这篇文章主要讲解SharedFlow与StateFlow的源码分析与实现,它是如何做到数据的挂起与恢复。
二、SharedFlow
从使用开始分析:
private fun testSharedFlow() {
val sharedFlow = MutableSharedFlow<Int>(
replay = 0,//相当于粘性数据
extraBufferCapacity = 0,//接受的慢时候,发送的入栈
onBufferOverflow = BufferOverflow.SUSPEND
)
lifecycleScope.launch {
(1..5).forEach {
println("emit1 send ago flow $it")
sharedFlow.emit(it)
println("emit1 send after flow $it")
}
sharedFlow.collect {
println("collect1 received ago shared flow $it")
}
}
}
2.1、初始化配置:
关于参数的含义可参考: Kotlin中 Flow、SharedFlow与StateFlow区别
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
require(replay >= 0) { "replay cannot be negative, but was $replay" }
require(extraBufferCapacity >= 0) { "extraBufferCapacity cannot be negative, but was $extraBufferCapacity" }
require(replay > 0 || extraBufferCapacity > 0 || onBufferOverflow == BufferOverflow.SUSPEND) {
"replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy $onBufferOverflow"
}
val bufferCapacity0 = replay + extraBufferCapacity
val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0 // coerce to MAX_VALUE on overflow
return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}
说明: MutableSharedFlow 主要实现接口如下:
public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
/**
* Emits a [value] to this shared flow, suspending on buffer overflow if the shared flow was created
* with the default [BufferOverflow.SUSPEND] strategy.
*
* See [tryEmit] for a non-suspending variant of this function.
*
* This method is **thread-safe** and can be safely invoked from concurrent coroutines without
* external synchronization.
*/
override suspend fun emit(value: T)
/**
* Tries to emit a [value] to this shared flow without suspending. It returns `true` if the value was
* emitted successfully. When this function returns `false`, it means that the call to a plain [emit]
* function will suspend until there is a buffer space available.
*
* A shared flow configured with a [BufferOverflow] strategy other than [SUSPEND][BufferOverflow.SUSPEND]
* (either [DROP_OLDEST][BufferOverflow.DROP_OLDEST] or [DROP_LATEST][BufferOverflow.DROP_LATEST]) never
* suspends on [emit], and thus `tryEmit` to such a shared flow always returns `true`.
*
* This method is **thread-safe** and can be safely invoked from concurrent coroutines without
* external synchronization.
*/
public fun tryEmit(value: T): Boolean
/**
* The number of subscribers (active collectors) to this shared flow.
*
* The integer in the resulting [StateFlow] is not negative and starts with zero for a freshly created
* shared flow.
*
* This state can be used to react to changes in the number of subscriptions to this shared flow.
* For example, if you need to call `onActive` when the first subscriber appears and `onInactive`
* when the last one disappears, you can set it up like this:
*
* ```
* sharedFlow.subscriptionCount
* .map { count -> count > 0 } // map count into active/inactive flag
* .distinctUntilChanged() // only react to true<->false changes
* .onEach { isActive -> // configure an action
* if (isActive) onActive() else onInactive()
* }
* .launchIn(scope) // launch it
* ```
*/
public val subscriptionCount: StateFlow<Int>
/**
* Resets the [replayCache] of this shared flow to an empty state.
* New subscribers will be receiving only the values that were emitted after this call,
* while old subscribers will still be receiving previously buffered values.
* To reset a shared flow to an initial value, emit the value after this call.
*
* On a [MutableStateFlow], which always contains a single value, this function is not
* supported, and throws an [UnsupportedOperationException]. To reset a [MutableStateFlow]
* to an initial value, just update its [value][MutableStateFlow.value].
*
* This method is **thread-safe** and can be safely invoked from concurrent coroutines without
* external synchronization.
*
* **Note: This is an experimental api.** This function may be removed or renamed in the future.
*/
@ExperimentalCoroutinesApi
public fun resetReplayCache()
}
```
public interface SharedFlow<out T> : Flow<T> {
/**
* A snapshot of the replay cache.
*/
public val replayCache: List<T>
}
```
```
public interface FlowCollector<in T> {
/**
* Collects the value emitted by the upstream.
* This method is not thread-safe and should not be invoked concurrently.
*/
public suspend fun emit(value: T)
}
```
参数解释:
详细解释可以参考上面的英文注释,大致翻译如下:
emit(value: T):向此共享流发出[value],如果创建了共享流,则在缓冲区溢出时挂起 使用默认的[BufferOverflow.]暂停挂起)策略tryEmit:返回一个 boolean 值,你可以这样判断返回值,当使用 emit 会挂起时,使用 tryEmit 会返回 false,其余情况都是 true。这意味着 tryEmit 返回 false 的前提是 extraBufferCapacity 必须设为 SUSPEND,且 Buffer 中空余位置为 0 。subscriptionCount:此共享流的订阅者(活动收集器)的数量。 对于新创建的共享流,结果StateFlow中的整数不是负数,并且从零开始。 此状态可用于对此共享流的订阅数量的变化作出反应。resetReplayCache: 重置粘滞缓存,新的订阅者只能获取新的事件,但是旧的订阅者(old subscriber)仍然会从缓存中获取旧的事件(因为缓存列表本身分为粘滞区和缓存区),意思是调用了该方法后,将清除发送池中的缓存(想当于清除已经发送过的数据),后续的订阅者只能收集在他订阅后的发送的数据。
2.2、MutableSharedFlow唯一实现SharedFlowImpl它的初始化
如下图:
由if条件可知:
bufferCapacity0 值来源于 replay + extraBufferCapacity
三、MutableSharedFlow唯一实现SharedFlowImpl
3.1、初始化从使用时入手分析
private class SharedFlowImpl<T>(
private val replay: Int, // replayCache的最大容量
private val bufferCapacity: Int, // buffered values的最大容量
private val onBufferOverflow: BufferOverflow // 溢出策略
) : AbstractSharedFlow<SharedFlowSlot>(), MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
/*
Logical structure of the buffer
buffered values
/-----------------------\
replayCache queued emitters
/----------/----------------------\
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | 1 | 2 | 3 | 4 | 5 | 6 | E | E | E | E | E | E | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^ ^ ^ ^
| | | |
head | head + bufferSize head + totalSize
| | |
index of the slowest | index of the fastest
possible collector | possible collector
| |
| replayIndex == new collector's index
---------------------- /
range of possible minCollectorIndex
head == minOf(minCollectorIndex, replayIndex) // by definition
totalSize == bufferSize + queueSize // by definition
INVARIANTS:
minCollectorIndex = activeSlots.minOf { it.index } ?: (head + bufferSize)
replayIndex <= head + bufferSize
*/
// 缓存数组,用于保存emit方法发射的数据,在需要时进行初始化
private var buffer: Array<Any?>? = null
// 新的订阅者从replayCache中获取数据的起始位置
private var replayIndex = 0L
// 当前所有的订阅者从缓存数组中获取的数据中,对应位置最小的索引
// 如果没有订阅者,则minCollectorIndex的值等于replayIndex
private var minCollectorIndex = 0L
// 缓存数组中buffered values缓存数据的数量
private var bufferSize = 0
// 缓存数组中queued emitters缓存数据的数量
private var queueSize = 0
// 当前缓存数组的起始位置
private val head: Long get() = minOf(minCollectorIndex, replayIndex)
// 当前缓存数组中replayCache缓存数据的数量
private val replaySize: Int get() = (head + bufferSize - replayIndex).toInt()
// 当前缓存数组中已经缓存的数据的数量
private val totalSize: Int get() = bufferSize + queueSize
// 当前缓存数组中buffered values的最末尾位置索引的后一位
private val bufferEndIndex: Long get() = head + bufferSize
// 当前数组中queued emitters的最末尾位置索引的后一位
private val queueEndIndex: Long get() = head + bufferSize + queueSize
...
}
3.1.1、发送:
sharedFlow.emit(it)
emit方法会先进行一次tryEmit的处理,当返回false的时候再进行suspend的发送操作
override suspend fun emit(value: T) {
if (tryEmit(value)) return // fast-path
emitSuspend(value)
}
3.1.2、再看tryEmit:
通过前面对tryEmit方法的注释判断缓存策略为 BufferOverflow.SUSPEND才有可能为true,所以再看tryEmitLocked方法
override fun tryEmit(value: T): Boolean {
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
val emitted = synchronized(this) {
if (tryEmitLocked(value)) {
resumes = findSlotsToResumeLocked(resumes)
true
} else {
false
}
}
for (cont in resumes) cont?.resume(Unit)
return emitted
}
3.1.3、tryEmitLocked方法:
果然从这里可以看到,当策略采用suspended同时缓存溢出的时候,返回false,否则,永远返回true,同时做一些事件上的入队处理等
@Suppress("UNCHECKED_CAST")
private fun tryEmitLocked(value: T): Boolean {
// Fast path without collectors -> no buffering
//注释1.--------没有检测到订阅者(collect收集器)
if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
// With collectors we'll have to buffer
// cannot emit now if buffer is full & blocked by slow collector
//注释2.--------检测到订阅者(collect收集器)如果当前有订阅者,同时buffered values已达到最大容量。
if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
when (onBufferOverflow) {
BufferOverflow.SUSPEND -> return false // will suspend,只有这里才会返回true
BufferOverflow.DROP_LATEST -> return true // just drop incoming
BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
}
}
//注释3.--------buffered values还可以继续添加数据, 将数据加入到缓存数组中
// 这里因为tryEmit方法不会挂起emit方法所在的协程,
// 所以value没有被封装成Emitter类型的对象
enqueueLocked(value)
bufferSize++ // value was added to buffer
// drop oldest from the buffer if it became more than bufferCapacity
//注释4.-------- 如果buffered values的数据数量超过最大容量的限制,
//则调用dropOldestLocked方法,丢弃最旧的数据
if (bufferSize > bufferCapacity) dropOldestLocked()
// keep replaySize not larger that needed
//注释5.-------- 如果replayCache中数据的数量超过了最大容量
if (replaySize > replay) { // increment replayIndex by one
// 更新replayIndex的值,replayIndex向前移动一位
updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
}
return true
}
- 从
tryEmitLocked方法代码注释1处可知: 因为是热流tryEmitNoCollectorsLocked方法就是没有订阅者的情况,
private fun tryEmitNoCollectorsLocked(value: T): Boolean {
assert { nCollectors == 0 }
//replay == 0直接返回,不需要去缓存
if (replay == 0) return true // no need to replay, just forget it now
//将值入队
enqueueLocked(value) // enqueue to replayCache
//缓存加1
bufferSize++ // value was added to buffer
// drop oldest from the buffer if it became more than replay
//如果缓存满了,就删除最老的那一条数据
// 如果buffered values的数据数量超过了replayCache的最大容量
// 则丢弃最旧的数据
// 因为新订阅者只会从replayCache中取数据,
// 如果没有订阅者,buffered values的数据数量超过replayCache的最大容量没有意义
if (bufferSize > replay) dropOldestLocked()
//重新设置订阅者的值位置索引
minCollectorIndex = head + bufferSize // a default value (max allowed)
return true
}
// enqueues item to buffer array, caller shall increment either bufferSize or queueSize
private fun enqueueLocked(item: Any?) {
//totalSize = 缓冲值的数目+排队发射器的数量
val curSize = totalSize
val buffer = when (val curBuffer = buffer) {
// 缓存数组为空,则进行初始化,初始化容量为2
null -> growBuffer(null, 0, 2)
// 如果超过了当前缓存数组的最大容量,则进行扩容,新的缓存数组的容量为之前的2倍
// growBuffer方法会把原来缓存数组的数据填充到新的缓存数组中
else -> if (curSize >= curBuffer.size) growBuffer(curBuffer, curSize,curBuffer.size * 2) else curBuffer
}
buffer.setBufferAt(head + curSize, item)
}
- 从
tryEmitLocked方法代码注释2处可知现在是有订阅者的情况:
bufferSize >= bufferCapacity缓存数组中buffered values缓存数据的数量 >= buffered values的最大容量。minCollectorIndex <= replayIndex当前所有的订阅者从缓存数组中获取的数据中,对应位置最小的索引<=新的订阅者从replayCache中获取数据的起始位置
if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
when (onBufferOverflow) {
BufferOverflow.SUSPEND -> return false // will suspend,只有这里才会返回true
BufferOverflow.DROP_LATEST -> return true // just drop incoming
BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
}
}
3.1.4、回到: 3.1.2、的tryEmit方法中:
我们知道tryEmitLocked方法进行数据缓存入队 尝试发射数据,如果发射成功, 收集已经挂起的订阅者的续体,唤醒订阅者,返回true
override fun tryEmit(value: T): Boolean {
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
val emitted = synchronized(this) {
if (tryEmitLocked(value)) {
//收集已经挂起的订阅者的续体
resumes = findSlotsToResumeLocked(resumes)
true
} else {
false
}
}
for (cont in resumes) cont?.resume(Unit)
return emitted
}
3.1.5、findSlotsToResumeLocked方法中:
private fun findSlotsToResumeLocked(resumesIn: Array<Continuation<Unit>?>): Array<Continuation<Unit>?> {
// 引用参数中的续体数组
var resumes: Array<Continuation<Unit>?> = resumesIn
// 用于记录需要恢复的续体的数量
var resumeCount = resumesIn.size
// 遍历订阅者数组
forEachSlotLocked loop@{ slot ->
// 获取续体,如果续体为空,说明对应订阅者的协程没有挂起,本次循环返回
val cont = slot.cont ?: return@loop
// 判断slot中index是否符合要求
// 如果不符合要求,则本次循环返回
if (tryPeekLocked(slot) < 0) return@loop
// 如果需要恢复的续体的数量超过续体数组的容量,则进行扩容
// 新的续体数组的容量是之前续体数组容量的2倍
if (resumeCount >= resumes.size) resumes = resumes.copyOf(maxOf(2, 2 * resumes.size))
// 保存续体到续体数组中
resumes[resumeCount++] = cont
// 清空slot中保存的续体
slot.cont = null
}
// 返回收集完的续体数组
return resumes
}
到这里非挂起的发送流程结束
3.2、挂起的方式发送
override suspend fun emit(value: T) {
//非挂起
if (tryEmit(value)) return // fast-path
//挂起
emitSuspend(value)
}
挂起调用emitSuspend方法,这里也调用了上面提到的findSlotsToResumeLocked方法
private suspend fun emitSuspend(value: T) =
// 直接挂起emit方法所在的协程,获取续体
suspendCancellableCoroutine<Unit> sc@{ cont ->
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
// 加锁
val emitter = synchronized(this) lock@{
// 这里再次尝试以tryEmit的方式发射数据
if (tryEmitLocked(value)) {
// 如果发射成功,则恢复续体的执行
cont.resume(Unit)
// 收集已经挂起的订阅者的续体
resumes = findSlotsToResumeLocked(resumes)
// 返回
return@lock null
}
// 将续体、待发射的数据等封装成Emitter类型的对象
Emitter(this, head + totalSize, value, cont).also {
// 加入到缓存数组中
enqueueLocked(it)
// queued emitters的数据的数量加1
queueSize++
// 如果buffered values的最大容量为0,即不存在
// 则收集已经挂起的订阅者的续体,保存到局部变量resumes中
if (bufferCapacity == 0) resumes = findSlotsToResumeLocked(resumes)
}
}
// emitter对象监听emit方法所在协程的取消
// 发生取消时会调用emitter对象的dispose方法
emitter?.let { cont.disposeOnCancellation(it) }
// 遍历,唤起挂起的订阅者
for (cont in resumes) cont?.resume(Unit)
}
SharedFlowImpl类实现了SharedFlow接口,重写了其中的常量replayCache,当有新订阅者出现时,如果replayCache存在,并且有缓存数据,则优先从replayCache中获取,代码如下:
override val replayCache: List<T>
// 只能获取,不能设置,加锁
get() = synchronized(this) {
// 获取当前replayCache中缓存数据的数量
val replaySize = this.replaySize
// 如果数量为0,则返回一个空列表
if (replaySize == 0) return emptyList()
// 若数量不为0,则根据容量创建一个列表
val result = ArrayList<T>(replaySize)
// 获取缓存数组
val buffer = buffer!!
// 遍历replayCache,将数据进行类型转换,并添加到列表中
@Suppress("UNCHECKED_CAST")
for (i in 0 until replaySize) result += buffer.getBufferAt(replayIndex + i) as T
// 返回列表
result
}
3.2、收集Collect
使用
sharedFlow.collect {
}
SharedFlowImpl 源码:
@Suppress("UNCHECKED_CAST")
override suspend fun collect(collector: FlowCollector<T>) {
// 为当前的订阅者分配一个SharedFlowSlot类型的对象
val slot = allocateSlot()
try {
// 如果collector类型为SubscribedFlowCollector,
// 说明订阅者监听了订阅过程的启动,则先回调
if (collector is SubscribedFlowCollector) collector.onSubscription()
// 获取订阅者所在的协程
val collectorJob = currentCoroutineContext()[Job]
// 死循环
while (true) {
var newValue: Any?
// 死循环
while (true) {
// 从缓存数组中获取数据
newValue = tryTakeValue(slot)
// 如果获取数据成功,则跳出循环
if (newValue !== NO_VALUE) break
// 走到这里,说明获取数据失败,
// 挂起订阅者所在协程,等待新数据的到来
awaitValue(slot)
}
// 走到这里,说明已经获取到了数据
// 判断订阅者所在协程是否是存活的,如果不是则抛出异常
collectorJob?.ensureActive()
// 进行类型转换,并向下游发射数据
collector.emit(newValue as T)
}
} finally {
// 释放已分配的SharedFlowSlot类型的对象
freeSlot(slot)
}
}
@SharedImmutable
@JvmField
internal val NO_VALUE = Symbol("NO_VALUE")
参考: 推荐这位大佬的文章:juejin.cn/post/714469…