MutableSharedFlow 缓存&取消场景

120 阅读2分钟

先把构造函数放这里方便查看

public fun <T> MutableSharedFlow(
    replay: Int = 0,//
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {}
场景1:在没有配置buffer的情况下(extraBufferCapacity为0),且是慢接收者,那么发送的消息会丢吗?

先说答案:消息不会丢,消息会被封装成Emitter放入buffer中,你没看错,即使extraBufferCapacity是0,也会创建buffer;

关键是看消息发送emit函数

注释1:如果发送成功,接收者没有挂起或者buffer有空间(配置extraBufferCapacity大于0的情况下)或者可以抛弃消息,则直接返回true;

注释2:基于我们的场景,那么tryEmit会返回false(因为被挂起了),而emitSuspend最后会走到enqueueLocked,把消息封装成Emitter放入buffer集合中;

override suspend fun emit(value: T) {
    if (tryEmit(value)) return // 1
    emitSuspend(value)//2
}

注释1:因为一开始buffer为null,所有走创建长度为2的分支; 注释2: 如果buffer不会null,且容量不够就会扩容,否则直接保存;

private fun enqueueLocked(item: Any?) {
    val curSize = totalSize
    val buffer = when (val curBuffer = buffer) {
        null -> growBuffer(null, 0, 2)//1
        else -> if (curSize >= curBuffer.size) growBuffer(curBuffer, curSize,curBuffer.size * 2) else curBuffer//2
    }
    buffer.setBufferAt(head + curSize, item)
}

细心的同学可能会发现,即使extraBufferCapacity为0,也有创建buffer集合;

场景2:Activity、Fragment 销毁,那么接收者协程是否也会同步取消;

先说答案:接收者协程会被同步取消,所以不用考虑内存泄露的问题;

关键看collect的代码

注释1:尝试获取value;

注释2:如果获取到了,则跳出内存循环,触发注释3进行回调,否则再次进入循环;

大家可能注意到了,在触发collector.emit之前,会collectorJob?.ensureActive()来判断协程状态;以lifecycleScope注册collect为例,当Activity onDestory执行时,lifecycleScope内部所有协程都cancel,使得isActive判断会返回false; 还有一个关键点,collect是挂起函数,当Activity onDestory执行时collect也是会被cancel的,也就是while循环就结束了。

override suspend fun collect(collector: FlowCollector<T>) {
    val slot = allocateSlot()
    try {
        ......
        while (true) {
            var newValue: Any?
            while (true) {
                newValue = tryTakeValue(slot) // 1
                if (newValue !== NO_VALUE) break// 2
                awaitValue(slot) 
            }
            collectorJob?.ensureActive()
            collector.emit(newValue as T)//3 
        }
    } finally {
        freeSlot(slot)
    }
}

判断协程是否存活,否则抛出异常,这个异常是会被内部消化掉,所以不用担心应用崩溃;

public fun Job.ensureActive(): Unit {
    if (!isActive) throw getCancellationException()
}