SharedFlow 源码解析

552 阅读6分钟

前言:在使用默认的 SharedFlow 的时候,发现 tryEmit 总是为false;然后修改溢出策略会崩溃;replay 和 extraBufferCapacity 应该怎么填写;等等都需要了解 SharedFlow 的运行机制

MutableSharedFlow 创建方法

public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
    // step1: replay 和 extraBufferCapacity 都需要大于0
    require(replay >= 0) { "replay cannot be negative, but was $replay" }
    require(extraBufferCapacity >= 0) { "extraBufferCapacity cannot be negative, but was $extraBufferCapacity" }
    
    // step2: 当 replay == 0 且 extraBufferCapacity == 0 只能是挂起溢出策略
    require(replay > 0 || extraBufferCapacity > 0 || onBufferOverflow == BufferOverflow.SUSPEND) {
        "replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy $onBufferOverflow"
    }
    
    val bufferCapacity0 = replay + extraBufferCapacity
    // step3: 2个大正数相加,得出的值为负数,矫正
    val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0
    return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}

SharedFlowImpl 构造函数

  • replay: 重放数目,重放 bufferEndIndexreplay 个元素, bufferEndIndex-1 为最快的 collector
  • bufferCapacity: 缓存区总大小,用于决定最快的收集器和最慢收集器相差的最大差距,由 replayextraBufferCapacity 相加而得
  • onBufferOverFlow: 缓存溢出策略,可设置三种
    1. DROP_OLDEST: 丢弃最老的
    2. DROP_LASTEST: 丢弃最新的
    3. SUSEPND: 挂起 => 这个策略在溢出的时候不可 tryEmit

SharedFlowImpl 存储的状态和关键索引

  • buffer : 存放最近弹出 bufferSize 个元素和 queueSize 个排队发射器
  • queueSize: 排队发射器的数量
  • bufferSize: 最近弹出的元素,容量由 bufferCapacity 决定
  • minCollectorIndex: 活跃收集器的最小索引,没有活跃的收集器的时候等于 replayIndex,当源码中没有收集器的时候值为 bufferEndIndex, 不过有收集器的时候都会矫正
  • replayIndex: 新收集器获取的索引 => 因为需要回放最近 replay 个元素

SharedFlowImpl 计算状态

  • head(buffer的头索引): minOf(minCollectorIndex, replayIndex)
  • replaySize(当前存在的回放数量): (head + bufferSize - replayIndex).toInt()
  • totalSize(buffer的总大小): bufferSize + queueSize
  • bufferEndIndex(缓存弹出过的数据最后的索引): head + bufferSize
  • queueEndIndex(buffer最后的索引): head + bufferSize + queueSize

SharedFlowImpl#buffer 官方定义结构图

image.png

SharedFlowImpl 发射流程

SharedFlowImpl#emit(value:T): 发射

override suspend fun emit(value: T) {
    // step1: 尝试快速发射, 发射成功则为true
    if (tryEmit(value)) return
    // step2: 快速发射失败,则进入挂起等待发射
    emitSuspend(value)
}

SharedFlowImpl#tryEmit(value:T): true代表发射成功,false代表发射失败

override fun tryEmit(value: T): Boolean {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val emitted = synchronized(this) {
        // step1: 尝试是否能发送成功,发送成功,并找到对应 
        if (tryEmitLocked(value)) {
            // step2: 发射成功,得到可恢复collectors的协程
            resumes = findSlotsToResumeLocked(resumes)
            true
        } else {
            false
        }
    }
    // step3: 恢复 可恢复collectors 挂起的协程
    for (cont in resumes) cont?.resume(Unit)
    return emitted
}

SharedFlowImpl#emitSuspend(value:T): 发射挂起

private suspend fun emitSuspend(value: T) = suspendCancellableCoroutine<Unit> sc@{ cont ->
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val emitter = synchronized(this) lock@{
        // step1: 重新再试一次,成功则恢复对应协程
        if (tryEmitLocked(value)) {
            cont.resume(Unit)
            // step1.1: 得到可恢复 collect 协程
            resumes = findSlotsToResumeLocked(resumes)
            return@lock null
        }
        // step2: 生成 Emitter 进入buffer,并且增加 queueSize, 同时挂起 emit 协程
        Emitter(this, head + totalSize, value, cont).also {
            enqueueLocked(it)
            queueSize++
            // step2.1: 如果当前为同步流,则得到可恢复 collect 协程
            if (bufferCapacity == 0) resumes = findSlotsToResumeLocked(resumes)
        }
    }
    // step3: 注册 emit 所在协程取消回调
    emitter?.let { cont.disposeOnCancellation(it) }
    // step4: 恢复对应 collect 协程
    for (r in resumes) r?.resume(Unit)
}

SharedFlowImpl#tryEmitLocked: 发射核心流程

private fun tryEmitLocked(value: T): Boolean {
    // step1: 当没有收集者的时候,trymit总是返回成功,处理 replay 数据
    if (nCollectors == 0) return tryEmitNoCollectorsLocked(value)
   
    // step2: 如果缓存区已满且当前收集者最小的索引小于replayIndex的时候
    if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
        when (onBufferOverflow) {
            // step2.1: 当前溢出处理为挂起,则不能弹出新值
            BufferOverflow.SUSPEND -> return false
            // step2.2: 当前溢出处理为丢弃最后,直接返回true,忽略弹出的新值
            BufferOverflow.DROP_LATEST -> return true
            // step2.3: 当前溢出处理为丢弃最先加入的,合并后续流程
            BufferOverflow.DROP_OLDEST -> {}
        }
    }
    
    // step3: 加入到 buffer 数组中
    enqueueLocked(value)
    // step4: 增加缓存区大小
    bufferSize++
    // step5: 如果超出设置的 bufferCapacity 大小,则丢弃最老的,这里的情况只有是溢出策略为丢弃最后或者可调整replayIndex(即当前所有收集者都是在replayIndex之后的),才会走到这里
    if (bufferSize > bufferCapacity) dropOldestLocked()
    // step6: 如果 replaySize > replay, 则调整 replayIndex
    if (replaySize > replay) { 
        updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
    }
    // step7: 弹出成功
    return true
}

SharedFlowImpl#tryEmitNoCollectorsLocked: 没有 collector,则处理 replay 数据

private fun tryEmitNoCollectorsLocked(value: T): Boolean {
    // step1: 没有设置 replay 同时也没有 colletors => 不用任何处理,直接返回true
    if (replay == 0) return true 
    // step2: 加入 value 到 buffer 中
    enqueueLocked(value)
    // step3: 更新 bufferSize 的大小
    bufferSize++ // value was added to buffer
    // step4: 当前 bufferSize 全部为 replay(因为没有collectors), 直接丢弃最老进入的(因为没有其他collector引用)
    if (bufferSize > replay) dropOldestLocked()
    // step5: 更新 minCollectorIndex
    minCollectorIndex = head + bufferSize
    return true
}

SharedFlowImpl#findSlotsToResumeLocked: 查找当前可恢复 collectors 进行恢复

private fun findSlotsToResumeLocked(resumesIn: Array<Continuation<Unit>?>): Array<Continuation<Unit>?> {
    var resumes: Array<Continuation<Unit>?> = resumesIn
    var resumeCount = resumesIn.size
    // step1: 遍历所有的collector
    forEachSlotLocked loop@{ slot ->
        // step2: 当前没有挂起,跳过
        val cont = slot.cont ?: return@loop
        // step3: 当前获取不到新值,跳过
        if (tryPeekLocked(slot) < 0) return@loop
        // step4: 存入恢复数组
        if (resumeCount >= resumes.size) resumes = resumes.copyOf(maxOf(2, 2 * resumes.size))
        resumes[resumeCount++] = cont
        // step5: 置空
        slot.cont = null 
    }
    return resumes
}

SharedFlowImpl#tryPeekLocked: -1代表未找到, >=0 代表可以获取新值

private fun tryPeekLocked(slot: SharedFlowSlot): Long {
    val index = slot.index
    // step1: buffer还有可用值,返回 slot#index
    if (index < bufferEndIndex) return index
    // step2: 有缓存区但是用完了,不能获取到值
    if (bufferCapacity > 0) return -1L 
    // step3: 没有缓存区(即同步流), 但当前不是第一个 Emitter, 不能获取到值
    if (index > head) return -1L
    // step4: 当前没有 Emitter, 不能获取到值
    if (queueSize == 0) return -1L
    // step5: 返回第一个 Emitter 的索引
    return index 
}

SharedFlowImpl 收集流程

SharedFlowImpl#collect

override suspend fun collect(collector: FlowCollector<T>): Nothing {
    // step1: 获取插槽,用于获取当前新值和恢复协程运行
    val slot = allocateSlot()
    try {
        if (collector is SubscribedFlowCollector) collector.onSubscription()
        val collectorJob = currentCoroutineContext()[Job]
        while (true) {
            var newValue: Any?
            while (true) {
                // step2: 尝试获取值,失败则挂起等待新值到来
                newValue = tryTakeValue(slot)
                // step3: 获取成功,则跳出循环
                if (newValue !== NO_VALUE) break
                // step4: 获取失败,挂起等待新值
                awaitValue(slot) 
            }
            // step5: 检查释放激活
            collectorJob?.ensureActive()
            // step6: 弹出新值
            collector.emit(newValue as T)
        }
    } finally {
        // step7: 释放插槽
        freeSlot(slot)
    }
}

SharedFlowImpl#allocateSlot: 获取并初始化一个插槽

class SharedFlowImpl {
    // ...
    protected fun allocateSlot(): S {
        var subscriptionCount: SubscriptionCountStateFlow? = null
        val slot = synchronized(this) {
            // step1: slot数据容量检查
            val slots = when (val curSlots = slots) {
                null -> createSlotArray(2).also { slots = it }
                else -> if (nCollectors >= curSlots.size) {
                    curSlots.copyOf(2 * curSlots.size).also { slots = it }
                } else {
                    curSlots
                }
            }
            var index = nextIndex
            var slot: S
            while (true) {
                // step2: 重用或者创建新插槽
                slot = slots[index] ?: createSlot().also { slots[index] = it }
                index++
                if (index >= slots.size) index = 0
                // step3: 占用插槽
                if ((slot as AbstractSharedFlowSlot<Any>).allocateLocked(this)) break
            }
            nextIndex = index
            // step8: 订阅者数目+1
            nCollectors++
            subscriptionCount = _subscriptionCount
            slot
        }
        subscriptionCount?.increment(1)
        return slot
    }
    
    // ...
 
    internal fun updateNewCollectorIndexLocked(): Long {
        // step6: 等于回放索引
        val index = replayIndex
        // step7:  如果 minCollectorIndex 比当前大,则修正
        if (index < minCollectorIndex) minCollectorIndex = index
        return index
    }
    
    // ...
}

internal class SharedFlowSlot : AbstractSharedFlowSlot<SharedFlowImpl<*>>() {
    @JvmField
    var index = -1L 

    @JvmField
    var cont: Continuation<Unit>? = null 

    override fun allocateLocked(flow: SharedFlowImpl<*>): Boolean {
        // step4: 已经被占用,返回
        if (index >= 0) return false
        // step5: 初始化index
        index = flow.updateNewCollectorIndexLocked()
        return true
    }

    // ...
}

SharedFlowImpl#tryTakeValue: 根据当前 slot#index 获取值

private fun tryTakeValue(slot: SharedFlowSlot): Any? {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val value = synchronized(this) {
        // step1: 尝试获取值
        val index = tryPeekLocked(slot)
        if (index < 0) {
            // step2.1: 获取失败
            NO_VALUE
        } else {
            // step2.2: 获取成功
            val oldIndex = slot.index
            // step2.3: 根据位置获取值
            val newValue = getPeekedValueLockedAt(index)
            // step2.4: slot#index 指向下一个值
            slot.index = index + 1
            // step2.5: 更新 collectorIndex 和 得到可恢复协程数组
            resumes = updateCollectorIndexLocked(oldIndex)
            newValue
        }
    }
    // step3: 恢复协程数组
    for (resume in resumes) resume?.resume(Unit)
    return value
}

SharedFlowImpl#updateCollectorIndexLocked: 当 collector 消失或者改变索引的时候,调用,返回可恢复 collectors 挂起和 emit 挂起的数组

internal fun updateCollectorIndexLocked(oldIndex: Long): Array<Continuation<Unit>?> {
    // step1: 因为不是最慢的收集器,所以对 SharedFlow 中的 index 无影响
    if (oldIndex > minCollectorIndex) return EMPTY_RESUMES
    
    // step2: 计算当前活跃的收集器最小索引
    val head = head
    var newMinCollectorIndex = head + bufferSize
    // step3: 当同步流的时候,可以使用第一个发射器
    if (bufferCapacity == 0 && queueSize > 0) newMinCollectorIndex++
    // step4: 遍历当前 collectors 中的 slot#index, 得到最小的 newMinCollectorIndex
    forEachSlotLocked { slot ->
        if (slot.index >= 0 && slot.index < newMinCollectorIndex) newMinCollectorIndex = slot.index
    }
    // step5: newMinCollectorIndex == minCollectorIndex,代表不止一个慢收集器
    if (newMinCollectorIndex <= minCollectorIndex) return EMPTY_RESUMES 
    
    // step6: 重新计算所有 index
    var newBufferEndIndex = bufferEndIndex
    // step7: 根据当前 nCollectors 获取可恢复 Emitter 数目
    val maxResumeCount = if (nCollectors > 0) {
        // step7.1: 获取当前buffer长度
        val newBufferSize0 = (newBufferEndIndex - newMinCollectorIndex).toInt()
        // step7.2: Emitter数目和剩余buffer数目取最小
        minOf(queueSize, bufferCapacity - newBufferSize0)
    } else {
        // step7.1: 没有收集者,则全弹出
        queueSize
    }
    
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val newQueueEndIndex = newBufferEndIndex + queueSize
    // step8: 有内容恢复
    if (maxResumeCount > 0) { 
        resumes = arrayOfNulls(maxResumeCount)
        var resumeCount = 0
        val buffer = buffer!!
        // step8.1: 遍历Emitter
        for (curEmitterIndex in newBufferEndIndex until newQueueEndIndex) {
            val emitter = buffer.getBufferAt(curEmitterIndex)
            if (emitter !== NO_VALUE) {
                emitter as Emitter
                // step8.2: 存储发送挂起的 cont 并将 Emitter 转 value
                resumes[resumeCount++] = emitter.cont
                buffer.setBufferAt(curEmitterIndex, NO_VALUE)
                buffer.setBufferAt(newBufferEndIndex, emitter.value)
                // step8.3: bufferEnd后移
                newBufferEndIndex++
                // step8.4: 等于恢复数,终止循环
                if (resumeCount >= maxResumeCount) break
            }
        }
    }
    
    
    val newBufferSize1 = (newBufferEndIndex - head).toInt()
    if (nCollectors == 0) newMinCollectorIndex = newBufferEndIndex
    var newReplayIndex = maxOf(replayIndex, newBufferEndIndex - minOf(replay, newBufferSize1))
    if (bufferCapacity == 0 && newReplayIndex < newQueueEndIndex && buffer!!.getBufferAt(newReplayIndex) == NO_VALUE) {
        newBufferEndIndex++
        newReplayIndex++
    }
    
    // step9: 更新索引
    updateBufferLocked(newReplayIndex, newMinCollectorIndex, newBufferEndIndex, newQueueEndIndex)
    // step10: 清除无用的发射器
    cleanupTailLocked()
    // step11: emit恢复不为空(代表有新数据),则查找collect可恢复的协程
    if (resumes.isNotEmpty()) resumes = findSlotsToResumeLocked(resumes)
    // step12: 返回所有可恢复的 emit 和 collect 协程
    return resumes
}

SharedFlowImpl#freeSlot: 重置插槽,推进 SharedFlow#相关index

class SharedFlowImpl {
    // ...
    protected fun freeSlot(slot: S) {
        var subscriptionCount: SubscriptionCountStateFlow? = null
        val resumes = synchronized(this) {
            nCollectors--
            subscriptionCount = _subscriptionCount 
            if (nCollectors == 0) nextIndex = 0
            // step1: 释放
            (slot as AbstractSharedFlowSlot<Any>).freeLocked(this)
        }
        // step2: 恢复挂起的携程
        for (cont in resumes) cont?.resume(Unit)
        // step3: 订阅数减1
        subscriptionCount?.increment(-1)
    }
    // ...
}

internal class SharedFlowSlot : AbstractSharedFlowSlot<SharedFlowImpl<*>>() {
    @JvmField
    var index = -1L

    @JvmField
    var cont: Continuation<Unit>? = null 

    override fun freeLocked(flow: SharedFlowImpl<*>): Array<Continuation<Unit>?> {
        assert { index >= 0 }
        val oldIndex = index
        // step1: 重置 slot#index = -1,代表没有观察者绑定
        index = -1L
        // step2: 重置 slot#cont
        cont = null
        // step3: 推进 SharedFlow#相关index
        return flow.updateCollectorIndexLocked(oldIndex)
    }
    
    // ...
}

总结:

  1. 当溢出策略不为 BufferOverflow.SUSPEND 的时候,可以一直调用 tryEmit, 此时不需要进入挂起状态,但此时会可能会丢失数据
  2. tryEmit 一个新值的时候会进入挂起状态,则 tryEmit 都是为失败
  3. replay == 1BufferOverflow.DROP_OLDEST 的时候,等价于 StateFlow
  4. bufferCapacity 等于 replay + extraBufferCapacity且溢出策略为BufferOverflow.SUSPEND, 代表最快collector速率和最慢collector速率的最大距离
  5. 当没有collector的时候,如果没设置 replay 参数, 则直接丢弃新的数据