launchIn
终止操作符,在指定的 scope 中启动 flow 生产
flowOn
影响上游操作符的执行 contxt
viewModelScope.launch {
val f = flow {
emit(1)
e("thread = ${Thread.currentThread().name}")
}
f.onEach {
e("onEach = $it")
e("onEach = ${Thread.currentThread().name}")
}
// 影响 flow 上游所执行的 context,都会运行在子线程中
.flowOn(threadPool.asCoroutineDispatcher())
// flowOn 后面还会运行在 viewModeScope 中,即主线程中
.onEach {
e("2onEach = $it")
e("2onEach = ${Thread.currentThread().name}")
}.collect {
}
}
// output
onEach = 1
onEach = pool-3-thread-1
2onEach = 1
2onEach = main
thread = pool-3-thread-1
callbackFlow
将异步 callback 转成冷流 flow,流结束时必须调用 close,使用 trySend 发送数据
private fun testCallbackFlow() = callbackFlow {
testCallback {
e("testFlow")
trySend(it)
close()
}
awaitClose {
e("close")
}
}
suspendCancellableCoroutine
构造一个挂起函数,并可在任何时间恢复它。它并不是 flow 操作符,只不过跟 callbackFlow 作用类似,放在一起说:suspendCancellableCoroutine更适合一次性异步操作,而callbackFlow更适合持续产生多个值的事件流。
它是协程中很重要的一个函数,很多地方都使用了该函数。它最常用的使用为将 callback 转成 suspend 函数
suspend fun fetchUserData(userId: String): User = suspendCancellableCoroutine { continuation ->
// 1. 启动异步操作
api.getUser(userId, object : ApiCallback<User> {
override fun onSuccess(result: User) {
// 2. 成功时恢复协程
continuation.resume(result)
}
override fun onFailure(error: Throwable) {
// 3. 失败时恢复协程并抛出异常
continuation.resumeWithException(error)
}
})
// 4. 设置取消回调
continuation.invokeOnCancellation {
api.cancelRequest(userId) // 如果协程被取消,则取消请求
}
}
reduce
终止操作符,用于对 flow 中的元素进行连续累加操作,其最终返回的是对所有元素操作后的值。参数含义如下
// accumulator 第一次时为 flow 的第一个元素
// 其余时间为 lambda 表达式的返回值
// value 第一次时为 flow 的第二个元素,
// 其余时间为 flow 中的其它元素
reduce { accumulator, value ->
accumulator + value
}
fold
与 reduce 类似,只不过 accumulator 第一次的值是 fold 中指定的参数
buffer
缓存上游数据。 因为 flow 是串行的,无论 emit 还是 collect 中发生挂起,都会延迟整个链路的执行。通过 buffer 可以在上游执行过快时,缓存上游数据,不阻塞上游的执行,然后依次传递给下游
conflate
只缓存上游最新数据,会丢弃中间值。buffer 会缓存上游所有数据,但 conflate 只缓存最新的,新数据到来时会删除旧的数据
collectLatest
collect 变种。当上游有新数据到来时,会取消当前 collect 的执行,转而执行最新的数据。conflate 会缓存最新数据直到 collect 执行完,collectLatest 会直接取消 collect 执行,这是两者最大的区别
combine
合并 flow:当某个 flow 下发新数据时,会结合其余 flow 的最新数据一起下发至下游。当 flowA 有新数据时,会和 flowB 中的最新数据组合发给下游;如果 flowA 又有新数据,还是会有 flowB 的最新数据组合发给下游。如下
val f = flow {
emit(1)
delay(10)
emit(2)
}
val b = flow {
emit("A")
delay(1000)
emit("B")
}
val c = f.combine(b) { a, b ->
Log.e("TAG", "test3: $a $b")
"$a $b"
}
// output
// 1 A
// 2 A
// 2 B B 发送时 f 的最新数据是 2,所以会和 2 组合一起发至下游
在有多个判断条件的场景时可使用 combine 简化操作:每一个条件发生变化时都往下游发送数据,下游根据这些条件的最新数据执行逻辑操作
zip
与 combine 类似,但只有所有 flow 中都有新数据时才会往下游发送,combine 是只有要新数据(任意一个流中有就行)就会发送数据。如下:
val flow = flow {
emit(1)
delay(1000)
emit(2)
}
val flow2 = (10..20).asFlow()
lifecycleScope.launch {
flow.zip(flow2) { f, s ->
"f = $f, s = $s"
}.collect {
// 只有 1,10;2,11 两组数据
// 因为 flow 只 emit 了两个数据
// 只有两个 flow 都有变化时才会更新,所以下游只能收到两个数据
e("it = $it")
}
}
merge
将多个 flow 合成一个,要求这些 flow 必须 emit 相同类型的数据,一旦这些 flow 中任何一个有新数据到来,都会往下游发送
combine 会向下游发送一个数据组(数据的个数与 flow 个数相同),但 merge 只会一次往下游发送一条数据
val flow = flow {
emit(1)
delay(1000)
emit(2)
}
val flow2 = (10..12).asFlow()
val flow3 = (30..33).asFlow()
lifecycleScope.launch {
merge(flow, flow2, flow3.onEach {
delay(10)
}).collect {
// 1,10-12,30-33,2
// 可以发现只要有任何一个 flow emit 了数据,下游就会收到
e("it = $it")
}
}
flat 系列
flat 系列一共有三个操作符:flatMapConcat,flatMapMerge,flatMapLatest。
三个操作符中 flat 指将一个二维数据拍平成一维数据,即将 List<Flow<Int>> 转换成 List<Int>,map 指对数据转换,第三个指数据合并策略
flatMapConcat:按顺序将 map 出的多个 flow 合成一个 flow,第一个 flow 中的元素一定会最先传递给下游
// flow 会按顺序 emit 1,2
flow.flatMapConcat {
flow {
emit(it * 2)
delay(2)
emit(it * 2 + 1)
}
}.collect {
// 最终会依次输出 2,3,4,5
Log.e("TAG", "test2: $it")
}
flatMapMerge:无序地将 map 出的多个 flow 合成一个 flow。这些 map 出的 flow 哪一个先 emit 了,处理时就直接将它 emit 的值 emit 给下游。可以指定参数 concurrency:表示并发处理多少个 flow。如果将值指定成 1,其效果与 flatMapConcat 一样
// 同样逻辑,将 flatMapConcat 换成 flatMapMerge
flow.flatMapMerge {
flow {
emit(it * 2)
delay(2)
emit(it * 2 + 1)
}
}.collect {
// 输出的 list 将是无序的,有可能是 2,4,3,5
Log.e("TAG", "test2: $it")
}
- flatMapLatest:后 map 出的 flow 会取消前 map 出的 flow。比如上游 emit 了两个元素,会 map 出两个 flow。当第二个 flow 到来时,就会取消第一个 flow,哪怕它并没有处理完
flow {
emit(1)
delay(1)
emit(2)
}.flatMapLatest {
flow {
emit(it * 2)
delay(2)
emit(it * 2 + 1)
}
}.collect {
// 依次输出 2,4,5
// 2 对应的 flow 到来时,1 的 flow 还没有执行完,但会被取消
// 所以 3 就没法 emit 到最终的 collect
Log.e("TAG", "test2: $it")
}
while 系列
flow 的有一些操作符会有一个加 while 的同构操作符,如 transform 有 transformWhile,take 有 takeWhile 等
while 的作用是:只有满足条件的数据才会发往下游,不满足时会取消整个 flow,前半部分作用与 filter 类似,后半部分是 filter 不具备的功能。
flow {
repeat(6) {
e("it = $it")
emit(it)
}
}.transformWhile {
emit("$it - $it")
// 当 it = 4 时,不满足条件,会取消上游的 flow
// 因此上游的 repeat 5 并不会输出
it < 3
}.collect {
e("collect = $it")
}
// output
it = 0
collect = 0 - 0
it = 1
collect = 1 - 1
it = 2
collect = 2 - 2
it = 3
collect = 3 - 3
flow 取消原理
在 flow 的操作符中经常有一些满足某个条件后,取消上游不再下发操作的操作。其实现原理主要是在满足条件后抛出一个指定异常,从而退出 flow 中的后续生产流程。比如上面的 while 系列,take 方法
// take 方法源码
public fun <T> Flow<T>.take(count: Int): Flow<T> {
return flow {
// 下游 collect 会执行到该 lambda 表达式
var consumed = 0
try {
collect { value ->
// 统计次数
if (++consumed < count) {
// 未满足次数,直接下发至下游
return@collect emit(value)
} else {
// 最后一条数据,emit() 后会抛出异常
return@collect emitAbort(value)
}
}
} catch (e: AbortFlowException) {
// 这里并不是直接 catch 住 emitAbort 中的抛出的异常
// collect 中的 Lambda 表达式是 suspend 函数
// 其抛出异常后会在恢复时再次抛出,即 emit 方法中会再次抛出
// 然后由 catch 抓住 emit() 再次抛出的异常
e.checkOwnership(owner = this)
}
}
}
private suspend fun <T> FlowCollector<T>.emitAbort(value: T) {
emit(value)
throw AbortFlowException(this)
}
stateIn 与 shareIn
将普通的 flow 转换成 StateFlow 或 SharedFlow。StateFlow 是冷流,只有观察者时才会开始生产数据;但 StaateFlow 与 SharedFlow 是热流,会立即生产数据。在转换时可通过 SharingStarted 指定是饿汉式还是懒汉式,饿汉式会立即开始生产数据;懒汉式会在有第一个订阅者时才开始生产数据。SharingStarted 一共有三种:
- SharingStarted.Eagerly:饿汉式,立即开始生产数据
- SharingStarted.Lazily:懒汉式,有订阅者时才开始生产数据,而且永远不会停止
- SharingStarted.WhileSubscribed:懒汉式,有订阅者时才开始生产数据,但比 Lazily 可定制性更高:
stopTimeoutMillis:最后一个订阅者消失后多长时间才会暂停上游生产者,默认会立即暂停生产。如果在这段时间内有新的消费者出现,则生产者不会被暂停。注意:只是暂停上游生产,不是取消,当有新的订阅者出现时,会接着暂停的地方继续执行。新的订阅者可接收到最后一次 emit 的数据replayExpirationMillis 定义缓存失效与暂停上游生产者的间隔。在经过 stopTimeoutMillis 后会暂停上游生产者,再经过 replayExpirationMillis 毫秒后,会重置缓存。也就是说即使已经暂停了上游生产者,但在 replayExpirationMillis 内重新注册的订阅者也会立即收到缓存的数据