Kotlin篇 > > 关于 Flow 的两年半的练习生涯

167 阅读7分钟
https://img.99ppt.com/pic/8775da77-b304-47d3-9258-771d170e6c44.png

能正确地提出问题就是迈出了创新的第一步。

想出新办法的人在他的办法没有成功以前,人家总说他是异想天开。

项目地址:gitee.com/vipvvitt/ko…

流的知识点目录

Flow 的基本使用
    1.1 Sequence 与 Flow
    1.2 Flow 的简单使用
    1.3 创建常规 Flow 的常用方式:
    1.4 Flow 是冷流(惰性的)
    1.5 Flow 的取消
Flow 的操作符
    2.1 Terminal flow operators 末端流操作符
        2.1.1 collect
        2.1.2 reduce
        2.1.3 fold
        2.1.4 launchIn
    2.2 流是连续的
    2.3 onStart 流启动时
    2.4 onCompletion 流完成时
        2.4.1 使用 try ... finally 实现
        2.4.2 通过 onCompletion 函数实现
    2.5 BackPressure 背压
        2.5.1 buffer 缓冲
        2.5.2 conflate 合并
    2.6 Flow 异常处理
        2.6.1 catch 操作符捕获上游异常
        2.6.2 retry、retryWhen 操作符重试
    2.7 Flow 线程切换
        2.7.1 响应线程
        2.7.2 flowOn 切换线程
    2.8 Flow 中间转换操作符
        2.8.1 map
        2.8.2 transform
        2.8.3 on@R_360_2428@
        2.8.4 filter
        2.8.5 drop / dropWhile
        2.8.6 take
        2.8.7 zip
        2.8.8 combine
        2.8.9 flattenContact 和 flattenMerge 扁平化处理
        2.8.10 flatMapMerge 和 flatMapContact
        2.8.11 flatMapLatest
stateFlow 和 ShareDFlow
    3.1 StateFlow
        3.1.1 StateFlow 基本使用
        3.1.2 为什么使用 StateFlow
        3.1.3 防止任务泄漏
        3.1.4 SateFlow 只会发射最新的数据给订阅者。
    3.2 SharedFlow
        3.2.1 SharedFlow 基本使用
        3.2.2 MutableSharedFlow 的其它接口
    3.3 StateFlow 和 SharedFlow 的使用场景
    3.4 将冷流转换为热流

Flow 异步流

案例场景:挂起函数可以异步的返回单个值,但是如何异步的返回多个计算好的值? 集合、序列、挂起函数、Flow 都可以实现,但是完美实现此场景的还需使用Flow异步流

先看下序列生成器:

val intSequence = sequence<Int> {
        Thread.sleep(1000) // 模拟耗时任务1
        yield(1)
        Thread.sleep(1000) // 模拟耗时任务2
        yield(2)
        Thread.sleep(1000) // 模拟耗时任务3
        yield(3)
    }
intSequence.forEach {
        println(it)
    }

操作符

★ 1.过渡流操作符

  • transform : 转换操作符

可以使用操作符转换流,就像使用集合与序列一样。

过渡操作符应用于上游流,并返回下游流。

这些操作符也是冷操作符,就像流一样。这类操作符本身不是挂起函数。

它运行的速度很快,返回新的转换流的定义。

如果不使用转换操作符,我们对流字符串处理可以使用map遍历


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

@Test
fun `test transform flow operator`() = runBlocking<Unit> {
    (1..3).asFlow()
        .map { request -> performRequest(request) }
        .map { request1 -> "Making request $request1" }
        .collect { value -> println(value) }
}
// Making request response 1
// Making request response 2
// Making request response 3

使用了transform,我们对流的处理可以更灵活。 这里接受到的数据与emit互不影响

@Test
fun `test transform flow operator`() = runBlocking<Unit> {
    (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
  • take : 限长操作符

这里take限制取2,则从第二个emit就不再执行,也就是这里的println

fun numbers() = flow<Int> {
    try {
        emit(1)
        emit(2)
        println("This line will not execute")
        emit(3)
    } finally {
        println("Finally in numbers")
    }
}

@Test
fun `test limit length operator`() = runBlocking<Unit> {
    numbers().take(2).collect { value -> println(value) }
}
// 1
// 2
// Finally in numbers

★ 2. 末端操作符

末端操作符是在流上用于启动流收集的挂起函数。
  • collect是最基础的末端操作符,但是还有另外一些更方便使用的末端操作符:

    • 1.转化为各种集合,例如toList与toSet。

    • 2.获取第一个(first)值与确保流发射单个(single)值的操作符。

    • 3.使用reduce与fold将流规约到单个值。

  • reduce示例,取1到5的平方累加,reduce为末端操作符产生结果

    • reduce 操作符可以将元素累加。 reduce的返回值类型必须和集合的元素类型相符。
@Test
fun `test terminal operator`() = runBlocking<Unit> {
    val sum = (1..5).asFlow()
        .map { it * it }
        .reduce { a, b -> a + b }
    println(sum)
}
// 55

而fold的返回值类型则不受约束。

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        emit(i)
    }
}

fun main() {
    runBlocking {
        val newStr = simpleFlow()
            .fold(StringBuilder()) { str: StringBuilder, a: Int ->
                str.append(a).append(" ")
            }
        println(newStr)
    }
}

★★★ 3. 组合操作符

就像Kotlin标准库中的Sequence.zip扩展函数一样,流拥有一个zip操作符用于组合两个流中的相关值。 类似于拉链

@Test
fun `test zip`() = runBlocking<Unit> {
    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

如果两个是异步的且时间间隔不一致,可以看到是以较长时间为间隔,也就是一遍取300ms的一遍等400ms的

@Test
fun `test zip2`() = runBlocking<Unit> {
    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 429 ms from start
// 2 -> Two at 832 ms from start
// 3 -> Three at 1241 ms from start

combine

组合每个流最新发出的值。

val flow = flowOf(1, 2).onEach { 
            delay(10) 
        }
val flow2 = flowOf("a", "b", "c").onEach{ 
            delay(15) 
        }
flow.combine(flow2) { 
        i, s -> i.toString() + s 
    } .collect {
          println(it) // Will print "1a 2a 2b 2c"
    }

merge

合并多个流成 一个流。可以用在 多级缓存加载上。

val numberFlow = flowOf(1, 2).onEach { delay(10) }
val stringFlow = flowOf("a", "b", "c").onEach { delay(15) }

listOf(numberFlow,stringFlow).merge()
                             .collect { value ->
                                 print(value)
                             }


// 1 a 2 b c

★★★ 4. 展平操作符

流表示异步接收的值序列,所以很容易遇到这样的情况:每个值都会触发对另一个值序列的请求,

然而,由于流具有异步的性质,因此需要不同的展平模式,为此,存在一系列的流展平操作符:

展平操作符
flatMapConcat连接模式
flatMapMerge合并模式
flatMapLatest最新展平模式
  • 1.flatMapConcat连接模式

示例:将两个异步流合并.. 如果使用Map,则就是双层流Flow,这里使用flatMapConcat展平连接

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

@Test
fun `test flatMapConcat`() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    (1..3).asFlow()
        .onEach { delay(100) }
        //.map { requestFlow(it) }        //Flow<Flow<String>>
        .flatMapConcat { requestFlow(it) }
        .collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
}
// 1: First at 127 ms from start
// 1: Second at 631 ms from start
// 2: First at 735 ms from start
// 2: Second at 1239 ms from start
// 3: First at 1342 ms from start
// 3: Second at 1846 ms from start
  • 2.flatMapMerge 合并模式
fun requestFlow(i: Int) = flow<String> {
    emit("$i: First")
    delay(500)
    emit("$i: Second")
}

@Test
fun `test flatMapMerge`() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    (1..3).asFlow()
        .onEach { delay(100) }
        .flatMapMerge { requestFlow(it) }
        .collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
}
// 1: First at 153 ms from start
// 2: First at 251 ms from start
// 3: First at 355 ms from start
// 1: Second at 654 ms from start
// 2: Second at 752 ms from start
// 3: Second at 858 ms from start
  • 3.flatMapLatest 最新展平模式
fun requestFlow(i: Int) = flow<String> {
    emit("$i: First")
    delay(500)
    emit("$i: Second")
}

@Test
fun `test flatMapLatest`() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    (1..3).asFlow()
        .onEach { delay(100) }
        .flatMapLatest { requestFlow(it) }
        .collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
}
// 1: First at 142 ms from start
// 2: First at 285 ms from start
// 3: First at 388 ms from start
// 3: Second at 889 ms from start

1.1 flow 与其他方式的区别

区别在于:

  • 名为flow的Flow类型构建器函数
  • flow{...} 构建快中的代码可以挂起,意思是可以使用挂起函数,例如可以使用delay()函数
  • 函数simpleFlow(构建flow函数) 不用标记suspend修饰符;意思是不用声明挂起函数
  • 流使用emit函数反射值
  • 流使用collect函数收集值
//简单使用
private fun createFlow(): Flow<Int> = flow {
    delay(1000)
    emit(1)
    delay(1000)
    emit(2)
    delay(1000)
    emit(3)
}
fun main() = runBlocking {
    createFlow().collect {
        println(it)
    }
}

1.2 - flow在Android中,实际应用场景

android 中 文件下载FLow的一个非常典型的应用场景 下载文件,主界面获取进度 下载是后台线程,展示进度是主线程,更新UI 工作原理:后台线程(view model 或者room)利用流发射进度到流通道中,主线程利用collect收集数据(进度、异常等数据),异步的源源不断的发送与收集

1.3- flow 冷流

  • Flow 是一种类似与序列的冷流, flow 构建器中的代码知道流被收集(collect)的时候才运行
  • 如同 Sequences 一样, Flows 也是惰性的,即在调用末端流操作符(collect 是其中之一)之前, flow{ ... } 中的代码不会执行。我们称之为冷流。
private fun createFlow(): Flow<Int> = flow {
    println("flow started")
    delay(1000)
    emit(1)
    delay(1000)
    emit(2)
    delay(1000)
    emit(3)
}
fun main() = runBlocking {
    val flow = createFlow()
    println("calling collect...")
    flow.collect {
        println(it)
    }
    println("calling collect again...")
    flow.collect {
        println(it)
    }
}

1.4- 流的连续性

流的每次单独收集都是按顺序执行的,除非使用特殊操作符 从上游到下游每个过渡操作符都会处理每个发射处的值,然后再交给末端操作符。 示例中:asFlowIntRange提供的流的快速构建器

fun main() = runBlocking {
    (1..5).asFlow()
        .filter {
            println("Filter $it")
            it % 2 == 0
        }
        .map {
            println("Map $it")
            "string $it"
        }.collect {
            println("Collect $it")
        }
}


Filter 1
Filter 2
Map 2
Collect string 2
Filter 3
Filter 4
Map 4
Collect string 4
Filter 5

1.5 流构建器(如何创建Flow)

流的构建器有3种:flowflowOfasFlow()

1.5.1 - flow构建器


flow {
    delay(1000)
    emit(1)
    delay(1000)
    emit(2)
    delay(1000)
    emit(3)
}

1.5.2- flowOf

flowOf构建器定义了一个发射固定值集的流。

flowOf(1,2,3).onEach {
    delay(1000)
}

1.5.3- asFlow()

使用.asFlow()扩展函数,可以将各种集合序列转换为

listOf(1, 2, 3).asFlow().onEach {
    delay(1000)
}

1.6 流上下文

流的收集总是在调用协程的上下文中发生,流的该属性称为上下文保存

fow{…}构建器中的代码必须遵循上下文保存属性,并且不允许从其他上下文中发射(emit):也就是说 构建流和收集流会是同个上下文,在同一个协程里面

flowOn操作符,该函数用于更改流发射的上下文。

1.7 启动流

使用launchIn 替换 collect 我们可以在单独的协程中启动流的收集 launchIn 用来在指定的 CoroutineScope 内启动 flow, 需要传入一个参数: CoroutineScope 源码

public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
    collect() // tail-call
}

示例:

private val mDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
fun main() {
    val scope = CoroutineScope(mDispatcher)
    (1..5).asFlow().onEach { println(it) }
        .onCompletion { mDispatcher.close() }
        .launchIn(scope)
}

再看一个例子:

fun main() = runBlocking{
  val cosTime = measureTimeMillis {
    (1..5).asFlow()
      .onEach { delay(100) }
      .flowOn(Dispatchers.IO)
      .collect { println(it) }
    flowOf("one", "two", "three", "four", "five")
      .onEach { delay(200) }
      .flowOn(Dispatchers.IO)
      .collect { println(it) }
  }
  println("cosTime: $cosTime")
}

我们希望并行执行两个 Flow ,看下输出结果:

1
2
3
4
5
one
two
three
four
five
cosTime: 1645

结果并不是并行执行的,这个很好理解,因为第一个 collect 不执行完,不会走到第二个。 正确地写法应该是,为每个** Flow** 单独起一个协程

fun main() = runBlocking<Unit>{
    launch {
        (1..5).asFlow()
            .onEach { delay(100) }
            .flowOn(Dispatchers.IO)
            .collect { println(it) }
    }
    launch {
        flowOf("one", "two", "three", "four", "five")
            .onEach { delay(200) }
            .flowOn(Dispatchers.IO)
            .collect { println(it) }
    }
}

或者使用 launchIn, 写法更优雅:

fun main() = runBlocking<Unit>{
    (1..5).asFlow()
        .onEach { delay(100) }
        .flowOn(Dispatchers.IO)
        .onEach { println(it) }
        .launchIn(this)
    flowOf("one", "two", "three", "four", "five")
        .onEach { delay(200) }
        .flowOn(Dispatchers.IO)
        .onEach { println(it) }
        .launchIn(this)
}

输出结果:

1
one
2
3
4
two
5
three
four
five

1.7.1 onStart 流启动时

Flow 启动开始执行时的回调,在耗时操作时可以用来做 loading。

fun main() = runBlocking {
    (1..5).asFlow()
        .onEach { delay(200) }
        .onStart { println("onStart") }
        .collect { println(it) }
}


onStart
1
2
3
4
5

1.7.2 onCompletion 流完成时

Flow 完成时(正常或出现异常时),如果需要执行一个操作,它可以通过两种方式完成:

  • 使用 try ... finally 实现
fun main() = runBlocking {
    try {
        flow {
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.collect { println(it) }
    } finally {
        println("Done")
    }
}
  • 通过 onCompletion 函数实现
fun main() = runBlocking {
    flow {
        for (i in 1..5) {
            delay(100)
            emit(i)
        }
    }.onCompletion { println("Done") }
        .collect { println(it) }
}



1
2
3
4
5
Done


1.8 流的取消

流采用了与协程同样的协助取消。流的收集可以在当流在一个可取消的挂起函数(例如 delay)中挂起的时候取消。

取消Flow 只需要取消它所在的协程即可。

示例,展示了当 withTimeoutOrNull 块中代码在运行的时候流是如何在超时的情况下取消并停止执行


fun simple(): Flow<Int> = flow { 
    for (i in 1..3) {
        delay(100)          
        println("Emitting $i")
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
    withTimeoutOrNull(250) { // 在 250 毫秒后超时
        simple().collect { value -> println(value) } 
    }
    println("Done")
}

1.8.1- 流的取消检测

  • 1.为方便起见,流构建器对每个发射值执行附加的ensureActive检测以进行取消, 这意味着从flow{…}发出的繁忙循环是可以取消的。
fun simpleFlow7() = flow<Int> {
    for (i in 1..5) {
        emit(i)
        println("Emitting $i")
    }
}

@Test
fun `test cancel flow check`() = runBlocking<Unit> {
    simpleFlow7().collect { value ->
        println(value)
        if (value == 3) cancel()
    }
}
// 1
// Emitting 1
// 2
// Emitting 2
// 3
// Emitting 3
  • 2.出于性能原因,大多数其他流操作不会自行执行其他取消检测,在协程处于繁忙循环的情况下,必须明确检测是否取消

@Test
fun `test cancel flow check`() = runBlocking<Unit> {
    (1..5).asFlow().collect { value ->
        println(value)
        if (value == 3) cancel()
    }
}
// 1
// 2
// 3
// 4
// 5

这里繁忙情况下(1…5).asFlow()取消失败了,本来应该只打印到3

  • 3.通过cancellable操作符来执行此操作。会影响效率

上面示例中加个.cancellable()即可取消


@Test
fun `test cancel flow check`() = runBlocking<Unit> {
    (1..5).asFlow().cancellable().collect { value ->
      println(value)
      if (value == 3) cancel()
    }
  }

// 1
// 2
// 3

1.9 流的背压

背压: 水流受到与流动方向一直的压力。 在这里,只要生产者生产的效率大于消费者消费效率,就产生背压。

1.9.1 - 背压产生的问题

下面示例中:生产者100ms后并发生产,消费者每300毫秒消费一个measureTimeMillis:统计协程总时长

结果1234 ms:约等于(100+300)*3。时间相当于所有的生产者消费者加起来,效率最低,那么如何优化?


fun simpleFlow8() = flow<Int> {
    for (i in 1..3) {
        delay(100)
        emit(i)
        println("Emitting $i ${Thread.currentThread().name}")
    }
}

@Test
fun `test flow back pressure`() = runBlocking<Unit> {
    val time = measureTimeMillis {
        simpleFlow8()
            .collect { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
    }
    println("Collected in $time ms")
}
// Collected 1 Test worker @coroutine#1
// Emitting 1 Test worker @coroutine#1
// Collected 2 Test worker @coroutine#1
// Emitting 2 Test worker @coroutine#1
// Collected 3 Test worker @coroutine#1
// Emitting 3 Test worker @coroutine#1
// Collected in 1234 ms

上述问题优化,解决方案

1.做大管子,增加容积 (buffer、flowOn方式)

2.降低生产者生产效率

3.提高消费者消费效率 (conflate、collectLatest方式)

  • buffer缓冲方式处理背压

我们加上一个buffer,结果1061 ms:约等于100+300*3


fun simpleFlow8() = flow<Int> {
    for (i in 1..3) {
        delay(100)
        emit(i)
        println("Emitting $i ${Thread.currentThread().name}")
    }
}

@Test
fun `test flow back pressure`() = runBlocking<Unit> {
    val time = measureTimeMillis {
        simpleFlow8()
            .buffer(50)
            .collect { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
    }
    println("Collected in $time ms")
}
// Emitting 1 Test worker @coroutine#2
// Emitting 2 Test worker @coroutine#2
// Emitting 3 Test worker @coroutine#2
// Collected 1 Test worker @coroutine#1
// Collected 2 Test worker @coroutine#1
// Collected 3 Test worker @coroutine#1
// Collected in 1061 ms
  • conflate()处理背压

合并发射项,不对每一个值进行处理:收集时过滤中间元素

缺点:每次取最新值,可能跳过中间的值

@Test
fun `test flow back pressure`() = runBlocking<Unit> {
    val time = measureTimeMillis {
        simpleFlow8()
            .conflate()
            .collect { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
    }
    println("Collected in $time ms")
}
// Emitting 1 Test worker @coroutine#2
// Emitting 2 Test worker @coroutine#2
// Emitting 3 Test worker @coroutine#2
// Collected 1 Test worker @coroutine#1
// Collected 3 Test worker @coroutine#1
// Collected in 756 ms
  • collectLatest() 处理背压

取消并重新发射最后一个值,这里只收集了最后一个值


@Test
fun `test flow back pressure`() = runBlocking<Unit> {
    val time = measureTimeMillis {
        simpleFlow8()
            .collectLatest { value ->
                delay(300)   //处理这个元素消耗300ms
                println("Collected $value ${Thread.currentThread().name}")
            }
    }
    println("Collected in $time ms")
}
// Emitting 1 Test worker @coroutine#2
// Emitting 2 Test worker @coroutine#2
// Emitting 3 Test worker @coroutine#2
// Collected 3 Test worker @coroutine#5
// Collected in 710 ms
  • flowOn 处理背压

当必须更改CoroutineDispatcher时,flowOn:操作符使用了相同的缓冲机制,但 是buffer函数显式地请求缓冲而不改变执行上下文。

下面示例:将生产者使用flowOn放到后台协程,这种更改了协程上下文,结果同样是1061 ms,原理是协程对不同线程默认实现了缓冲

fun simpleFlow8() = flow<Int> {
    for (i in 1..3) {
        delay(100)
        emit(i)
        println("Emitting $i ${Thread.currentThread().name}")
    }
}

@Test
fun `test flow back pressure`() = runBlocking<Unit> {
    val time = measureTimeMillis {
        simpleFlow8()
            .flowOn(Dispatchers.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 Test worker @coroutine#1
// Collected 2 Test worker @coroutine#1
// Collected 3 Test worker @coroutine#1
// Collected in 1061 ms

2.0 流的异常处理

当运算符中的发射器或代码抛出异常时,有几种处理异常的方法:

上游使用catch()声明式处理函数,下游用try/catch 命令式函数

try/catch块

catch函数

1.接收流 出现异常

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

@Test
fun `test flow exception`() = runBlocking<Unit> {
    try {
        simpleFlow().collect { value ->
            println(value)
            // Throws an IllegalStateExceptio
            check(value <= 1) { "Collected $value" }
        }
    } catch (e: Throwable) {
        println("Caught $e")
    }
}
// Emitting 1
// 1
// Emitting 2
// 2
// Caught java.lang.IllegalStateException: Collected 2
  1. 发射流 出现异常

但是对于流的发送方来说,try/catch块会打破flow的设计原则,因此不建议使用。

建议使用flow提供的catch函数:

@Test
fun `test flow exception2`() = runBlocking<Unit> {
    flow {
        emit(1)
        throw ArithmeticException("Div 0")
    }.catch { e: Throwable -> println("Caught $e") }
            .flowOn(Dispatchers.IO)
            .collect { println(it) }
}
// Caught java.lang.ArithmeticException: Div 0
// 1
    1. 异常恢复

可以通过在catch中再次emit来达到恢复异常的效果

3.0 流的完成

当流收集完成时(普通情况或异常情况),它可能需要执行一个动作。

  • 命令式finally块

  • onCompletion声明式处理

    • onCompletion只能获取异常信息,不能捕获异常,如果需要程序不崩溃,需要catch异常
fun simpleFlow2() = (1..3).asFlow()

@Test
fun `test flow complete in finally`() = runBlocking<Unit> {
  	// 命令式finally块【示例】
    try {
        simpleFlow2().collect { println(it) }
    } finally {
        println("Done")
    }
}

@Test
fun `test flow complete in onCompletion`() = runBlocking<Unit> {
  	// onCompletion声明式处理【示例】
    simpleFlow2()
            .onCompletion { println("Done") }
            .collect { println(it) }
}

onCompletion只能获取异常信息,不能捕获异常,如果需要程序不崩溃,需要catch异常

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

@Test
fun `test flow complete in onCompletion`() = runBlocking<Unit> {
    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不仅可以捕获发送流的异常,亦可以捕获collect阶段的异常

fun simpleFlow2() = (1..3).asFlow()

@Test
fun `test flow complete in onCompletion`() = runBlocking<Unit> {
    simpleFlow2()
        .onCompletion { exception ->
            if (exception != null) println("Flow completed exceptionally")
        }
        .collect { value ->
            println(value)
            check(value <= 1) { "Collected $value" }
        }
}
// 1
// 2
// Flow completed exceptionally

FlowPractice 协程的Flow实践

flow_samples.png

flow_page_struction.png

  • Download 文件下载

Flow 主要用于 异步返回多个值,文件下载就是Flow 最经典的一个应用场景

flow_download.jpeg

  • Retrofit :网络数据
  • Room :Jetpack 本地数据库
  • stateFlow & SharedFlow :热流

FLOW 是冷流

什么是冷流?

简单来说,就是如果Flow 有了订阅者Collector 以后,发射出来的值才会实实在在存在于内存中。 这跟懒加载的概念很像。

与之相对的是 热流StateFlow 和 SharedFlow 是热流,在垃圾回收之前,都是存在内存之中的,并且处于活跃状态。

stateFlow : 是一个状态器式 可观察数据流,可以向其收集器发出当前状态更新和新状态更新。 还可通过其 value属性读取当前状态值。

Flow 与 jetpack paging3

paging3

  • 加载数据的流程

  • 分页逻辑

  • 上游数据的缓存:屏幕旋转后,都会重新加载数据,解决此种情况 .cachedIn(viewModelScope)

viewmodel, 放到属性上面去保存

优化后,同时不会有内存泄漏的风险

优化前:

fun loadMovie() : Flow<PagingData<Movie>> {
        return Pager(
            config = PagingConfig(
                pageSize = PAGING_PAGE_SIZE,
                // 滑动到 item位置加载更多
                prefetchDistance = 1,
                initialLoadSize= PAGING_INITIAL_PAGE_SIZE
            ),
            pagingSourceFactory = {MoviePagingSource()}
        ).flow
    }

优化后:

 private val movies by lazy {
        Pager(
            config = PagingConfig(
                pageSize = PAGING_PAGE_SIZE,
                // 滑动到 item位置加载更多
                prefetchDistance = 1,
                initialLoadSize= PAGING_INITIAL_PAGE_SIZE
            ),
            pagingSourceFactory = {MoviePagingSource()}
        ).flow.cachedIn(viewModelScope)
    }
    
    fun loadMovie() : Flow<PagingData<Movie>> = movies

项目地址:gitee.com/vipvvitt/ko…