Kotlin Flow学习笔记

556 阅读4分钟

解说

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个)

末端流操作符

  • 转化为各种集合,例如 toListtoSet

  • 获取第一个(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")
    }
    
  • 使用 reducefold 将流规约到单个值。

    @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

未完待续....