Kotlin:Channel 和 Flow 的互相使用

在看 flow 相关的源码,总是看到 FusibleFlow、ChannelFlow、ChannelFlowOperatorImpl 等内容,于是把 Channel 相关内容代码都过一遍

 // flow 的操作符
 public fun <T> Flow<T>.flowOn(context: CoroutineContext): Flow<T> {
     checkFlowContext(context)
     return when {
         context == EmptyCoroutineContext -> this
         this is FusibleFlow -> fuse(context = context)
        else -> ChannelFlowOperatorImpl(this, context = context)
    }
 }
 

Channel是什么

Kotlin 版的 BlockingQueue, 我的个人理解是支持在协程环境下的使用的队列数据结构

根据 capacityonBufferOverflow 获取不同 Channel 实例

public fun <E> Channel(
    capacity: Int = RENDEZVOUS,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
    onUndeliveredElement: ((E) -> Unit)? = null
): Channel<E> =
    when (capacity) {
        RENDEZVOUS -> {
            if (onBufferOverflow == BufferOverflow.SUSPEND)
                // step1.1: 溢出策略为挂起的时候,无须缓存区,挂起就好
                RendezvousChannel(onUndeliveredElement)
            else
                // step1.2: 溢出丢弃策略,设置一个缓存区存放
                ArrayChannel(1, onBufferOverflow, onUndeliveredElement)
        }
        // step2: 设置合并容量模式,因为会自动抛弃最老的数据, 其实我感觉这里只生效 DROP_OLDEST, 要么不判断,要么判断全
        CONFLATED -> {
            require(onBufferOverflow == BufferOverflow.SUSPEND) {
                "CONFLATED capacity cannot be used with non-default onBufferOverflow"
            }
            ConflatedChannel(onUndeliveredElement)
        }
        // step3: 设置无限制模式,底层使用链表实现,因为容量无限制,相对应的溢出也不存在了
        UNLIMITED -> LinkedListChannel(onUndeliveredElement) 
        // step4: 设置缓存模式,如果为挂起策略则容量为64(默认), 反之容量为1,因为剩余2种溢出策略不是丢弃最新的就是丢弃最老的,所以容量1足够
        BUFFERED -> ArrayChannel(
            if (onBufferOverflow == BufferOverflow.SUSPEND) CHANNEL_DEFAULT_CAPACITY else 1,
            onBufferOverflow, onUndeliveredElement
        )
        // step5: 设置指定容量的的时候,创建 ArrayChannel
        else -> {
            if (capacity == 1 && onBufferOverflow == BufferOverflow.DROP_OLDEST)
                step5.1: 与 CONFLATED相同直接复用
                ConflatedChannel(onUndeliveredElement) 
            else
                ArrayChannel(capacity, onBufferOverflow, onUndeliveredElement)
        }
    }

  • Tips: 其实容量还有一个特殊值为 OPTIONAL_CHANNEL, 是 ChannelFlow 内部使用的,代表可选择是否使用Channel,即能不用就不用;场景为如果非 ContinuationInterceptor 上下文切换 并且 无buffer 的时候,不需要 Channel 的介入
class ChannelFlowOperator{
    // ...
    override suspend fun collect(collector: FlowCollector<T>) {
        // step1: 如果channel是可选的且没有buffer(flowOn/flowWith操作符)
        if (capacity == Channel.OPTIONAL_CHANNEL) {
            val collectContext = coroutineContext
            val newContext = collectContext + context 
            // step2: 没有上下文发生改变,则开始原始流收集
            if (newContext == collectContext)
                return flowCollect(collector)
            // step3: 不需要改变协程的线程环境
            if (newContext[ContinuationInterceptor] == collectContext[ContinuationInterceptor])
                return collectWithContextUndispatched(collector, newContext)
        }
        // step4: 创建 channel 开始收集
        super.collect(collector)
    }
    // ...
}

Channel 转成 Flow: 目的让 Channel 具备 Flow 的运算符

Channel 转换 flow 有2种实现方式,本质是同一种实现方式,只是是否可以被多次 collect

  1. ReceiveChannel#consumeAsFlow(): 只能被 collect 一次,多次 collect 时候会抛异常;
  2. ReceiveChannel#receiveAsFlow: 能被 collect 多次,但 Channel 中的一条消息只能被一个 collector 所消费, 俗称扇形消费
public fun <T> ReceiveChannel<T>.receiveAsFlow(): Flow<T> = ChannelAsFlow(this, consume = false)
    
public fun <T> ReceiveChannel<T>.consumeAsFlow(): Flow<T> = ChannelAsFlow(this, consume = true)

private class ChannelAsFlow<T>(
    private val channel: ReceiveChannel<T>,
    private val consume: Boolean,
    context: CoroutineContext = EmptyCoroutineContext,
    capacity: Int = Channel.OPTIONAL_CHANNEL,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : ChannelFlow<T>(context, capacity, onBufferOverflow) {
    
    private val consumed = atomic(false)

    private fun markConsumed() {
        if (consume) {
            check(!consumed.getAndSet(true)) { "ReceiveChannel.consumeAsFlow can be collected just once" }
        }
    }
    
    // ...
    override suspend fun collect(collector: FlowCollector<T>) {
        // step1: 从 receiveAsFlow 或者 consumeAsFlow 构造可知,只会进入此分支
        if (capacity == Channel.OPTIONAL_CHANNEL) {
            // step2: 检查是否消费过
            markConsumed()
            // step3: 把 channel 的交给 collector 收集
            collector.emitAllImpl(channel, consume) 
        } else {
            super.collect(collector) 
        }
    }
    // ... 
}
    
private suspend fun <T> FlowCollector<T>.emitAllImpl(channel: ReceiveChannel<T>, consume: Boolean) {
    //step4: 检查协程是否正常
    ensureActive()
    
    var cause: Throwable? = null
    try {
        while (true) {
            // step5: 获取 channel 中队头 => 由这里知道当前值被此 collector 消费,其他 collector 就获取不到了
            val result = run { channel.receiveCatching() }
            // step6: channel关闭的,则跳出循环
            if (result.isClosed) {
                result.exceptionOrNull()?.let { throw it }
                break
            }
            // step7: 未关闭,弹出获取的值
            emit(result.getOrThrow())
        }
    } catch (e: Throwable) {
        cause = e
        throw e
    } finally {
        // step8: 取消 channel
        if (consume) channel.cancelConsumed(cause)
    }
}

BroadcastChannel: 广播Channel(已经被 StatedFlowSharedFlow 替换)

  1. 当容量 capacity 设置为 Conflated, 实际会生成 ConflatedBroadcastChannel, 相当于溢出的时候每次丢弃最老的,并且内部只有一个状态存储值, 等价于 StatedFlow

  2. 当容量 capacity 设置为大于0的时候,实际生成 ArrayBroadcastChannel, 完全被 SharedFlow 功能代替,并且还多了 replay 功能

ChannelFlow: 目的扩展Flow功能,比如切换协程执行上下文、增加buffer等

FusibleFlow: 目的是提供一个融合方法,可以返回一个进行自身上下文的切换容量溢出策略配置后的流

public interface FusibleFlow<T> : Flow<T> {
    public fun fuse(
        context: CoroutineContext = EmptyCoroutineContext,
        capacity: Int = Channel.OPTIONAL_CHANNEL,
        onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
    ): Flow<T>
}

ChannelFlow: 继承与FusibleFlow

ChannelFlow 其实算 Channel 场景的一个应用;Channel 自身提供了 capacityonBufferOverflow 配置,可以配置当作背压来使用;同时 Channel 本身也是一个生产消费者模型的数据结构,可以在其他协程生产,在 collector 协程进行消费;所以 Kotlin 官方使用 Channel 来实现 flowOnbuffer 流操作符的实现

则开头所贴的代码就好理解了
public fun <T> Flow<T>.flowOn(context: CoroutineContext): Flow<T> {
    checkFlowContext(context)
    return when {
        // step1: 如果没有上下文切换,则返回自身
        context == EmptyCoroutineContext -> this
        // step2: 如果自身支持切换的话,则调用自身方法进行切换
        this is FusibleFlow -> fuse(context = context)
        // step3: ChannelFlowOperatorImpl 继承 ChannelFlow,ChannelFlow 继承 FusibleFlow,走到此分支,则代表是一个普通流,使用 ChanelFlowOperatorImpl 进行包装切换上下文
        else -> ChannelFlowOperatorImpl(this, context = context)
    }
}

ChannelFlowOperatorImpl 源码解析: 从 ChannelFlowOperatorImpl#collect 为起点

internal class ChannelFlowOperatorImpl<T>(
    flow: Flow<T>,
    context: CoroutineContext = EmptyCoroutineContext,
    capacity: Int = Channel.OPTIONAL_CHANNEL,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : ChannelFlowOperator<T, T>(flow, context, capacity, onBufferOverflow) {

    internal val collectToFun: suspend (ProducerScope<T>) -> Unit
        get() = { collectTo(it) }

    // Tips: 从上述 flowOn 可知, 不会对流进行无限包装,则返回原始流即可
    override fun dropChannelOperators(): Flow<T> = flow
    
    // step1: 开始收集内容
    override suspend fun collect(collector: FlowCollector<T>): Unit =
        coroutineScope {
           val channel: ReceiveChannel<T> = produceImpl(this)
            // step10: 传入的 collector 接受 channel 传过来的内容
            collector.emitAll(channel)
        }
    
    public open fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> =
         // step2: 启动一个新的协程去执行 collectToFun,即 collectTo
        scope.produce(context, produceCapacity, onBufferOverflow, start = CoroutineStart.ATOMIC, block = collectToFun)
    
    protected override suspend fun collectTo(scope: ProducerScope<T>) =
        // step8: 创建 collector 进行初始流收集
        flowCollect(SendingCollector(scope))
    
    override suspend fun flowCollect(collector: FlowCollector<T>) =
        flow.collect(collector)
}
    
public class SendingCollector<T>(
    private val channel: SendChannel<T>
) : FlowCollector<T> {
    // step9: 把初始流的内容发射到 channel 中
    override suspend fun emit(value: T): Unit = channel.send(value)
}
    
internal fun <E> CoroutineScope.produce(
    context: CoroutineContext = EmptyCoroutineContext,
    capacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    onCompletion: CompletionHandler? = null,
    @BuilderInference block: suspend ProducerScope<E>.() -> Unit
): ReceiveChannel<E> {
    // step3: 按照容量和溢出策略配置channel
    val channel = Channel<E>(capacity, onBufferOverflow)
    // step4: 获得新的协程环境
    val newContext = newCoroutineContext(context)
    // step5: channel 和 协程上下文
    val coroutine = ProducerCoroutine(newContext, channel)
    // step6: 注册协程完成回调
    if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion)
    // step7: 启动协程,会执行 ChannelFlowOperator#collectToFun => ChannelFlowOperator#collectTo
    coroutine.start(start, coroutine, block)
    return coroutine
}

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值