之前介绍的启动协程方法,比如 launch、async 都是协程的单次启动。如果有复杂场景,比如发送多个数据,就需要使用 flow 数据流。在 flow 中,数据如水流一样经过上游发送,中间站处理,下游接收。
创建 flow 有 3 种方式:
flow{}
flowOf()
asFlow()
1.flow{} 中使用 emit 发送数据。
fun flowEmit() = runBlocking {
flow {
emit(1)
emit(2)
emit(3)
emit(4)
emit(5)
}
.filter {
it > 2
}
.map {
it * 2
}
.take(2)
.collect {
// 6 8
println(it)
}
}
2.flowOf
flowOf() 可以将指定的一串数据转换为 flow,接收可变参数。
fun flowOfFun() = runBlocking {
flowOf(1, 2, 3, 4, 5)
.filter { it > 2 }
.map { it * 2 }
.take(2)
.collect {
// 6 8
println(it)
}
listOf(1, 2, 3, 4, 5)
.filter { it > 2 }
.map { it * 2 }
.take(2)
.forEach {
// 6 8
println(it)
}
}
3.asFlow
asFlow() 可以将 List 集合转换为 flow。toList() 可以将 flow 转换为 List 集合。
fun flow2list() = runBlocking {
flowOf(1, 2, 3, 4, 5)
// flow to list
.toList()
.filter { it > 2 }
.map { it * 2 }
.take(2)
.forEach {
println(it)
}
listOf(1, 2, 3, 4, 5)
// list as flow
.asFlow()
.filter { it > 2 }
.map { it * 2 }
.take(2)
.collect {
println(it)
}
}
4.中间操作符
创建 flow 之后使用中间操作符处理 flow 的每一个数据。flow 的中间操作符和 list 集合的操作符非常类似。
常用中间操作符:
filter
filter 传入判断条件,条件满足时过滤数据,否则不将数据流向下游。
map 传入映射函数,将每个数据传入映射函数,得到结果继续传入下游。
take 传入非负整数 n,取前 n 个数据传入下游。
5.终止操作符
collect 是 flow 的终止操作符,收集每一个数据经过中间操作符后的最终结果,表示 flow 流的终止,后面不能再调用中间操作符。
除了 collect,还有一些其他的终止操作符,first、single、fold、reduce。
1)collect
返回所有元素,结束 flow。
2)first
返回第一个元素,结束 flow。
3)single
返回唯一元素,结束flow。不能多于一个,也不能一个没有。
4).fold
折叠所有元素。指定一个函数和初始值,对每一个元素反复执行函数,返回最后的结果。
5)reduce
reduce 和 fold 很类似,reduce 没有初始值。
first、single、fold、reduce 本质都是封装了 collect ,因此它们都是终止操作符。
6.onStart 是 flow 的开始生命周期回调。onStart 的执行时机和它在 flow 位置无关。
7.onComplete
flow 执行完后回调 onComplete。onComplete 的执行时机和它在 flow 的位置无关。
flow 正常执行完回调 onComplete。
8.异常处理
flow 的异常处理可以分为上游异常和下游异常。上游异常指创建 flow 或者中间操作符发生的异常。下游异常指终止操作符 collect 发生的异常。
上游异常
上游异常可以用 catch 函数捕获异常。catch 函数和它的位置相关,只能捕获 catch 上游的异常。
下游异常不能用 catch 函数,需要在 collect 的作用域用 try-catch 捕获。
catch 函数无法捕获下游的 filter 除 0 异常。
9.线程切换
1)flowOn 可以指定上游所有操作符运行的线程,和它的位置相关。
collect 运行在 main 线程,上游运行在 IO 线程,指定 DefaultDispatcher。
flowOn 在 filter 之前,emit 执行在 IO 线程,filter 和 collect 执行在 main 线程。
因为 flowOn 只能用于上游,在 collect 可以用 withContext 切换线程,但不建议这么用。
collect 运行在 DefaultDispatcher,其他运行在 main 线程。
flow 的 emit、filter、collect 都运行在 DefaultDispatcher。
2)launchIn
flow 提供了 launchIn 函数指定在哪个线程执行。launchIn 运行在指定的 CoroutineScope。
flowOn 之前的运行在 Dispatchers.IO,下游运行在 launchIn 指定的 scope。
launchIn 调用了 scope 的 launch,然后执行 collect。相当于终止操作符。
10.flow 是冷的
flow 是冷的,只有接收者存在的情况下才会发送数据。如果不调用 collect,emit 不会执行。相反 channel 是热的,不管有没有接收者都会发送。
flow 的 emit 未执行。
总结
flow 是 kotlin 提供的解决复杂异步场景的方案。
flow 由创建、中间操作符、终止操作符三个部分组成。
flow 的生命周期可以分为 onStart 和 onComplete,与它们在 flow 的位置无关。
flow 的异常处理使用 catch。catch 与位置相关。
flow 的线程切换使用 flowOn 和 launchIn。flowOn 控制上游,launchIn 控制全局。
flow 是冷的,只有存在接收者它才会开始执行。