引言
本文源码基于Coroutine1.3.4。Kotlin Coroutine 1.3.2 新增了flow库,通过官方描述:Flow — cold asynchronous stream with flow builder and comprehensive operator set (filter, map, etc). 可知Flow十分类似RxJava中的Obsaverble。下面通过简单例程来看一下它的使用:
fun main() {
runBlocking {
flow {
for (i in 1..5) {
emit(i)
}
}.collect {
println("$it")
}
}
}
这段代码逻辑很简单,但是却包含了Flow上游流的生成以及下游流的收集过程,下面深入看一下这一过程是如何实现的。
上游构造SafeFlow
在上面代码中先是通过flow()函数创建了Flow对象:
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
val safeCollector = SafeCollector(collector, coroutineContext)
try {
safeCollector.block()
} finally {
safeCollector.releaseIntercepted()
}
}
}
可以看到flow()函数返回了一个SafeFlow对象,其继承了Flow类,重写了父类的collect()方法。除此之外就没有其他任何操作了,这里我们明白了“cold asynchronous stream ”的意思了,即上游构造Flow对象时其block内部的逻辑是没有执行的,只有在下游调用了SafeFlow的collect()方法后才会执行block中的逻辑。
下游collect()逻辑
在流下游收集时调用的collect()函数并不是SafeFlow中重写的那个函数,实际上中间做了中转,最后还是会调用到SafeFlow中的collect()函数。看一下main()函数中调用的collect()函数的定义:
//FlowKt.class
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)
})
可以看到进一步调用SafeFlow中的collect()函数,其传入的参数是类型为FlowCollector的匿名内部对象,重写父类FlowCollector的 emit(value: T) = action(value),这里的action即为下游collect {}中的逻辑,从这里可以知道当上游调用FlowCollector实现类中的emit(value)方法时,也即会执行下游collect {}中的action(value)逻辑,这也是一个发射即接收的操作,中间不设及任何转换。
重新回到上一节中SafeFlow中collect()函数的代码。其先是构造了一个SafeCollector对象safeCollector,注意其collector参数是这里的FlowCollector的匿名内部对象。接着执行safeCollector.block()即会执行flow {}中的代码,其中逻辑是五次执行emit(i),也就会执行到SafeCollector中的emit()方法:
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
}
}
}
可以看到这里挂起协程,并将当前Continuation对象uCont和value传给另一个emit()函数:
/**
* This is a crafty implementation of state-machine reusing.
* First it checks that it is not used concurrently (which we explicitly prohibit) and
* then just cache an instance of the completion in order to avoid extra allocation on each emit,
* making it effectively garbage-free on its hot-path.
*/
private fun emit(uCont: Continuation<Unit>, value: T): Any? {
val currentContext = uCont.context
// 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>)
}
通过注释可以知道这里巧妙运用了状态机。第一步是检查并发,这里不允许并发;第二步是将Continuation对象保存在completion字段中,避免额外分配;第三步是调用emitFun()进一步处理。
//SafeCollectorKt.class
@Suppress("UNCHECKED_CAST")
private val emitFun =
FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>
//FlowCollector.kt
public interface FlowCollector<in T> {
public suspend fun emit(value: T)
}
这里大概是将FlowCollector中只有一个参数的emit(value: T)方法转换成有三个参数的方法,看一下反编译成Java代码或许更清楚一点:
private static final Function3 emitFun = (Function3)TypeIntrinsics.beforeCheckcastToFunctionOfArity(new Function3() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1, Object var2, Object var3) {
return this.invoke((FlowCollector)var1, var2, (Continuation)var3);
}
@Nullable
public final Object invoke(@NotNull FlowCollector p1, @Nullable Object p2, @NotNull Continuation continuation) {
InlineMarker.mark(0);
Object var10000 = p1.emit(p2, continuation);
InlineMarker.mark(2);
InlineMarker.mark(1);
return var10000;
}
public final KDeclarationContainer getOwner() {
return Reflection.getOrCreateKotlinClass(FlowCollector.class);
}
public final String getName() {
return "emit";
}
public final String getSignature() {
return "emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;";
}
}, 3);
在invoke()方法中p1是传如的FlowCollector的匿名内部对象,p2即为发射的value值,continuation是传入的续体,最后返回FlowCollector的匿名内部对象emit()方法的执行结果。下面再看一下emit()方法:
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)
})
其进一步调用lambda表达式action,action即是collect {}中的代码,因此会执行 println("$it")。至此一次发射与接收的流程完成,即完成一次for循环的emit过程,接着会重新执行第二次emit过程。