关于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会循环挂起尝试从队列中获取发送的值。 (似乎冷流中不存在背压,这个不是太清楚,希望有人帮我分析分析)