解说
Flow 是一种类似于序列的冷流 — 这段 flow 构建器中的代码直到流被收集的时候才运行。这在以下的示例中非常明显:
fun simple(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(100)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling simple function...")
val flow = simple()
println("Calling collect...")
flow.collect { value -> println(value) }
println("Calling collect again...")
flow.collect { value -> println(value) }
}
打印如下:
Calling simple function...
Calling collect...
Flow started
1
2
3
Calling collect again...
Flow started
1
2
3
用法
flow { } flowOf
把集合或者数组转换成Flow
.asFlow()
.take(只获取前N个)
末端流操作符
-
获取第一个(first)值与确保流发射单个(single)值的操作符。
@Test fun testFlowFirst() = runBlocking { val first = (1..10).asFlow() // 一个请求流 .first { println(it) //返回true代表返回it,如果一个true都没返回会报错java.util.NoSuchElementException: // Expected at least one element matching the predicate false } println("end $first") }@Test fun testFlowSingle() = runBlocking { //当流中的元素多于一个的时候会报错 java.lang.IllegalArgumentException: Flow has more than one element。 //由此判断single的作用是用于判断流的有元素个数是否单个。 val first = flow<Int> { emit(1) emit(2) emit(3) } // 一个请求流 .single() println("end $first") } -
@Test fun testFlowReduce() = runBlocking { //求1到5的平方的和 val sum = (1..5).asFlow() .map { it * it } // 数字 1 至 5 的平方 .reduce { a, b -> a + b } // 求和(末端操作符) println(sum) } 执行步骤: 1.map执行一次 2.map执行一次 3.reduce a=步骤1的值 ,b=步骤2的值 4.map执行一次 5.reduce a=步骤3的值,b=步骤4的值 重复步骤4和步骤5直到结束....@Test fun testFlowFold() = runBlocking { val sum = (1..5).asFlow() .fold(10) { acc: Int, i: Int -> acc + i } println(sum) } 执行步骤: 1.fold提供一个初始值并赋值给acc 2.第二次执行方法体时acc就是方法体上一次返回的acc+i的返回值,i为flow发送的值
flowOn 操作符用于更改流发射的上下文
==flow{}代码块里面不能用其他切换线程,必须使用flowOn,否则会报错。==
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
Thread.sleep(100) // 假装我们以消耗 CPU 的方式进行计算
log("Emitting $i")
emit(i) // 发射下一个值
}
}.flowOn(Dispatchers.Default) // 在流构建器中改变消耗 CPU 代码上下文的正确方式
fun main() = runBlocking<Unit> {
simple().collect { value ->
log("Collected $value")
}
}
运行:
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 1
[main @coroutine#1] Collected 1
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 2
[main @coroutine#1] Collected 2
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 3
[main @coroutine#1] Collected 3
缓冲
.buffer()缓冲发射项
上流发射不需要等待下游处理,会缓存起来待下游需要的时候获取
@Test
fun testFlowHuanChong()= runBlocking{
val time = measureTimeMillis {
simple()
.buffer()// 缓冲发射项,无需等待
.collect { value ->
delay(300) // 假装我们花费 300 毫秒来处理它
println(value)
}
}
println("Collected in $time ms")
}
fun simple(): Flow<Int> = flow {
for (i in 1..5) {
delay(100) // 假装我们异步等待了 100 毫秒
emit(i) // 发射下一个值
println("发送 $i")
}
}
==注意,当必须更改 CoroutineDispatcher 时,flowOn操作符使用了相同的缓冲机制, 但是我们在这里显式地请求缓冲而不改变执行上下文。==
合并
.conflate()
==上游不会管下游是否处理得来数据,会一直发送数据,下游会拿最新能处理的数据,忽略中间的数据。==
fun simple(): Flow<Int> = flow {
for (i in 1..5) {
delay(100) // 假装我们异步等待了 100 毫秒
emit(i) // 发射下一个值
println("发送 $i")
}
}
@Test
fun testHeBing()= runBlocking {
val time = measureTimeMillis {
simple()
.conflate()// 缓冲发射项,无需等待
.collect { value ->
delay(300) // 假装我们花费 300 毫秒来处理它
println(value)
}
}
println("Collected in $time ms")
}
结果:
.collectLatest()
处理最新值
==每次上游发送一个值就会取消接收上一个上游数据的下游代码块.==
@Test
fun testCollectLatest() = runBlocking {
val time = measureTimeMillis {
simple()
.collectLatest { value -> // 取消并重新发射最后一个值
println("Collecting $value")
delay(300) // 假装我们花费 300 毫秒来处理它
println("Done $value")
}
}
println("Collected in $time ms")
}
结果:
Collecting 1
发送 1
Collecting 2
发送 2
Collecting 3
发送 3
Collecting 4
发送 4
Collecting 5
发送 5
Done 5
组合多个流
.zip(xxx)
==组合两个流中的相关值==
val nums = (1..3).asFlow() // 数字 1..3
val strs = flowOf("one", "two", "three") // 字符串
nums.zip(strs) { a, b -> "$a -> $b" } // 组合单个字符串
.collect { println(it) } // 收集并打印
结果:
1 -> one
2 -> two
3 -> three
解析:
a是nums流中的元素,b是strs流中的元素
.combine(xxx)
==流中发射一次上游内容,下游就会触发一次collect代码块==
val nums = (1..3).asFlow().onEach { delay(300) } // 发射数字 1..3,间隔 300 毫秒
val strs = flowOf("one", "two", "three").onEach { delay(400) } // 每 400 毫秒发射一次字符串
val startTime = System.currentTimeMillis() // 记录开始的时间
nums.combine(strs) { a, b -> "$a -> $b" } // 使用“combine”组合单个字符串
.collect { value -> // 收集并打印
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
结果:
1 -> one at 442 ms from start
2 -> one at 645 ms from start
2 -> two at 844 ms from start
3 -> two at 945 ms from start
3 -> three at 1245 ms from start
展平流
flatMapConcat{}
fun requestFlow(i: Int): Flow<String> = flow {
emit("$i: First")
delay(500) // 等待 500 毫秒
emit("$i: Second")
}
val startTime = System.currentTimeMillis() // 记录开始时间
(1..3).asFlow().onEach { delay(100) } // 每 100 毫秒发射一个数字
.flatMapConcat { requestFlow(it) }
.collect { value -> // 收集并打印
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
结果:
1: First at 121 ms from start
1: Second at 622 ms from start
2: First at 727 ms from start
2: Second at 1227 ms from start
3: First at 1328 ms from start
3: Second at 1829 ms from start
flatMapMerge{}
==个人理解是上游不会管下游是否消费,一直发射执行flatMapMerge中代码块。==
==接收可选的用于限制并发收集的流的个数的 concurrency参数(默认情况下,它等于 DEFAULT_CONCURRENCY)。==
fun requestFlow(i: Int): Flow<String> = flow {
emit("$i: First")
delay(500) // 等待 500 毫秒
emit("$i: Second")
}
val startTime = System.currentTimeMillis() // 记录开始时间
(1..3).asFlow().onEach { delay(100) } // 每 100 毫秒发射一个数字
.flatMapMerge { requestFlow(it) }
.collect { value -> // 收集并打印
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
结果:
1: First at 136 ms from start
2: First at 231 ms from start
3: First at 333 ms from start
1: Second at 639 ms from start
2: Second at 732 ms from start
3: Second at 833 ms from start
flatMapLatest{}
==在新上游发射数据时,会取消flatMapLatest代码块中所有代码==
fun requestFlow(i: Int): Flow<String> = flow {
emit("$i: First")
delay(500) // 等待 500 毫秒
emit("$i: Second")
}
val startTime = System.currentTimeMillis() // 记录开始时间
(1..3).asFlow().onEach { delay(100) } // 每 100 毫秒发射一个数字
.flatMapLatest { requestFlow(it) }
.collect { value -> // 收集并打印
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
结果:
1: First at 142 ms from start
2: First at 322 ms from start
3: First at 425 ms from start
3: Second at 931 ms from start
异常
==流的上游不允许try catch,应该在下游try catch或者使用.catch{}==
fun simple(): Flow<String> =
flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // 发射下一个值
}
}
方式一:捕获整个流异常
try{
simple()
.collect { value -> println(value) }
}catch(e: Throwable){
}
方式二:透明捕获,捕获上游异常,并可以继续发射内容到下游
simple()
//catch中代码块能捕获上游异常
.catch { e -> emit("Caught $e") } // 发射一个异常
.collect { value -> println(value) }
方式三:声明式捕获,将 collect 操作符的代码块移动到 onEach 中,并将其放到 catch 操作符之前。收集该流必须由调用无参的 collect() 来触发。
simple()
.onEach { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
.catch { e -> println("Caught $e") }
.collect()
流完成
命令式 finally 块
fun simple(): Flow<Int> = (1..3).asFlow()
fun main() = runBlocking<Unit> {
try {
simple().collect { value -> println(value) }
} finally {
println("Done")
}
}
结果:
1
2
3
Done
声明式处理
==onCompletion 中的传参能收到整个流中发生的异常,但是并不能让程序不崩溃==
==与 catch 操作符的另一个不同点是 onCompletion 能观察到所有异常并且仅在上游流成功完成(没有取消或失败)的情况下接收一个 null 异常。==
fun simple(): Flow<Int> = flow {
emit(1)
throw RuntimeException()
}
fun main() = runBlocking<Unit> {
simple()
.onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
.catch { cause -> println("Caught exception") }
.collect { value -> println(value) }
}
结果:
1
Flow completed exceptionally
Caught exception
执行顺序,上游发射——>上游发生异常-->onCompletion-->catch
取消
==为方便起见,流构建器对每个发射值执行附加的 ensureActive 检测以进行取消。 这意味着从 flow { ... }发出的繁忙循环是可以取消的:==
fun foo(): Flow<Int> = flow {
for (i in 1..5) {
println("Emitting $i")
emit(i)
}
}
fun main() = runBlocking<Unit> {
foo().collect { value ->
if (value == 3) cancel()
println(value)
}
}
结果:
Emitting 1
1
Emitting 2
2
Emitting 3
3
Emitting 4
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c
让繁忙的流可取消
===在协程处于繁忙循环的情况下,必须明确检测是否取消。 可以添加 .onEach { currentCoroutineContext().ensureActive() }, 但是这里提供了一个现成的 cancellable 操作符来执行此操作:==
fun main() = runBlocking<Unit> {
(1..5).asFlow().cancellable().collect { value ->
if (value == 3) cancel()
println(value)
}
}
结果:
1
2
3
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@5ec0a365