王学岗Kotlin协程(四)————Flow异步流

参考文章

异步返回值的多个方案

1,什么时候用flow呢?————kotlin要表示多个值
如何表示多个值?挂起函数可以异步返回单个值,但是如果要异步返回多个计算好的值,
就只能用flow了。其它方案的缺陷,集合返回了多个值,但是不是异步;序列返回了多个值,是同步。

    //返回了多个值,但不是异步
    fun simpleList(): List = listOf(1, 2, 3)

    //返回了多个值,是同步。序列与集合的区别:集合长度是固定的,序列是不固定的。
    //SequenceScope只能使用自己已有的挂起函数
    fun simpleSequence(): Sequence = sequence {
        for (i in 1..3) {
           Thread.sleep(1000)  //阻塞,线程被占用,不能干其它的事情。这里我们假装在计算
            //delay(1000)//只能使用已有的挂起函数
            yield(i)
        }
    }

    //集合+挂起函数方案:返回了多个值,是异步,但是一次性返回了多个值
//我们要的是像Sequence一次给一个值
    suspend fun simpleList2(): List {
        delay(1000)
        return listOf(1, 2, 3)
    }



    @Test
    fun `test multiple values`() {
        //simpleList().forEach { value -> println(value) }

        //simpleSequence().forEach { value -> println(value) }
    }

    @Test
    fun `test multiple values2`() = runBlocking {
        simpleList2().forEach { value -> println(value) }
    }

这个时候我们就可以使用flow来满足我们的要求。

    //返回多个值,是异步的
    fun simpleFlow() = flow {//folw构建器,会构建一个flow对象出来
        for (i in 1..3) {
            delay(1000) //假装在一些重要的事情
            emit(i) //发射,产生一个元素用emit
        }
    }

main方法中搜集元素

simpleFlow().collect{value->println(value)}

这里我们的运行效果与Sequence一模一样,但Sequence是阻塞主线程的,flow是不阻塞主线程的。我们下面通过代码证明flow是不阻塞主线程的。
修改main方法中搜集元素的代码

 @Test
    fun `test multiple values3`() = runBlocking {
        launch {//为了证明flow不是被阻塞的,我们再起一个任务
            for (k in 1..3) {
                println("I'm not blocked $k")
                delay(1500)
            }
        }

        simpleFlow().collect { value -> println(value) }
    }

打印输出如下

I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3

运行效果与Sequence同,但后者是阻塞的,两个任务来回切换,证明flow是不阻塞的。
注:SequenceScope只能使用自己已有的挂起函数王学岗Kotlin协程(四)————Flow异步流_第1张图片
注:1,通过flow构建器,得到Flow对象。
3,我们自己通过flow构建一个simpleFlow(名字可以随便取),前面可以去掉关键字suspend,这一点很重要,意味着我们可以在很多地方调用它。

flow的典型应用就是下载文件。UI界面点击按钮下载文件,下载文件是通过后台线程(如dispature.IO),通过emit发射数据(下载进度,异常,下载完成等)给主线程,主线程通过collect拿到数据,在UI中更新。
王学岗Kotlin协程(四)————Flow异步流_第2张图片

flow是冷流

flow构建器重的代码直到流被收集的时候才运行,代码如下

fun simpleFlow2() = flow {
        println("Flow started")
        for (i in 1..3) {
            delay(1000)
            emit(i)
        }
    }

    @Test
    fun `test flow is cold`() = runBlocking {
        val flow = simpleFlow2()//调用这个的时候并没有打印Flow started
        println("Calling collect...")
        flow.collect { value -> println(value) }//此时才开始打印Flow started
        println("Calling collect again...")
        flow.collect { value -> println(value) }
    }

只有调用collect的时候构建器代码才会执行,才会发射元素,再次调用collect就会再次执行构建器代码,再次发射元素
打印输出如下

Calling collect……
Flow started
1
2
3
Calling collect again……
Flow started
1
2
3

流的连续性

王学岗Kotlin协程(四)————Flow异步流_第3张图片
注:按顺序放进去按顺序拿出来

    @Test
    fun `test flow continuation`() = runBlocking {
//asFlow()是IntRange的扩展属性,构建流的构建器。

        (1..5).asFlow().filter {
            it % 2 == 0//过滤偶数
        }.map {
            "string $it"
        }.collect {
            println("Collect $it")
        }
    }

注:asFlow的源码

/**
* Creates a flow that produces values from the range.
*/
public fun IntRange.asFlow():Flow=flow{
forEach{
value -> emit(value)
}
}

打印输出如下

Collect string 2
Collect string 4

流的构建器

王学岗Kotlin协程(四)————Flow异步流_第4张图片

  @Test
    fun `test flow builder`() = runBlocking {
       flowOf("one","two","three")
                .onEach { delay(1000) }//每隔一秒钟发射个元素
                .collect { value ->
                    println(value)//每隔一秒钟打印输出one two three
                }

        (1..3).asFlow().collect { value ->
            println(value)
        }
    }

流的上下文

王学岗Kotlin协程(四)————Flow异步流_第5张图片
注:collect是挂起函数,需要在协程里调用,构建流并不需要在协程里面。构建流和收集流会在同一个协程上下文里面。
我们代码运行验证下

  fun simpleFlow3() = flow {
        println("Flow started ${Thread.currentThread().name}")
        for (i in 1..3) {
            delay(1000)
            emit(i)
        }
    }

//这种方法无法解决上下文保存
    fun simpleFlow4() = flow {
        withContext(Dispatchers.IO) {//withContext切换,后台线程发射元素
            println("Flow started ${Thread.currentThread().name}")
            for (i in 1..3) {
                delay(1000)
                emit(i)
            }
        }
    }


    @Test
    fun `test flow context`() = runBlocking {
       simpleFlow3()//都在主线程
                .collect { value -> println("Collected $value ${Thread.currentThread().name}") }
        simpleFlow4()//这里会报错,
                .collect { value -> println("Collected $value ${Thread.currentThread().name}") }
    }

看下运行结果

Flow started main @coroutine#1
Collected 1 main @coroutine#1
Collected 2 main @coroutine#1
Collected 3 main @coroutine#1

Flow started DefaultDispatcher-worker-1 @coroutine#1
红色异常省略

建流和收集流会在同一个协程上下文里面,但这很不实用,比如我们需要后台下载文件,并更新UI。如果要打破这种特性,withContext不行,要使用flowOn操作符来打破上下文保存的特性
在这里插入图片描述

fun simpleFlow5() = flow {
        println("Flow started ${Thread.currentThread().name}")
        for (i in 1..3) {
            delay(1000)
            emit(i)
        }
    }.flowOn(Dispatchers.Default)

    @Test
    fun `test flow on`() = runBlocking {
        simpleFlow5()
                .collect { value -> println("Collected $value ${Thread.currentThread().name}") }
    }

代码运行如下

Flow started DefaultDispatcher-worker-1 @coroutine#2
Collected 1 main @coroutine#1
Collected 2 main @coroutine#1
Collected 3 main @coroutine#1

在指定协程中收集流

在这里插入图片描述
看个需求,有很多事件在流里面,现在要一 一的处理这些事件

 //事件源,三个事件源源不断发送过来
    fun events() = (1..3)
            .asFlow()
            .onEach { delay(100) }
            .flowOn(Dispatchers.Default)

处理

events().onEach{
event ->println("Event:$event ${Thread.currentThread.name}")
}

onEach是个过渡操作符,此时没有末端操作符,不会发送。我们加上collect末端操作符

events().onEach{
event ->println("Event:$event ${Thread.currentThread.name}")
}.collect{};

虽然输出了,但是我们想收集处理在一个指定的协程里面,这时候可以使用launchIn

 fun `test flow launch`() = runBlocking {
 val job = events()
                .onEach { event -> println("Event: $event ${Thread.currentThread().name}") }
                //.collect {  }
                //launchIn需要一个作用域,在作用域中指定协程上下文
                .launchIn(CoroutineScope(Dispatchers.IO))
                  .join()//调用join才会等待子协成执行完。
                  }

launchIn返回的是一个Job对象。
看下打印输出:

Event:1 DefaultDispatcher-worker-3 @coroutine#2
Event:1 DefaultDispatcher-worker-3 @coroutine#2
Event:1 DefaultDispatcher-worker-3 @coroutine#2

因为有隐式调用,this就是主线程,所以在主线程中调用也可以这么写,
.launchIn(this)

流的取消

流采用与协程同样的协作取消(在协程中启动流,协程取消了,流也就取消了),像往常一样,流的收集可以是当流在一个可取消的挂起函数(如delay)中挂起的时候取消。
我们代码看个例子,流在超时的时候取消

 fun simpleFlow6() = flow {
        for (i in 1..3) {
            delay(1000)
            emit(i)
            println("Emitting $i")
        }
    }


    @Test
    fun `test cancel flow`() = runBlocking {
//2500则超时,超时取消协程,协程被取消了,流自然也被取消了
        withTimeoutOrNull(2500) {
            simpleFlow6().collect { value -> println(value) }
        }
        println("Done")
    }

我们看下打印输出

1
Emitting 1
2
Emitting 2
Done

流的取消检测

王学岗Kotlin协程(四)————Flow异步流_第6张图片

 fun simpleFlow7() = flow {
        for (i in 1..5) {
            emit(i)//这里会检测ensureActive,发完3发送4的时候这里检测到active为false。就不会继续发送了。
            println("Emitting $i")
        }
    }

    @Test
    fun `test cancel flow check`() = runBlocking {
        simpleFlow7().collect { value ->
            println(value)
            if (value == 3) cancel()//执行cancel的时候active为false
        }

打印输出如下

1
Emitting 1
2
Emitting 2
3
Emitting 3
报红色的异常,

可以发现4和5没有发出来。
下面这种情况则不会取消流

 (1..5).asFlow().collect { value ->
            println(value)
            if (value == 3) cancel()
        }

程序打印输出如下

1
2
3
4
5
红色异常

可以发现没有取消成功。
如果需要取消,必须明确检测是否取消,需增加cancellable(会影响性能)

 (1..5).asFlow().cancellable().collect { value ->
            println(value)
            if (value == 3) cancel()
        }

打印输出如下

1
2
3
红色异常

使用缓冲与flowOn处理背压

王学岗Kotlin协程(四)————Flow异步流_第7张图片
背压解决方案:1,降低生产效率;2,提高消费效率
背压解决方案一 buffer

fun simpleFlow8() = flow {
        for (i in 1..3) {
            delay(100)//生产这个元素需要100
            emit(i)
            println("Emitting $i ${Thread.currentThread().name}")
        }
    }

    @Test
    fun `test flow back pressure`() = runBlocking {
        val time = measureTimeMillis {
            simpleFlow8()
                  .collect { value ->
                delay(300)   //处理这个元素消耗300ms,生产效率小于消费效率
                println("Collected $value ${Thread.currentThread().name}")
            }
        }

        println("Collected in $time ms")//总计耗时1200
    }

打印输出如下

Collected 1  main @coroutine#1
Emitting 1 main @coroutine#1
Collected 2  main @coroutine#1
Emitting 2 main @coroutine#1
Collected 3  main @coroutine#1
Emitting 3 main @coroutine#1
Collected in 1226 ms

性能优化下,提升下运行效率。这里使用缓存做性能优化。
都在子线程

 @Test
    fun `test flow back pressure`() = runBlocking {
        val time = measureTimeMillis {
            simpleFlow8()
            .buffer(50)//指定50个缓存大小。
                  .collect { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
        }

        println("Collected in $time ms")
    }

打印输出如下

Emitting 1 main @coroutine#2
Emitting 2 main @coroutine#2
Emitting 3 main @coroutine#2
Collected 1  main @coroutine#1
Collected 2  main @coroutine#1
Collected 3  main @coroutine#1
Collected in 1069 ms

可以发现一次性发送出123,然后分开三次去收集。时间消耗缩短了。
前面的代码都在主线程,我们可以并行发送(切换线程),

 @Test
    fun `test flow back pressure`() = runBlocking {
        val time = measureTimeMillis {
            flowOn(Dispathcers.Default)
                  .collect { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
        }

        println("Collected in $time ms")/
    }

发送在后台线程,搜集在主线程
打印输出如下

Emitting 1 DefaultDispatcher-worker-1 @coroutine#2
Emitting 2 DefaultDispatcher-worker-1 @coroutine#2
Emitting 3 DefaultDispatcher-worker-1 @coroutine#2
Collected 1 main @coroutine#1
Collected 2 main @coroutine#1
Collected 3 main @coroutine#1
Collected in 1068 ms

合并与处理最新值

背压解决方案二 conflate合并
conflate并不去处理每个值,而是处理最新的值

fun simpleFlow8() = flow {
        for (i in 1..3) {
            delay(100)//生产这个元素需要100
            emit(i)
            println("Emitting $i ${Thread.currentThread().name}")
        }
    }

    @Test
    fun `test flow back pressure`() = runBlocking {
        val time = measureTimeMillis {
            simpleFlow8()
            .conflate()
                  .collect { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
        }

        println("Collected in $time ms")
    }

运行结果如下

Emitting 1 main @coroutine#2
Emitting 2 main @coroutine#2
Emitting 3 main @coroutine#2
Collected 1 main @coroutine#1
Collected 3 main @coroutine#1
collected in 770 ms

跳过了中间值
背压解决:collectLatest()取消并重新发射最后一个值

fun simpleFlow8() = flow {
        for (i in 1..3) {
            delay(100)//生产这个元素需要100
            emit(i)
            println("Emitting $i ${Thread.currentThread().name}")
        }
    }

    @Test
    fun `test flow back pressure`() = runBlocking {
        val time = measureTimeMillis {
            simpleFlow8()
                  .collectLast { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
        }

        println("Collected in $time ms")
    }

打印输出如下

Emitting 1 main @coroutine#2
Emitting 2 main @coroutine#2
Emitting 3 main @coroutine#2
Collected 3 main @coroutine#5
Collected in 785 ms

过渡流操作符

王学岗Kotlin协程(四)————Flow异步流_第8张图片

一 过渡操作符之转换操作符

  suspend fun performRequest(request: Int): String {
        delay(1000)
        return "response $request"
    }

    @Test
    fun `test transform flow operator`() = runBlocking {
        (1..3).asFlow()
                .map { request -> performRequest(request) }
                .collect { value -> println(value) }
  

打印输出如下

response 1
response 2
response 3

transform 可以进行任意次数的变换

       //更复杂的变换,
        (1..3).asFlow()
                .transform { request ->
                    emit("Making request $request")
                    emit(performRequest(request))
                }.collect { value -> println(value) }

    }

打印输出如下

Making request 1
response 1
Making request 2
response 2
Making request 3
response 3

第二 过渡操作符之限长操作符

  fun numbers() = flow {
        try {
            emit(1)
            emit(2)
            println("This line will not execute")//不会打印输出,因为只取2个元素
            emit(3)
        } finally {
            println("Finally in numbers")
        }
    }

    @Test
    fun `test limit length operator`() = runBlocking {
//只取两个元素
        numbers().take(2).collect { value -> println(value) }
    }

看下打印输出

1
2
Finally in numbers

操作符之末端流操作符

王学岗Kotlin协程(四)————Flow异步流_第9张图片

 @Test
    fun `test terminal operator`() = runBlocking {
        val sum = (1..5).asFlow()
                .map { it * it }
                .reduce { a, b -> a + b }//变成平方后累加
        println(sum)
    }

打印输出如下

55

其它的自己试一试,不在讲解

操作符之组合操作符

王学岗Kotlin协程(四)————Flow异步流_第10张图片

  @Test
    fun `test zip`() = runBlocking {
        val numbs = (1..3).asFlow()
        val strs = flowOf("One", "Two", "Three")
        numbs.zip(strs) { a, b -> "$a -> $b" }.collect { println(it) }
    }

打印输出如下

1->One
2->Two
3->Three

加延迟

  @Test
    fun `test zip2`() = runBlocking {
        val numbs = (1..3).asFlow().onEach { delay(300) }
        val strs = flowOf("One", "Two", "Three").onEach { delay(400) }
        val startTime = System.currentTimeMillis()
        numbs.zip(strs) { a, b -> "$a -> $b" }.collect {
            println("$it at ${System.currentTimeMillis() - startTime} ms from start")
        }
    }

打印输出如下

1->One at 449 ms from start
2->Two at 850 ms from start
3->Three at 1254 ms from start

每次间隔400,不是700

操作符之展平操作符

王学岗Kotlin协程(四)————Flow异步流_第11张图片

  fun requestFlow(i: Int) = flow {
        emit("$i: First")
        delay(500)
        emit("$i: Second")
    }

    @Test
    fun `test flatMapConcat`() = runBlocking {
        //Flow>
        val startTime = System.currentTimeMillis()
        (1..3).asFlow()
                .onEach { delay(100) }
                .flatMapConcat { requestFlow(it) }
                .collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
    }

//打印输出
1:First at 135 ms from start
1:Second at 636 ms from start
2:First at 737 ms from start
3:Second at 1237 ms from start
3:First at 1337 ms from start
3:Second at 1838 ms from start
    @Test
    fun `test flatMapMerge`() = runBlocking {
        //Flow>
        val startTime = System.currentTimeMillis()
        (1..3).asFlow()
                .onEach { delay(100) }
                //.map { requestFlow(it) }
                .flatMapMerge { requestFlow(it) }
                .collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
    }
//打印输出
1:First at 174 ms from start
2:First at 264 ms from start
3:First at 366 ms from start
1:Second at 673 ms from start
2:Second at 765 ms from start
3:Second at 868 ms from start

    @Test
    fun `test flatMapLatest`() = runBlocking {
        //Flow>
        val startTime = System.currentTimeMillis()
        (1..3).asFlow()
                .onEach { delay(100) }
                //.map { requestFlow(it) }
                .flatMapLatest { requestFlow(it) }
                .collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
    }

}
//打印输出
1:First at 170 ms from start
2:First at 369 ms from start
3:First at 470 ms from start
3:Second at 972 ms from start

流的异常处理

王学岗Kotlin协程(四)————Flow异步流_第12张图片
收集元素的时候抛出一个异常

 fun simpleFlow() = flow {
        for (i in 1..3) {
            println("Emitting $i")
            emit(i)
        }
    }

    @Test
    fun `test flow exception`() = runBlocking {
            simpleFlow().collect { value ->
                println(value)
//抛一个异常,"Collected $value"是异常信息
                check(value <= 1) { "Collected $value" }
            }
    }

代码运行如下

Emitting 1
1
Emitting 2
2
java.lang.IlleagleStateException:Collected 2
下面是红色异常信息(略)

捕获该异常

 fun simpleFlow() = flow {
        for (i in 1..3) {
            println("Emitting $i")
            emit(i)
        }
    }

    @Test
    fun `test flow exception`() = runBlocking {
        try {
            simpleFlow().collect { value ->
                println(value)
//抛一个异常,"Collected $value"是异常信息
                check(value <= 1) { "Collected $value" }
            }
        } catch (e: Throwable) {
            println("Caught $e")
        }
    }

代码运行如下

Emitting 1
1
Emitting 2
2
Caught java.lang.IllegalStateException:Collected 2

上面的异常都是收集的时候抛出的异常,下面是构建时候抛出的异常

 flow {
            emit(1)
            throw ArithmeticException("Div 0")
         }
                .flowOn(Dispatchers.IO)
                .collect { println(it) }

运行直接抛出红色异常信息,没有收集到任何信息。下面我们捕获该异常

 flow {
            emit(1)
            throw ArithmeticException("Div 0")
        }.catch { e: Throwable -> println("Caught $e") }
                .flowOn(Dispatchers.IO)
                .collect { println(it) }

打印输出如下,

1
Caught java.lang.ArithmeticException:Div 0

我们通过catch函数捕获了上游的异常。
我们捕获到了异常,是可以在异常中再次恢复。代码如下。

 flow {
            throw ArithmeticException("Div 0")
            emit(1)
        }.catch { e: Throwable ->
            println("Caught $e")
            emit(10)//在异常中恢复
        }.flowOn(Dispatchers.IO).collect { println(it) }

    }

打印输出

Caught java.lang.ArithmeticException:Div 0
10

流的完成

王学岗Kotlin协程(四)————Flow异步流_第13张图片

包括正常完成和异常结束
使用finally

 fun simpleFlow2() = (1..3).asFlow()
  @Test
    fun `test flow complete in finally`() = runBlocking {
        try {
            simpleFlow2().collect { println(it) }
        } finally {
            println("Done")
        }

    }

打印输出如下

1
2
3
Done

使用函数

simpleFlow2()
                .onCompletion { println("Done") }
                .collect { println(it) }

执行完后会调用onCompletion。
打印输出如下

1
2
3
Done

onCompletion相比finally的优势在于发生异常时会,获取到异常信息,但不会捕获

 fun simpleFlow3() = flow {
        emit(1)
        throw RuntimeException()
    }

simpleFlow3()
                .onCompletion { exception ->
                    if (exception != null) println("Flow completed exceptionally")
                }
             
                .collect { println(it) }

打印输出如下

1
Flow completed exceptionally
红色异常信息

如果想捕获异常

simpleFlow3()
                .onCompletion { exception ->
                    if (exception != null) println("Flow completed exceptionally")
                }
                .catch { exception -> println("Caught $exception") }
                .collect { println(it) }

打印输出如下,不在有红色异常信息

1
Flow completed exceptionally
Caught java.lang.RuntimeException

onCompletion与获取(不是捕获)下游异常,代码如下

 simpleFlow2()
                .onCompletion { exception ->
                    if (exception != null) println("Flow completed exceptionally")
                }
                .collect { value ->
                    println(value)
                      //抛一个异常
                    check(value <= 1) { "Collected $value" }
                }

打印输出如下

1
2
Flow completed exceptionally
红色异常信息

如果要捕捉下游异常就需要try catch了。这里就不再展示

你可能感兴趣的:(kotlin,android,kotlin,开发语言)