前言:在使用默认的 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: 重放数目,重放bufferEndIndex前replay个元素,bufferEndIndex-1为最快的collectorbufferCapacity: 缓存区总大小,用于决定最快的收集器和最慢收集器相差的最大差距,由replay和extraBufferCapacity相加而得onBufferOverFlow: 缓存溢出策略,可设置三种DROP_OLDEST: 丢弃最老的DROP_LASTEST: 丢弃最新的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 + queueSizebufferEndIndex(缓存弹出过的数据最后的索引): head + bufferSizequeueEndIndex(buffer最后的索引): head + bufferSize + queueSize
SharedFlowImpl#buffer 官方定义结构图
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)
}
// ...
}
总结:
- 当溢出策略不为
BufferOverflow.SUSPEND的时候,可以一直调用tryEmit, 此时不需要进入挂起状态,但此时会可能会丢失数据 - 当
tryEmit一个新值的时候将会进入挂起状态,则tryEmit都是为失败 - 当
replay == 1和BufferOverflow.DROP_OLDEST的时候,等价于 StateFlow bufferCapacity等于replay + extraBufferCapacity且溢出策略为BufferOverflow.SUSPEND, 代表最快collector速率和最慢collector速率的最大距离- 当没有
collector的时候,如果没设置replay参数, 则直接丢弃新的数据