Kotlin中 Flow、SharedFlow与StateFlow区别源码分析(二)

1,179 阅读10分钟

一、简介

上篇文章: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它的初始化

如下图: image.png

由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处可知现在是有订阅者的情况:
  1. bufferSize >= bufferCapacity 缓存数组中buffered values缓存数据的数量 >= buffered values的最大容量。
  2. 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…