核心原理
Flow 中最重要的函数一共有三个:emit,collect, flow。其中 flow 最简单,它就是一工厂方法,生产并返回 Flow 对象。不使用 flow 函数直接 new Flow 效果也一样。
使用这三个函数可以构建一个最基本的 flow 使用
val f = flow<Int> { // lambda 1
emit(1)
}
f.collect { // lambda 2
Log.e(TAG, "onCreate: $it")
}
上面代码表明了 Flow 最核心的执行流程是:调用 collect 会触发 lambda 1 的执行,调用 emit 会触发 lambda 2 的执行。这是 Flow 最最核心的流程。
如果 debug 跟代码可以发现,collect 直接调用了 lambda 1,而 emit 直接调用了 lambda 2。它们就是普普通通的方法调用,没有任何神奇操作。因此 可以直接 try-catch 住对方抛出的异常,这部分可参考 catch 操作符原理,这就是操作符 first 函数的底层实现原理:它通过抛出一个特殊异常中止了整个 emit 流程.
我们常说 Flow 是冷流,只有在被使用时才会调用它的生产函数(即上面代码中的 lambda 1)。原理很简单:如果没有调用 collect(),那么 lambda 1 只是被定义而没有被调用,当然不会执行,也就不会生产数据。
常见误区
-
Flow 是同步的,它不是异步的。一般 Flow 会用在生产者消费者中,它的同步说明生产者和消费者运行在同一线程。消费者调用 collect 方法,collect() 又是方法调用形式一层层往上调用到最初生产者,最初生产者又通过 emit 一层层往下传递到消费者,整个过程并不会切换线程。
-
虽然 flow 是同步的,但 flow 本身要运行在协程之中,因此它不一定运行在主线程中。collect 是 suspend 函数只能运行在协程中,而 flow 的整个传递链路是普通方法调用,所以最初生产者、操作符都会在某个协程中被调用。比如下面代码
private fun usersFlow(): Flow<String> = flow { repeat(2) { // currentCoroutineContext 是 suspend 函数 // 但整个 usersFlow 并没有任何协程相关的配置,依旧可以调用 suspend 函数 // 就这说明 flow 整个运行在某个协程中 val ctx = currentCoroutineContext() val name = ctx[CoroutineName]?.name emit("User$it in $name") } }
flow, channelFlow 与 callbackFlow
三者都是 Flow 的工厂方法,返回 Flow 对象。而 Flow 是冷流,所以只有消费者订阅时才开始生产数据
- flow 返回的 Flow 是同步的,即消费者与生产者相当于方法直接调用,不存在线程切换。lambda 表达式是 FlowCollector 的扩展函数,调用的 emit 就是 FlowCollector 中的 emit 方法
- channelFlow: lambda 表达式是 ProducerScope 的扩展函数,它是 CoroutineScope 的子类,所以可以启动协程,也就是说生产者与消费者可处于不同线程。当然 channelFlow 了可当作普通的 flow 使用,使用效果与 flow 完全一样
- callbackFlow 用于将 callback 转换成 flow,callback 单次的使用挂起函数,多次的才使用 flow
如下代码虽然用了 channelFlow 但并没有切换线程,所以消费者中的 delay 依旧会阻塞生产中,即使有 buffer。
channelFlow<Int> {
repeat(10) {
send(Random(System.currentTimeMillis()).nextInt())
}
}.buffer(10).collect {
delay(2000)
Log.e(TAG, "collect: ${Thread.currentThread()} $it")
}
同样的代码,只要将 channelFlow 中代码换成如下代码,可以发现生产者会很迅速地生产完所有数据,但消费者还是隔 2s 消费一次。
withContext(Dispatchers.Default) {
repeat(10) {
Log.e(TAG, "sendResult: ${Thread.currentThread()}")
scope.send(Random(System.currentTimeMillis()).nextInt())
}
}
从上面两段代码可以很明显地看出 flow 与 channelFlow 的区别:前者用于同步生产,后者用于异步(因为可用协程)。
flowOn
切换上游运行的协程环境
上面说过,flow 的所有代码都运行在某个协程中,既然是协程那么然有 CoroutineContext,也必须可以修改某中某些元素。这就是 flowOn 操作符的意义。
onEmpty
指的是它的直接上游是否生产有数据,而不是最初的生产者是否生产有数据
- onEmpty 中可以调用 emit 往下游发送数据。能否调用 emit 往下游发送数据主要看 lambda 表达式是不是 FlowCollector 的扩展函数。下面 catch, onCompletion 都可以调用 emit。
示例
如下代码,其中第一段代码中 onEmpty 会执行。虽然最初生产者 (lambda 1) 生产有数据,但被 filter 过滤了,所以 onEmpty 的直接上游是没有数据的,就会执行
第二段代码中第二个 onEmpty 不会执行,因为它的直接上游 onEmpty 有数据产生,虽然最初的生产者没有生产数据。
// 第一段代码
flow<Int> { // lambda 1
emit(1)
}.filter {
it >= 2
}.onEmpty {
Log.e(TAG, "onCreate: onEmpty")
}.collect { // lambda 2
Log.e(TAG, "onCreate: $it")
}
// 第二段代码
flow<Int> {
}.onEmpty {
emit(1) // 生产有数据,所以第二个 onEmpty 不会执行
}.onEmpty {
emit(2)
}.collect {
Log.e(TAG, "onCreate: collect $it")
}
源码
onEmpty 的内部执行逻辑如下:下游调用 onEmpty() 返回的 Flow 对象的 collect() 方法,就会调用到 unsafeFlow() 返回值的 collect() 方法,进而触发 onEmpty 中 lambda 1 的执行,而 lambad 1 中的参数 this 也指向了下游传入的对象(一般就是 collect() 函数后面跟的 lambda 表达式)。
public fun <T> Flow<T>.onEmpty(
action: suspend FlowCollector<T>.() -> Unit
): Flow<T> = unsafeFlow { this: FlowCollector -> // lambda 1
var isEmpty = true
// 调用上游的 collect 方法,从而触发上游生产数据
collect { it: T ->
isEmpty = false
// 调用 emit,将数据又传递给下游
// 调用 emit() 方法的对象就是 lambda 1 中的 this
// 也即下游 collect() 中定义的 lambda 表达式
emit(it)
}
// collect 是一个 suspend 方法,执行到此步时说明上游生产者已将所有数据发送下去
// 所以 isEmpty 就可以表示上游是否给了数据
if (isEmpty) {
val collector = SafeCollector(this, currentCoroutineContext())
try {
collector.action()
} finally {
collector.releaseIntercepted()
}
}
}
internal inline fun <T> unsafeFlow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
return object : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
collector.block()
}
}
}
onCompletion
整个 flow 结束时的回调
示例
-
它与 onEmpty 最大的区别在于:onEmpty 只关注它的直接上游是否产生有数据,而 onCompletion 关注整个 flow 链。
-
如果有多个 onCompletion,这些 onCompletion 会顺序执行
-
无论 onCompletion 位于何处,它都是 flow 链中最后一环,而且从上游传下来的数据并不会经过它,但它调用的 emit 会传递给 collect 或其它终止操作符
如下代码:
flow<Int> {
}.onEmpty {
Log.e(TAG, "onCreate: empty")
emit(3)
}.onCompletion {
Log.e(TAG, "onCreate: onCompletion 1 ")
emit(1)
}.onCompletion {
Log.e(TAG, "onCreate: onCompletion 2")
emit(2)
}.collect {
Log.e(TAG, "onCreate: collect $it")
}
// output
onCreate: empty // 因为 flow{} 中没有数据,所以 onEmpty 最先执行
onCreate: collect 3 // onEmpty 中 emit(3),直接传递到最终的 collect 中
onCreate: onCompletion 1 // 整个流程结束,所以回调 onComplete
onCreate: collect 1 // 第一个 onComplete 调用 emit() 触发最后 collect 的执行
onCreate: onCompletion 2 // 第二个 onComplete 被触发
onCreate: collect 2 // 第二个 onComplete 中调用了 emit(),触发最后 collect 执行
源码
onCompletion 内部逻辑:直接将下游的消费者(FlowCollector 对象)传递给上游,所以上游调用 emit() 时就直接到达它的下游,因此所有的数据并不会经过 onCompletion。
public fun <T> Flow<T>.onCompletion(
action: suspend FlowCollector<T>.(cause: Throwable?) -> Unit
): Flow<T> = unsafeFlow { this : FlowCollector ->
try {
// 调用的是 onCompletion 的所属者
// 传递的是 this,所以上游调用的 emit() 就是 this 中的 emit
// 数据就会直达 onCompletion 的下游
collect(this)
} catch (e: Throwable) {
// ....
}
// 正常回调
// 如果 action 里面调用了 emit(),相当于调用 sc 的 emit
// 而 sc 最终又转交给 this
// 所以 onCompletion 中的 emit() 也会到下游中
val sc = SafeCollector(this, currentCoroutineContext())
try {
sc.action(null)
} finally {
sc.releaseIntercepted()
}
}
catch
捕获上游 flow 链中的任何异常
使用
catch 只能捕获上游异常,无法捕获下游异常。因此如果在 collect{} 中出现异常,catch 无能为力 —— 因为 collect 是终止操作符,catch 无法在它后面调用。
如果需要捕获 collect{} 中的异常,可以将 collect{} 中的操作移动到 onEach{} 中,在 onEach{} 后面调用 catch。如下:
flow.onStart { println("Before") }
.onEach {
// 原 collect 中的逻辑
}
.catch {
// 异常处理逻辑
}
// 空 collect,触发生产者开始生产数据
// 这个是必须有的
.collect()
源码
看 catch 源码主要看 flow 中关于异常的处理逻辑
public fun <T> Flow<T>.catch(action: suspend FlowCollector<T>.(cause: Throwable) -> Unit): Flow<T> =
flow {
val exception = catchImpl(this)
if (exception != null) action(exception)
}
internal suspend fun <T> Flow<T>.catchImpl(
collector: FlowCollector<T>
): Throwable? {
var fromDownstream: Throwable? = null
try {
collect {
try {
// 捕获 emit 异常,保证下游异常也能被捕获
collector.emit(it)
} catch (e: Throwable) {
// 此处重新抛出下游异常,会被外面 catch 住
fromDownstream = e
throw e
}
}
} catch (e: Throwable) {
val fromDownstream = fromDownstream
// 如果是下游异常,重新抛一次
// 如果是取消的,也要重新抛出次
if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
throw e // Rethrow exceptions from downstream and cancellation causes
} else {
// 如果下游没发生异常,那就是上游发生了异常,将异常返回
// 并最终传给 catch
if (fromDownstream == null) {
return e
}
// 如果下游有异常,都需要抛出去,取消整个 flow 链
if (e is CancellationException) {
fromDownstream.addSuppressed(e)
throw fromDownstream
} else {
e.addSuppressed(fromDownstream)
throw e
}
}
}
return null
}
dropWhile
丢失所有元素,直到第一个不满足条件的元素出现。因此,可以实现某些元素的首先出现。
看名字叫 dropUntil 是不是更合适些。在 Flow::shareIn() 的源码中,调用过程中有如下代码。其中使用了 dropWhile 表示直到 it == start 时才放行,保证了某个元素的最先位置。
其源码很简单
stateIn 与 shareIn
将普通 Flow 转换成 StateFlow 与 SharedFlow
两者都需要 CoroutineScope 类型的参数,用于指定上游生产者运行的协程环境,这一点与 launchIn 操作符作用一样。以 stateIn 为例说明源码
其它操作符
省略