kotlin中的flow(四)-源码解析

10,700 阅读4分钟

关于kotlin中的flow(一)-flow的基础用法

关于kotlin中的flow(二)-SharedFlow和StateFlow

关于kotlin中的flow(三)-使用flow来实现eventbus

这一章主要结合flow源码对普通flow进行一些分析。 先来看flow定义:

public interface Flow<out T> {
    /**
     * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
     * This method should never be implemented or used directly.
     *
     * The only way to implement the `Flow` interface directly is to extend [AbstractFlow].
     * To collect it into a specific collector, either `collector.emitAll(flow)` or `collect { ... }` extension
     * should be used. Such limitation ensures that the context preservation property is not violated and prevents most
     * of the developer mistakes related to concurrency, inconsistent flow dispatchers and cancellation.
     */
    @InternalCoroutinesApi
    public suspend fun collect(collector: FlowCollector<T>)
}

flow的定义很简单,只有一个collect方法,里面是collector,这个collector就是用来接收数据的真实对象了。在flow的定义上有一句是不能直接使用flow接口,而是应该使用AbsctracFlow 抽象类:

@FlowPreview
public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {

    @InternalCoroutinesApi
    public final override suspend fun collect(collector: FlowCollector<T>) {
        val safeCollector = SafeCollector(collector, coroutineContext)
        try {
            collectSafely(safeCollector)
        } finally {
            safeCollector.releaseIntercepted()
        }
    }
    public abstract suspend fun collectSafely(collector: FlowCollector<T>)
}

AbstractFlow主要的作用就是在flow的基础上用SafeCollector进行发送,具体内容需要看SafeCollector类:

internal actual class SafeCollector<T> actual constructor(
    @JvmField internal actual val collector: FlowCollector<T>,
    @JvmField internal actual val collectContext: CoroutineContext
) : FlowCollector<T>, ContinuationImpl(NoOpContinuation, EmptyCoroutineContext), CoroutineStackFrame {
....

override suspend fun emit(value: T) {
    return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
        try {
            emit(uCont, value)
        } catch (e: Throwable) {
            // Save the fact that exception from emit (or even check context) has been thrown
            lastEmissionContext = DownstreamExceptionElement(e)
            throw e
        }
    }
}

private fun emit(uCont: Continuation<Unit>, value: T): Any? {
    val currentContext = uCont.context
    currentContext.ensureActive()
    // This check is triggered once per flow on happy path.
    val previousContext = lastEmissionContext
    if (previousContext !== currentContext) {
        checkContext(currentContext, previousContext, value)
    }
    completion = uCont
    return emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
}

}

重点是safeCollector里的emit方法。 suspendCoroutineUninterceptedOrReturn这个方法是一个比较重要的方法,参考这篇文章 Kotlin协程分析(二)——suspendCoroutineUninterceptedOrReturn。 来看下面一个emit方法。

  • 首先获取当前协程的context:CoroutineContext(通过suspendCoroutineUninterceptedOrReturn回调返回)
  • 首先确保当前处于活跃状态(currentContext.ensureActive())
  • 然后得到上一次发送时的CoroutineContext(),如果当前context和上一次emit的context不同,则进行检查:
private fun checkContext(
    currentContext: CoroutineContext,
    previousContext: CoroutineContext?,
    value: T
) {
    if (previousContext is DownstreamExceptionElement) {
        exceptionTransparencyViolated(previousContext, value)
    }
    checkContext(currentContext)
    lastEmissionContext = currentContext
}

这个方法主要做了三步:

  • 上一个contex是否是一个exceptionElement,如果是,则调用exceptionTransparencyViolated方法:
private fun exceptionTransparencyViolated(exception: DownstreamExceptionElement, value: Any?) {
    /*
     * Exception transparency ensures that if a `collect` block or any intermediate operator
     * throws an exception, then no more values will be received by it.
     * For example, the following code:
     * ```
     * val flow = flow {
     *     emit(1)
     *     try {
     *          emit(2)
     *     } catch (e: Exception) {
     *          emit(3)
     *     }
     * }
     * // Collector
     * flow.collect { value ->
     *     if (value == 2) {
     *         throw CancellationException("No more elements required, received enough")
     *     } else {
     *         println("Collected $value")
     *     }
     * }
     * ```
     * is expected to print "Collected 1" and then "No more elements required, received enough" exception,
     * but if exception transparency wasn't enforced, "Collected 1" and "Collected 3" would be printed instead.
     */
    error("""
        Flow exception transparency is violated:
            Previous 'emit' call has thrown exception ${exception.e}, but then emission attempt of value '$value' has been detected.
            Emissions from 'catch' blocks are prohibited in order to avoid unspecified behaviour, 'Flow.catch' operator can be used instead.
            For a more detailed explanation, please refer to Flow documentation.
        """.trimIndent())
}

可以看到,这个方法直接抛出一个错误,这一步主要目的是:如果一个collect代码块或者操作符抛出了错误,那么接下来上游不会再发送任何消息,因为此时previousContext是一个excetion,会被过滤掉。(collector的emit回调会调用我们订阅flow时使用的collect方法,把值传递过来,所以最上面的try捕获到emit错误以后会执行lastEmissionContext = DownstreamExceptionElement(e))。

  • 第二步继续checkContext:
@JvmName("checkContext") // For prettier stack traces
internal fun SafeCollector<*>.checkContext(currentContext: CoroutineContext) {
    val result = currentContext.fold(0) fold@{ count, element ->
        val key = element.key
        val collectElement = collectContext[key]
        if (key !== Job) {
            return@fold if (element !== collectElement) Int.MIN_VALUE
            else count + 1
        }

        val collectJob = collectElement as Job?
        val emissionParentJob = (element as Job).transitiveCoroutineParent(collectJob)
        /*
         * Code like
         * ```
         * coroutineScope {
         *     launch {
         *         emit(1)
         *     }
         *
         *     launch {
         *         emit(2)
         *     }
         * }
         * ```
         * is prohibited because 'emit' is not thread-safe by default. Use 'channelFlow' instead if you need concurrent emission
         * or want to switch context dynamically (e.g. with `withContext`).
         *
         * Note that collecting from another coroutine is allowed, e.g.:
         * ```
         * coroutineScope {
         *     val channel = produce {
         *         collect { value ->
         *             send(value)
         *         }
         *     }
         *     channel.consumeEach { value ->
         *         emit(value)
         *     }
         * }
         * is a completely valid.
         */
        if (emissionParentJob !== collectJob) {
            error(
                "Flow invariant is violated:\n" +
                        "\t\tEmission from another coroutine is detected.\n" +
                        "\t\tChild of $emissionParentJob, expected child of $collectJob.\n" +
                        "\t\tFlowCollector is not thread-safe and concurrent emissions are prohibited.\n" +
                        "\t\tTo mitigate this restriction please use 'channelFlow' builder instead of 'flow'"
            )
        }

        /*
         * If collect job is null (-> EmptyCoroutineContext, probably run from `suspend fun main`), then invariant is maintained
         * (common transitive parent is "null"), but count check will fail, so just do not count job context element when
         * flow is collected from EmptyCoroutineContext
         */
        if (collectJob == null) count else count + 1
    }
    if (result != collectContextSize) {
        error(
            "Flow invariant is violated:\n" +
                    "\t\tFlow was collected in $collectContext,\n" +
                    "\t\tbut emission happened in $currentContext.\n" +
                    "\t\tPlease refer to 'flow' documentation or use 'flowOn' instead"
        )
    }
}

fold会在context的父contex中递归调用后面的lamda(CoroutineContex有一个实现是CombineContext,它比较接近链表,可以将两个context结合起来,父context定义为子contex中的一个left对象,fold方法会递归调用left(CombineContext)的.fold方法,直到context为element,调用element自身的flod方法),可以理解成遍历整个context链并调用后面的lamda。这个方法里面主要是协程上面的一些检测,等后面分析协程的时候会具体分析。

  • 将当前context赋值给lastContext,更新context。 回到emit方法来,执行完checkContext以后,执行:
completion = uCont

completion是collector的协程,我理解的是,将当前emit调用者的协程赋值给当前collector,保持两者统一。 最后调用

emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)

emitFun的定义:

@Suppress("UNCHECKED_CAST")
private val emitFun =
    FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>

主要是对emit做了一些封装,将collector、Continuation、value等封装到一起。 到此为止emit看完了。

冷流的真相

关于flow方法

flow方法是最常用生成flow的方法,它的源码是:

public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)

// Named anonymous object
private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
    override suspend fun collectSafely(collector: FlowCollector<T>) {
        collector.block()
    }
}

可以看到,flow方法就是生成一个AbscractFlow,并且把lamda定义为collctor的拓展回调,也就是在lamda中使用emit其实就是调用上面SafeCollector的emit方法,当AbscractFlow.collectSafely触发时,才会回调最外部传入的lamda表达式,也就是说触发collect才会触发lamda->进而触发lamda中的emit

关于collect方法

collect方法是最常用的订阅flow的方法了,调用collect以后flow才会正式发送数据,先看一下collect的源码:

public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
    collect(object : FlowCollector<T> {
        override suspend fun emit(value: T) = action(value)
    })

他直接生成一个collector对象并回调对象的emit也就是外部传入的lamda,也就完成了将emit中的数据传给外部lamda。然后它调用了collect方法,结合flow中的源码,触发collect方法才会触发flow{}中的lamda,于是实现了冷流。

冷流、热流的区别

  • 冷流的emit是在collector中,热流的emit在flow自身之中,
  • 冷流的collect会奖collector中的emit回调中的值回传给外部lamda,热流的collect会循环挂起尝试从队列中获取发送的值。 (似乎冷流中不存在背压,这个不是太清楚,希望有人帮我分析分析)