观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言

 版权声明

  本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17588612.html

  本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
 

  Flow是配合Kotlin协程使用的异步编程工具。其实Flow的概念并不是独家的,更早之前Java端就有自带的stream与大名鼎鼎的RxJava,它们的思想都是响应式编程思想(或者也可以称呼链式编程),当时的响应式编程思想就是为了解决Java各个线程的异步处理结果进行同步。其更底层的思想核心是观察者模式或者生产消费者模式。所以回到Flow,它看似复杂有很多新概念,其实核心归根结底就是观察者模式思想与生产消费者模式思想。

Flow的使用结构

一个简单的例子

一个极简的例子快速了解一下,emit为发射,collect为接收

fun demo1() {
    GlobalScope.launch {
        flow<Int> {
            for (i in 0..10) {
                //emit为发射函数
                emit(i)
            }
        }.collect {
            //collect接收,它还是挂起函数必须在协程中调用
            Log.e("zh", "结果 = ${it} ")
        }
    }
}

/*  输出结果:
   结果 = 0
   结果 = 1
   结果 = 2
   结果 = 3
   结果 = 4
   结果 = 5
   结果 = 6
   结果 = 7
   结果 = 8
   结果 = 9
   结果 = 10
*/

构建方式

Flow有多种多样的构建方式,下面一一举例:

方式一 默认方式 

以默认的方式创建flow,上面的例子已经举例了,这里再举例一下。

fun demo1() {
    GlobalScope.launch {
        flow<String> {
            delay(500)
            emit("苹果")
            delay(500)
            emit("西瓜")
            delay(500)
            emit("香蕉")
            delay(500)
            emit("哈密瓜")
        }.collect{
            Log.e("zh", "时间 = ${System.currentTimeMillis()} 结果 = ${it}", )
        }
    }
}

/*  
    输出结果:
    时间 = 1690598408614 结果 = 苹果
    时间 = 1690598409116 结果 = 西瓜
    时间 = 1690598409617 结果 = 香蕉
    时间 = 1690598410118 结果 = 哈密瓜
 */

方式二 从集合发射流

通过集合调用asFlow()函数

fun demo2() {
    GlobalScope.launch {
        //从集合发射流
        (0..3).asFlow().collect {
            Log.e("zh", "结果 = ${it}", )
        }
        
        listOf<String>("苹果","西瓜","香蕉","哈密瓜").asFlow().collect{
            Log.e("zh", "结果 = ${it}", )
        }
    }
}
/*
    输出结果:
        结果 = 0
        结果 = 1
        结果 = 2
        结果 = 3
        结果 = 苹果
        结果 = 西瓜
        结果 = 香蕉
        结果 = 哈密瓜
 */

方式三 自建集合发射流

通过调用flowOf()函数

fun demo2() {
    GlobalScope.launch {
        flowOf("苹果" to 1, "香蕉" to 2, "西瓜" to 3).collect { pair ->
            Log.e("zh", "结果 = ${pair.first} ${pair.second}", )
        }
    }
}
/*
    输出结果:
        结果 = 苹果 1
        结果 = 香蕉 2
        结果 = 西瓜 3
 */

冷流

Flow默认是冷流的,冷流的概念是只有订阅者订阅后,发布者才会生产数据。 并且订阅者与发布者是一一对应关系,数据只会给目标订阅者发送,不会发送给其他订阅者。这类似于你去工厂下订单,工厂才会生产你需要的产品,并且只发送给你。

以下代码验证Flow的冷流特性,下面用了一个for循环,分别间隔一秒collect一次,而从结果我们可以看到每次的时间都是不一样的,这说明每一次collect,flow的代码块都重新执行了一遍:

fun demo3() {
    val flow = flow<String> {
        val simpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
        val timeString = simpleDateFormat.format(System.currentTimeMillis())
        emit(timeString)
    }

    GlobalScope.launch {
        for (index in 0..2){
            delay(1000)
            flow.collect {
                //collect调用它了,flow才会执行
                Log.e("zh", "结果 = ${it} ")
            }
        }
    }
}

/*
    输出结果:
        结果 = 13:53:53
        结果 = 13:53:54
        结果 = 13:53:55
 */

热流

有冷流就会有热流的概念,热流其实是观察者模式(或者广播)的概念,发布端无论有没有订阅者,都会始终执行,并且有多个订阅者时,发布端跟订阅者是一对多的关系,热流可以与多个订阅者共享信息。

在kotlin的flow创建热流主要是依靠shareIn与stateIn这两个函数。但是shareIn与stateIn是不一样的,它们的区别如下

1.shareIn是分享,它会返回SharedFlow,它用于流的全部完整数据的缓存与分享。

2.stateIn是状态记录,它会返回StateFlow,StateFlow继承于SharedFlow类, StateFlow 和 LiveData 的行为是类似的. 会发射最后一个数据给订阅者.

shareIn

shareIn(分享)函数,也是一个发送者对多个订阅者的概念,但是shareIn创建的热流还是与广播或者观察者模式有些不一样。shareIn创建的热流有以下几个特点:

1.shareIn的热流数据是粘性的,flow的热流会根据你设置的数值,缓存对应数量的数据。在你订阅的时候在分享给你。

2.shareIn的热流发送数据不能连续没有变化,shareIn好像自带间隔数据的去重功能(这部分过滤重复数据的概念会在下面的例子详细说明)。这点很重要!不理解它会让你以为数据出现了丢失!

fun demo1() {
    val job = SupervisorJob()
    val mainScope = CoroutineScope(Dispatchers.Main + job)
    val simpleDateFormat = SimpleDateFormat("HH:mm:ss:SSS", Locale.getDefault())

    val sharedFlow = flow {
        for (i in 0..5){
            val timeString = simpleDateFormat.format(System.currentTimeMillis())
            Log.e("zh", "触发发送数据 index = $i 时间 = ${timeString}")
            /*
                如果发送单单的时间戳字符串,时间戳字符串很多是一样的时间字符串,这会导致数据去重。
                为了避免这种情况这里添加了序号数值。
             */
            emit("序号 = $i 时间 = ${timeString}")
        }
        /*
          解释shareIn的参数:
            scope 是创建热流的协程域
            started 为热流的启动模式
            replay为缓存数据的数量,不能设置为0,否则在没有订阅者时会立刻丢弃缓存的数据
         */
    }.shareIn(scope = mainScope, started = SharingStarted.Eagerly, replay = 5)


    /*
       这里创建了两个协程并且延迟错开分别调用了collect,这.是为了证明上面的flow发送数据的代码块里只执行了一遍。
     */
    GlobalScope.launch {
        delay(2000)
        sharedFlow.collect{
            Log.e("zh", "结果A = ${it} ", )
        }
    }
    GlobalScope.launch {
        delay(3000)
        sharedFlow.collect{
            Log.e("zh", "结果B = ${it} ", )
        }
    }
}

/*
输出结果:
    触发发送数据 index = 0 时间 = 19:57:55:631
    触发发送数据 index = 1 时间 = 19:57:55:634
    触发发送数据 index = 2 时间 = 19:57:55:634
    触发发送数据 index = 3 时间 = 19:57:55:635
    触发发送数据 index = 4 时间 = 19:57:55:635
    触发发送数据 index = 5 时间 = 19:57:55:635
    结果A = 序号 = 1 时间 = 19:57:55:634
    结果A = 序号 = 2 时间 = 19:57:55:634
    结果A = 序号 = 3 时间 = 19:57:55:635
    结果A = 序号 = 4 时间 = 19:57:55:635
    结果A = 序号 = 5 时间 = 19:57:55:635
    结果B = 序号 = 1 时间 = 19:57:55:634
    结果B = 序号 = 2 时间 = 19:57:55:634
    结果B = 序号 = 3 时间 = 19:57:55:635
    结果B = 序号 = 4 时间 = 19:57:55:635
    结果B = 序号 = 5 时间 = 19:57:55:635
*/

shareIn的三种启动模式

通常会选择WhileSubscribed(),因为这个比较节约性能,会停止

/**
 * shareIn被调用后,共享数据(立即执行flow发送数据的代码块)是立即开始,永远不会停止。
*/
 public val Eagerly: SharingStarted = StartedEagerly()

/**
 * 第一个collect(订阅者)被调用后,共享数据(flow发送数据的代码块)才会开始,永远不会停止。
*/
public val Lazily: SharingStarted = StartedLazily()

/**
 * 第一个collect(订阅者)被调用后,共享数据(flow发送数据的代码块)才会开始,在最后一个订阅者消失时立即停止(默认情况下),并永久保留重播缓存(默认情况下)。
 * 它具有以下可选参数:
 * [stopTimeoutMillis] 配置最后一个订阅者消失和停止共享协程之间的延迟(以毫秒为单位)。默认为零(立即停止)。
 * [replayExpirationMillis] 在停止共享协同程序和重置重放缓存之间配置一个延迟(以毫秒为单位)(这使得[shareIn]操作符的缓存为空,并将[stateIn]操作符的缓存值重置为原始的' initialValue ')。
 * 默认为' Long '。MAX_VALUE '(永远保持重播缓存,永远不会重置缓冲区)。使用零值将使缓存立即过期。
*/
public fun WhileSubscribed(stopTimeoutMillis: Long = 0, replayExpirationMillis: Long = Long.MAX_VALUE)

stateIn

使用stateIn(记录)函数也可以一对多,但是它只会将最后一个数据分享给订阅者。

fun demo1() {
    val simpleDateFormat = SimpleDateFormat("HH:mm:ss:SSS", Locale.getDefault())

    GlobalScope.launch {
        val stateFlow = flow {
            for (i in 0..5){
                val timeString = simpleDateFormat.format(System.currentTimeMillis())
                Log.e("zh", "触发发送数据 index = $i 时间 = ${timeString}")
                emit("序号 = $i 时间 = ${timeString}")
            }
        }.stateIn(this)//使用默认的stateIn(scope: CoroutineScope),也是stateIn被调用后flow代码块里的代码就会立即执行

        GlobalScope.launch {
            delay(2000)
            stateFlow.collect{
                Log.e("zh", "结果A = ${it} ", )
            }
        }
        GlobalScope.launch {
            delay(3000)
            stateFlow.collect{
                Log.e("zh", "结果B = ${it} ", )
            }
        }
    }
}

/*
  输出结果
    触发发送数据 index = 0 时间 = 14:03:18:400
    触发发送数据 index = 1 时间 = 14:03:18:402
    触发发送数据 index = 2 时间 = 14:03:18:402
    触发发送数据 index = 3 时间 = 14:03:18:402
    触发发送数据 index = 4 时间 = 14:03:18:402
    触发发送数据 index = 5 时间 = 14:03:18:403
    结果A = 序号 = 5 时间 = 14:03:18:403
    结果B = 序号 = 5 时间 = 14:03:18:403
 */

除了上面的,stateIn还有一个重载函数,

这个函数其实与我在最上面举例的shareIn的实现是相似的,这里就不重复举例了,第一个参数是协程的作用域,并且在第二个参数上也可以填入上面的三种启动模式,第三个参数为默认值

public fun <T> Flow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T
): StateFlow<T> {
    val config = configureSharing(1)
    val state = MutableStateFlow(initialValue)
    val job = scope.launchSharing(config.context, config.upstream, state, started, initialValue)
    return ReadonlyStateFlow(state, job)
}

背压概念 与 collectLatest 停止当前工作以收集最新值

Backpressure(背压)

Backpressure(背压)是指在异步场景中,被观察者发送事件速度远快于观察者的处理速度的情况。在上面的例子中收集都使用了collect函数, collect会收集每一个值并且保证执行完处理代码。 但是如果collect正在处理耗时数据,而flow的发送端是不会停下来,发送端会一直发送,而collect则会堵塞发过来的数据,等待当前collect函数代码块里的耗时逻辑处理完,才会执行下一个发送过来的数据。这样会导致我们看到最后最新的数据时已经延迟了很久。

collectLatest 停止当前工作以收集最新值

上面的背压情况,在一些业务下并不希望耗时等待,只想尽快看到最后最新的数据,这个时候就可以使用collectLatest来替代collect进行数据收集。collectLatest并不会保证每一个发送来的时间都处理完,当有新数据发送过来时,如果collectLatest的代码块没有执行完成,它会立刻停止处理老数据。去处理最新发送过来的数据。

代码例子

fun demo1() {
    GlobalScope.launch {
        flow<Int> {
            for (i in 0..2){
                //请注意这里延迟50毫秒是为了对比下面collectLatest的延迟
                delay(50)
                emit(i)
            }
        }.collectLatest{
            //这里分别延迟了2次50毫秒,是为了表示这个代码块里的耗时,是始终大于上面发送端的。
            delay(50)
            Log.e("zh", "模拟正在耗时处理 = ${it}")
            delay(50)
            Log.e("zh", "最终处理完结果 = ${it}")
        }
    }
}

/*
  输出结果:
    模拟正在耗时处理 = 0
    模拟正在耗时处理 = 1
    模拟正在耗时处理 = 2
    最终处理完结果 = 2
 */

filter-过滤

filter属于中间操作符,将数据过滤

fun demo3() {
    GlobalScope.launch {
        flow<Int> {
            for (i in 0..10){
                emit(i)
            }
        }.filter {
            //除余,过滤掉奇数
            it % 2 == 0
        }.collect{
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        结果 = 0
        结果 = 2
        结果 = 4
        结果 = 6
        结果 = 8
        结果 = 10
 */

filterNot-过滤

filterNot属于中间操作符, 它是反向过滤的,会将true的结果去除,将false结果继续发射

fun demo3() {
    GlobalScope.launch {
        flow<Int> {
            for (i in 0..10){
                emit(i)
            }
        }.filterNot {
            //除余,过滤掉偶数
            it % 2 == 0
        }.collect{
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        结果 = 1
        结果 = 3
        结果 = 5
        结果 = 7
        结果 = 9
 */

filterNotNull-过滤null值

filterNotNull属于中间操作符,它会将null过滤掉。

fun demo1() {
    GlobalScope.launch {
        flow<String?> {
            //这里的集合故意添加了一个null值
            listOf<String?>("苹果", "香蕉", null, "芒果").forEach {
                Log.e("zh", "要发送的值 = ${it}")
                emit(it)
            }
        }.filterNotNull().collect {
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        要发送的值 = 苹果
        结果 = 苹果
        要发送的值 = 香蕉
        结果 = 香蕉
        要发送的值 = null
        要发送的值 = 芒果
        结果 = 芒果
 */

filterIsInstance-类型过滤

filterIsInstance属于中间操作符,它会过滤数据类型

fun demo1() {
    GlobalScope.launch {
        flow {
            emit("苹果")
            emit(1)
            emit(true)
            emit("芒果")
        }.filterIsInstance<String>() //这里的filterIsInstance类型为String,过滤除了String以外的类型
            .collect {
                Log.e("zh", "结果 = ${it}")
            }
    }
}

/*
    输出结果:
        结果 = 苹果
        结果 = 芒果
 */

map-转换组合

map属于中间操作符,用于将数据转换或者组合

fun demo3() {
    val fruits = listOf<String>("苹果", "香蕉", "芒果")
    GlobalScope.launch {
        flow<Int> {
            for (i in 0..2) {
                emit(i)
            }
        }.map { it: Int ->
            Pair<String, Int>(fruits[it], it)
        }.collect { it: Pair<String, Int> -> //这里其实可以不用写it: Pair<String, Int>,写出来就是想表达,上面的flow<Int>被转换成了Pair<String, Int>
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        结果 = (苹果, 0)
        结果 = (香蕉, 1)
        结果 = (芒果, 2)
 */

mapLatest-转换组合

mapLatest属于中间操作符

有时候我们的map方法处理转换需要耗时,但是发送端会一直发射新数据,导致map的耗时堆积积累,这会出现获取最后一个数据时已经耗时了很久。在一些情况下我们并不关切前面的数据,只希望获取最新的数据,这里就可以使用mapLatest。

fun demo3() {
    val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
    val flow = flow<Int> {
        for (i in 1..3){
            emit(i)
        }
    }
    GlobalScope.launch {
        //这里实现map作为下面mapLatest的对比
        flow.map {
            //这里写30毫秒延迟,模拟处理数据需要耗时
            delay(30)
            timeFormat.format(System.currentTimeMillis()) to it
        }.collect{
            Log.e("zh", "map >> 结果 = ${it}")
        }

        flow.mapLatest {
            //这里写30毫秒延迟,模拟处理数据需要耗时
            delay(30)
            timeFormat.format(System.currentTimeMillis()) to it
        }.collect{
            Log.e("zh", "mapLatest >> 最新数据结果 = ${it}")
        }
    }
}

/*
输出结果:
    map >> 结果 = (15:25:03, 1)
    map >> 结果 = (15:25:03, 2)
    map >> 结果 = (15:25:03, 3)
    mapLatest >> 最新数据结果 = (15:25:03, 3)
 */

mapNotNull-转换组合不包含null值

mapNotNull属于中间操作符

fun demo1() {
    val fruits = listOf<String>("苹果","芒果","水蜜桃","茄子")
    GlobalScope.launch {
        flow<String> {
            for (item in fruits){
                emit(item)
            }
        }.mapNotNull {
            if (it == "茄子"){
                //茄子不属于水果,返回null
                null
            } else {
                System.currentTimeMillis() to it
            }
        }.collect{
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        结果 = (1690620115923, 苹果)
        结果 = (1690620115923, 芒果)
        结果 = (1690620115923, 水蜜桃)
 */

drop-弃用指定数量前段数据

drop属于中间操作符,它会弃用你给定数量的前段数据。

fun demo1() {
    GlobalScope.launch {
        flow {
            emit(1)
            emit(2)
            emit(3)
            emit(4)
        }.drop(2).collect{
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        结果 = 3
        结果 = 4
 */

dropWhile-找到首个不满足条件的值,然后继续发送它和剩下的数据

dropWhile属于中间操作符,你不能简单的将它理解成判断范围(这变成跟上面的过滤操作符一样了),它其实用来找到数据集合的执行出发点。

fun demo1() {
    GlobalScope.launch {
        flow {
            emit(1)
            emit(2)
            emit(3)
            emit(4)
            emit(5)
            emit(6)
        }.dropWhile { it != 3 } //因为dropWhile的特性,3这个值也会继续发送
            .collect {
                Log.e("zh", "结果 = ${it}")
            }
    }
}

/*
    输出结果:
        结果 = 3
        结果 = 4
        结果 = 5
        结果 = 6
 */

take-只允许指定数量的前段数据发送

take属于中间操作符,它只允许指定数量的前段数据发送

fun demo1() {
    GlobalScope.launch {
        flow {
            emit(1)
            emit(2)
            emit(3)
            emit(4)
            emit(5)
            emit(6)
        }.take(3).collect {
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        结果 = 1
        结果 = 2
        结果 = 3
 */

takeWhile-找到首个不满足条件的值,发送它前面的值

takeWhile属于中间操作符

fun demo1() {
    GlobalScope.launch {
        flow {
            emit(1)
            emit(2)
            emit(3)
            emit(4)
            emit(5)
            emit(6)
        }.takeWhile { it != 3 }.collect {
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
    输出结果:
        结果 = 1
        结果 = 2
 */

debounce-防抖动,指定数据接收的间隔时间

debounce属于中间操作符,debounce这个函数有点复杂。它的意思是指每一次发送端发送数据的时间间隔,如果小于设置的间隔时间那就不发送,但是会发送最后一个数据与大于间隔时间的数据。

fun demo1() {
    GlobalScope.launch {
        flow {
            emit(1)
            delay(990)
            emit(2)
            delay(990)
            emit(3)
            delay(990)
            emit(4)
            delay(990)
            emit(5)
            delay(1100)
            emit(6)
            delay(1100)
            emit(7)
            delay(990)
            emit(8)
            delay(990)
            emit(9)
            delay(990)
            emit(10)
        }.debounce(1000).collect {
            //debounce执行结果因为1、2、3、4、7、8、9的间隔是990,小于1000,所以都不会发送,而10则是最后一个数据所以会被发送
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
输出结果:
    结果 = 5
    结果 = 6
    结果 = 10
*/

sample-定时周期接收

sample属于中间操作符,它会在指定时间内,周期性收集数据

fun demo1() {
    GlobalScope.launch {
        flow {
            emit(1)
            delay(100)
            emit(2)
            delay(100)
            emit(3)
            delay(100)
            emit(4)
            delay(100)
            emit(5)
            delay(100)
            emit(6)
            delay(100)
            emit(7)
            delay(100)
            emit(8)
            delay(100)
            emit(9)
            delay(100)
            emit(10)
        }.sample(200).collect {
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
输出结果:
    结果 = 2
    结果 = 4
    结果 = 6
    结果 = 8
*/

distinctUntilChanged与distinctUntilChangedBy-过滤重复数据直到发生变化

distinctUntilChanged与distinctUntilChangedBy都属于中间操作符。distinctUntilChanged用于过滤重复数据,但是,请注意!这里的重复过滤并不是指整体数据。流是没有完整数据的概念,所以它过滤上下2个数据之间的重复。下面代码会举例帮助理解。

flowOf(1, 1, 2, 2).distinctUntilChanged().collect {
    Log.e("zh", "结果1 = ${it}")
}
/*
  输出结果
    结果1 = 1
    结果1 = 2
 */


flowOf(1, 2, 1, 2).distinctUntilChanged().collect {
    Log.e("zh", "结果2 = ${it}")
}
/*
输出结果
    结果2 = 1
    结果2 = 2
    结果2 = 1
    结果2 = 2
 */

上面的例子中,第一个结果已经去重了,第二个没有,这是因为第二个数据间隔都发生了变化。

这里展示下distinctUntilChangedBy的使用方式

fun demo1() {
    GlobalScope.launch {
        val list = listOf<String>("苹果","苹果","香蕉","苹果","火龙果","火龙果","西瓜")
        flow {
            for ((index, value ) in list.withIndex()){
                emit(index to value)
            }
        }.distinctUntilChangedBy { it.second }.collect{
            Log.e("zh", "结果 = ${it}" )
        }
    }
}

/*
输出结果:
    结果 = (0, 苹果)
    结果 = (2, 香蕉)
    结果 = (3, 苹果)
    结果 = (4, 火龙果)
    结果 = (6, 西瓜)
*/

zip-将两个流合并组合

zip属于中间操作符,zip可以将两个流的数据组合后输出。此外zip不会被flow的代码块里的逻辑执行耗时所影响,它会严格按照数据顺序一对一合并组合。如果有先发送的数据,那么它只会等另一个flow的数据发送完。

fun demo1() {
    GlobalScope.launch {
        val fruitsFlow = flowOf("苹果", "哈密瓜", "香蕉", "桃子", "菠萝", "火龙果", "西瓜","榴莲")
        val idFlow = flowOf(1, 2, 3)
        //zip的合并会根据那个流数据数量最少,合并对应数量的流
        fruitsFlow.zip(idFlow){ it1, it2->
            return@zip it1 to it2
        }.collect{
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
  输出结果:
    结果 = (苹果, 1)
    结果 = (哈密瓜, 2)
    结果 = (香蕉, 3)
 */

merge-将两个流并发输出

merge与上面的zip的区别是,zip是提供高级函数组合流的数据,而merage则是之间将两个流在一个collect里输出

fun demo1() {
    GlobalScope.launch {
        val fruitsFlow = flowOf("苹果", "哈密瓜", "香蕉")
        val idFlow = flowOf(1, 2, 3, 4)
        merge(fruitsFlow, idFlow).collect {
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
  输出结果:
    结果 = 苹果
    结果 = 1
    结果 = 2
    结果 = 3
    结果 = 4
    结果 = 哈密瓜
    结果 = 香蕉
 */

combine-受耗时影响进行并发组合

  combine属于中间操作符,你咋看会感觉与上面的zip跟相似,但是其实combine与zip的合并完全不同。zip不会被每一个flow代码块的的耗时影响。而combine会根据flow的代码块耗时进行组合。下面A、B与 1 、2两组flow的合并就出现了3个数据。

这里解释下面代码的意思,以及为什么会出现3个数据:

1.首先flow1的A数据先被发送,这个时候flow2的1数据也被发送了,它们会互相组合(A,1)

2.然后flow1需要等待100毫秒,flow2需要等待50毫秒。所以接下来flow2的2数据会被先发送,但是发送后flow1还没有更新数据。所以只能拿flow1的旧数据A与2进行组合(A,2).

3.最后flow1最后执行发送B数据了,但是flow2的数据已经发送完了,所以会跟flow2最后的数据2进行组合(B,2)。

fun demo1() {
    GlobalScope.launch {
        val flow1 = flow {
            emit("A")
            delay(100)
            emit("B")

        }
        val flow2 = flow {
            emit(1)
            delay(50)
            emit(2)
        }
        flow1.combine(flow2){ it1,it2->
            return@combine it1 to it2
        }.collect{
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
  输出结果:
    结果 = (A, 1)
    结果 = (A, 2)
    结果 = (B, 2)
 */

flattenConcat-将多个flow流按顺序展开

flattenConcat属于中间操作符,并且它会将多个flow流按顺序展开后输出。

fun demo1() {
    GlobalScope.launch {
        flow{
            emit(flowOf(1,2,3))
            emit(flowOf("A","B","C"))
        }.flattenConcat().collect{
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
  输出结果:
    结果 = 1
    结果 = 2
    结果 = 3
    结果 = A
    结果 = B
    结果 = C 
 */

flattenMerge-将多个flow流并发展开

flattenMerge属于中间操作符,并且它会将多个flow流按并发后输出

fun demo1() {
    GlobalScope.launch {
        flow{
            emit(flowOf(1,2,3))
            emit(flowOf("A","B","C"))
        }.flattenMerge().collect{
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
  输出结果:
    结果 = 1
    结果 = A
    结果 = B
    结果 = C
    结果 = 2
    结果 = 3
 */

flatMapConcat-将多个flow的按顺序组合

flatMapConcat属于中间操作符,它可以将多个flow按照顺序进行组合,并且不受到时间影响,也不是并发输出。下面的代码中2个flow里都添加了时间延迟,但是都未影响flow按输出顺序进行组合。

fun demo1() {
    GlobalScope.launch {
        flow {
            emit("a")
            delay(100)
            emit("b")
            emit("c")
        }.flatMapConcat { value ->
            flow {
                emit(value + "_")
                delay(200)
                emit(value + "_last")
            }
        }.collect{
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
  输出结果:
    结果 = a_ 
    结果 = a_last 
    结果 = b_ 
    结果 = b_last 
    结果 = c_ 
    结果 = c_last 
 */

flatMapLatest-将多个flow的最后最新数据组合

flatMapLatest属于中间操作符,此函数最会保证最后一个数据的flow执行完整,请参考下面的代码

fun demo1() {
    GlobalScope.launch {
        flow {
            emit("a")
            delay(100)
            emit("b")
        }.flatMapLatest { value ->
            flow {
                emit(value + "_")
                delay(200)
                emit(value + "_last")
            }
        }.collect{
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
  输出结果:
    结果 = a_ 
    结果 = b_ 
    结果 = b_last 
 */

切换线程

fun demo1() {
    GlobalScope.launch {
        flow {
            //这里的线程应该是跟随创建线程
            Log.e("zh", "发送端的线程ID = ${Thread.currentThread().getId()}")
            emit(1)
        }.flowOn(Dispatchers.IO).map {
            Log.e("zh", "中间操作符_1_处理的线程ID = ${Thread.currentThread().getId()}")
            return@map it
        }.flowOn(Dispatchers.Main).map {
            Log.e("zh", "中间操作符_2_处理的线程ID = ${Thread.currentThread().getId()}")
            return@map it
        }.flowOn(Dispatchers.Default).collect {
            Log.e("zh", "接收端的线程ID = ${Thread.currentThread().getId()}  结果 = ${it} ")
        }
    }
}

/*
  输出结果:
    发送端的线程ID = 864
    中间操作符_1_处理的线程ID = 2
    中间操作符_2_处理的线程ID = 864
    接收端的线程ID = 864  结果 = 1
 */

异常捕获

fun demo1() {
    GlobalScope.launch {
        flow {
            emit(1)
            //这里故意创建了一个空指针异常
            throw NullPointerException("没有找到数据")
        }.catch {
            Log.e("zh", "异常捕获 = $it")
        }.collect {
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
  输出结果:
    结果 = 1
    异常捕获 = java.lang.NullPointerException: 没有找到数据
 */

retryWhen-异常重试

抛出异常后,retryWhen方法可以判断后决定是否重试执行flow代码块里的代码

fun demo1() {
    GlobalScope.launch {
        var isTest = true
        flow {
            if (isTest){
                throw NullPointerException("e")
            }
            emit("ABC")
        }.retryWhen { cause, attempt ->
            //触发重试
            if (attempt == 1L){
                isTest = false
            }
            if (cause is NullPointerException){
                Log.e("zh", "触发重试")
                return@retryWhen true //返回true 表示要重试
            }
            return@retryWhen false

        }.catch { //异常捕获一定要在retryWhen下面,否则retryWhen不会触发,此外catch也一定要添加否则会直接报错
            Log.e("zh", "异常", )
        }.collect{
            Log.e("zh", "结果 = ${it} ", )
        }
    }
}

/*
  输出结果:
    触发重试
    结果 = ABC 
 */

flow的生命周期

flow的生命周期函数有4个:

onStart  流准备开始发送前触发

onEach  每一个数据流发出前触发,这一般是用来打log用的或者检查数据

onCompletion  流全部发送完毕后触发

onEmpty  没有任何数据发送时触发

代码

onStart 、onEach  、onCompletion的演示

fun demo1() {
    GlobalScope.launch {
        flow {
            val list = listOf("苹果", "哈密瓜", "香蕉", "桃子")
            for (item in list){
                emit(item)
            }
        }.onStart {
            Log.e("zh", "开始发送流")
        }.onEach {
            Log.e("zh", "每个都执行 = ${it}")
        }.onCompletion {
            Log.e("zh", "流全部发送完毕")
        }.collect{
            Log.e("zh", "结果 = ${it}")
        }
    }
}

/*
  输出结果:
    开始发送流
    每个都执行 = 苹果
    结果 = 苹果
    每个都执行 = 哈密瓜
    结果 = 哈密瓜
    每个都执行 = 香蕉
    结果 = 香蕉
    每个都执行 = 桃子
    结果 = 桃子
    流全部发送完毕
 */

演示onEmpty  

fun demo1() {
    GlobalScope.launch {
        flow<Int> {
            //这里不发送任何数据以触发下面的onEmpty
        }.onEmpty {
            Log.e("zh", "flow未发出数据")
        }.collect {
            Log.e("zh", "结果 = ${it}")

        }
    }
}

/*
  输出结果:
    flow未发出数据
 */

MutableStateFlow与MutableSharedFlow 在实际项目MVVM模式下使用的Flow

MutableStateFlow与MutableSharedFlow是非常重要的两个热流flow!因为它们以后你会真正的经常使用!在看了上面的SharedFlow与StateFlow例子后,你应该能明白MutableStateFlow与MutableSharedFlow的区别,这里不在重复说明。现在你可能会纳闷,为什么有了SharedFlow与StateFlow后还需要MutableStateFlow与MutableSharedFlow? 这两个其实是实际开发中用于MVVM模式下传递数据的关键(当然它们不是MVVM唯一选择,你非要不使用Flow也可以选择之前的MutableLiveData)。下面以MutableStateFlow来举例,实际项目中是如何使用的。

效果图

仓库类(M层)

import kotlinx.coroutines.flow.MutableStateFlow

class DemoRepository {
    val data = MutableStateFlow<String>("null")

    fun postData(content: String) {
        //这里只是模拟修改了数据,实际开发可以数据库更新数据、数据库获取数据、请求接口网络
        data.value = content
    }
}

ViewModel类(VM层)

class DemoViewModel : ViewModel() {
    private val mDemoRepository :DemoRepository = DemoRepository()
    val data: MutableStateFlow<String> = mDemoRepository.data

    fun postData(content:String){
        mDemoRepository.postData(content)
    }
}

Activity(V层)


import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModelProvider

class DemoActivity : AppCompatActivity() {
    private lateinit var mViewModel: DemoViewModel
    
    companion object {
        fun jump(context: Context) {
            context.startActivity(Intent(context, DemoActivity::class.java))
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mViewModel = ViewModelProvider(this).get(DemoViewModel::class.java)
        setContent {
            APage()
        }
    }

    @Composable
    fun APage() {
        val dataState = mViewModel.data.collectAsState()
        Column(
            Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text(
                text = dataState.value,
                fontSize = 40.sp,
                modifier = Modifier.padding(bottom = 50.dp)
            )
            Button(onClick = { mViewModel.postData("设置数据 ${System.currentTimeMillis()}") }) {
                Text(text = "更新数据")
            }
        }
    }
}

 

End

posted on 2023-07-29 15:27  观心静  阅读(829)  评论(0编辑  收藏  举报