如何表示多个值
- 挂起函数可以异步的返回单个值,但是该如何异步返回多个计算好的值呢?
异步返回多个值的方案
- 集合
- 序列
- 挂起函数
- Flow
/*suspend*/ fun simpleFlow() = flow {
for(i in 1..3){
delay(1000)
emit(i)
}
}
Flow与其他方式区别
- 名为flow的Flow类型构建器函数
- flow{...}构建块中的代码可以挂起
- 函数simpleFlow不再标有suspend修饰符
- 流使用emit函数发射值
- 流使用collect函数收集值
Flow应用
-
在Android当中,文件下载是Flow的一个非常典型的应用
冷流
- Flow是一种类似于序列的冷流,flow构建器中的代码直到流被收集的时候才运行
fun simpleFlow() = flow {
println("Flow started")
for(i in 1..3){
delay(1000)
emit(i)
}
}
fun testFlowIsCode() = runBlocking {
val flow = simpleFlow()
println("Flow Collect")
flow.collect { println(it) }
println("Flow Collect again")
flow.collect { println(it) }
}
返回结果
Flow Collect
Flow started
1
2
3
Flow Collect again
Flow started
1
2
3
Process finished with exit code 0
根据以上返回结果可以看出代码执行val flow = simpleFlow()的时候没有执行flow{...}构建块中的代码,只有调用collect的时候才执行,这就是冷流
流的连续性
- 流的每次单独收集都是按照顺序执行的,除非使用特殊操作符
- 从上游到下游每个过度操作符都会处理每个发射出的值,然后再交给末端操作符
fun testFlowContinuation() = runBlocking {
(1..5).asFlow().filter {
it%2 == 0
}.map {
"string $it"
}.collect {
println(it)
}
}
流构建器
- flowOf构建器定义了一个发射固定值集的流
- 使用.asFlow()扩展函数,可是将各种集合与序列转换为流
fun testFlowOf() = runBlocking {
flowOf("one", "two", "three")
.onEach {
delay(1000)//每隔1s发射一个元素
}.collect{
println(it)
}
}
流上下文
- 流的收集总是在调用协程的上下文中发生,流的该属性称为上下文保存
- flow{...}构建器中的代码必须遵循上下文保存属性,并且不允许从其他上下文中发射(emit)
fun simpleFlow() = flow {
println1("Flow started ${Thread.currentThread().name}")
for(i in 1..3) {
delay(1000)
emit(i)
}
}
fun testFlowContext() = runBlocking {
simpleFlow().collect { value ->
println1("$value ${Thread.currentThread().name}")
}
}
看以上代码两个方法都存在于主线程,那如果flow{...}改变一下线程如下
fun simpleFlow() = flow {
withContext(Dispatchers.IO){
println1("Flow started ${Thread.currentThread().name}")
for(i in 1..3) {
delay(1000)
emit(i)
}
}
}
执行一下,就会报错
Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
Flow was collected in [BlockingCoroutine{Active}@599ad64a, BlockingEventLoop@2cbb850d],
but emission happened in [DispatchedCoroutine{Active}@42689e0c, Dispatchers.IO].
Please refer to 'flow' documentation or use 'flowOn' instead
这就证明必须遵循上下文保存属性,并且不允许从其他上下文中发射
- flowOn操作符,该函数用于更改流发射的上下文
fun simpleFlow() = flow {
println1("Flow started ${Thread.currentThread().name}")
for(i in 1..3) {
delay(1000)
emit(i)
}
}.flowOn(Dispatchers.IO)
启动流
- 使用launchIn替换collect我们可以在单独的协程中启动流的收集
//事件源
fun event() = (1..3).asFlow().onEach { delay(100) }.flowOn(Dispatchers.Default)
fun testEventLaunch() = runBlocking {
event().onEach {event->
println1("Event: $event ${Thread.currentThread().name}")
}.launchIn(CoroutineScope(Dispatchers.IO)).join()
}
流的取消
- 流采用与协程同样的协作取消。像往常一样,流的收集可以是当流在一个可取消的挂起函数(例如 delay)中挂起的时候取消
fun simpleFlow1() = flow {
println1("Flow started ${Thread.currentThread().name}")
for(i in 1..3) {
delay(1000)
emit(i)
println1("Emitting $i")
}
}.flowOn(Dispatchers.IO)
fun testCancelFlow() = runBlocking {
withTimeoutOrNull(2500) {
simpleFlow1().collect { value -> println1(value) }
}
println1("Done")
}
流的取消监测
- 为方便起见,流构建器对每个发射值执行附加的 ensureActive 监测以进行取消,这意味着从flow{...}发出繁忙循环是可以取消的
- 出于性能原因,大多数其他流操作不会自行执行其他取消监测,在协程出于繁忙循环的情况下,必须明确监测是否取消
- 通过cancellable操作符来执行此操作
fun testCancelFlowCheck() = runBlocking {
(1..5).asFlow().cancellable().collect {
println1(it)
if(it == 3)cancel()
}
}
背压
水流收到与流动方向一致的压力叫做背压或者生产者的效率大于消费者的效率
- buffer(),并发运行流中发射元素的代码,相当于把管道延长,增加缓冲区
- conflate(),合并发射项,不对每个值进行处理
- collectLates(),取消并重新发射最后一个值
- 当必须更改CoroutineDispatcher时,flowOn操作符使用了相同的缓存机制,但是buffer函数显式地请求缓冲而不改变执行上下文
fun simpleFlow2() = flow {
for(i in 1..3) {
delay(100)
emit(i)
println1("Emitting $i")
}
}
fun testFlowBackPressure() = runBlocking {
val time = measureTimeMillis {
simpleFlow2()
// .flowOn(Dispatchers.Default)
// .buffer(50)
// .conflate()
// .collectLatest {
// }
.collect {
delay(300)
println1("$it")
}
}
println1(time)
}
过渡流操作符
- 可以使用操作符转换流,就像使用集合与序列一样
- 过渡操作符应用于上游流,并返回下游流
- 这些操作符也是冷操作符,就像流一样。这类操作符本身不是挂起函数
- 它运行速度很快,返回新的转换流的定义
suspend fun performRequest(request: Int): String {
delay(1000)
return "response $request"
}
fun testTransformFlowOperator() = runBlocking {
(1..5).asFlow().transform { request ->
emit("Making request $request")
emit(performRequest(request))
}.collect {
println1(it)
}
}
限长操作符
take
fun number() = flow {
try {
emit(1)
emit(2)
emit(3)
} finally {
println1("Finally in numbers")
}
}
fun testLimitLengthOperator() = runBlocking {
number().take(2).collect { println1(it) }
}
末端操作符
- 末端操作符是在流上用于启动流收集的挂起函数。collect是最基础的末端操作符,但是还有另外一些方便使用的末端操作符
- 转化为各种集合,例如toList和toSet
- 获取第一个(first)值和确保流发射单个(single)值的操作符
- 使用reduce与fold将流规约到单个值
fun testTerminalOperator() = runBlocking {
val sum = (1..5).asFlow().map { it * it }.reduce { a, b ->
a+b
}
println1(sum)
}
fun testTerminalOperator() = runBlocking {
val sum = (1..5).asFlow()
.fold(0, {acc, i -> acc + i })//以0为初始值,求1到5的和
println1(sum)
}
组合多个流
- 就像Kotlin标准库中的Sequence.zip扩展函数一样,流拥有一个zip操作符用于组合两个流中的相关值
fun testZip() = runBlocking {
val numbers = (1..5).asFlow()
val strs = flowOf("one", "two", "three")
numbers.zip(strs) { a, b ->
"$a -> $b"
}.collect { println1(it) }
}
展平流
- 流表示异步接收的值序列,所以很容易遇到这样的情况:每个值都会触发对另一个值序列的请求,然而,由于流具有异步的性质,因此需要不同的展平方式,为此,存在一系列的流展平操作符
- flatmapConcat 连接模式
- flatMapMerge 合并模式
- flatMapLatest最新展平模式
fun requestFlow(i: Int) = flow {
emit("$i first")
delay(500)
emit("$i second")
}
fun testFlatMapConcat() = runBlocking {
(1..3).asFlow().onEach { delay(100) }
// .map { requestFlow(it) } // 转换后会变成 Flow>因此需要展平处理
.flatMapConcat { requestFlow(it) }
// .flatMapConcat { requestFlow(it) }
// .flatMapConcat { requestFlow(it) }
.collect { println1(it) }
}
返回结果对比
flatMapConcat 模式
1 first
1 second
2 first
2 second
3 first
3 second
flatMapMerge 模式
1 first
2 first
3 first
1 second
2 second
3 second
flatMapLatest 模式
1 first
2 first
3 first
3 second
流的异常处理
- 当运算符中发射器或代码抛出异常时,有几种处理异常的方法
- try/catch块
- catch函数
fun number() = flow {
try {
emit(1)
emit(2)
emit(3)
} finally {
println1("Finally in numbers")
}
}.catch { e : Throwable ->
// 在catch块中 还可以继续发射元素
emit(4)
}
流的完成
- 当流收集完成时(普通情况或异常情况),它可能需要执行一个动作。
- 命令式finally块
- onCompletion声明式处理
fun simpleFlow4() = (1..3).asFlow()
fun testFlowCompleteInFinally() = runBlocking {
// try {
// simpleFlow4().collect { println1(it) }
// } finally {
// println1("Done")
// }
simpleFlow4().onCompletion {exception-> //onCompletion还可以打印出上游下游异常信息
println1("Done ${exception ?: ""}")
}.collect {
println1(it)
}
}