Kotlin Flow 操作符

前言

Kotlin 拥有函数式编程的能力,使用Kotlin开发,可以简化开发代码,层次清晰,利于阅读。

然而Kotlin拥有操作符很多,其中就包括了flow。Kotlin Flow 如此受欢迎大部分归功于其丰富、简洁的操作符,巧妙使用Flow操作符可以大大简化我们的程序结构,提升可读性与可维护性。本篇文章列举一些比较常用 Kotlin 操作符,通过关键原理与使用场景列子来讲解Flow操作符。

1. collect接收操作符

用于数据接收,此操作符没有返回对象,后面不可再添加操作符:

kotlin 复制代码
fun flowCollect() {
//流启动
        viewModelScope.launch {
            flow {
                for (i in 1..3) {
                    Log.d("TAG"," flowCollect $i")
                    emit(i)
                    delay(1000)
                }
            }.collect {
                Log.d("TAG"," collect $it")
            }//后面不可再接收其他操作符
        }
    }

2. launchIn操作符

流的启动主要有两种,一种是上面的作用域.launch启动一个流,用collect操作符接收数据;

一种是launchIn操作符启动流,官方不建议用这种,可能出于数据安全考虑,一般建议在onResume方法调用后启动协程,因为怕数据接收了,但View还没创建出来。但链式调用真的很好用,用onEach操作符接收数据。

kotlin 复制代码
 flow {
            for (i in 1..3) {
                Log.d("TAG"," flowCollect $i")
                emit(i)
                delay(1000)
            }
        }.onCompletion {
            Log.d("TAG","onCompletion ")
        }.launchIn(viewModelScope)//在ViewModel中,直接launchIn在ViewModelScope作用域

3. onEach操作符

返回一个流,该流在上游流的每个值向下游发出之前调用给定的操作。也可以用来接收数据,与上面collect不同的是此操作符返回流,我们后面还可接其他操作符。上面的launchIn操作符启动时,可用于接收数据。

kotlin 复制代码
 fun flowOnEach() {
        flow {
            for (i in 1..3) {
                LogUtils.d("Emitting $i")
                emit(i)
                delay(1000)
            }
        }.onEach {
            LogUtils.d("onEach $it")
        }.onCompletion {
            LogUtils.d("onCompletion ")
        }.launchIn(viewModelScope)
    }

4. reduce操作符

reduce 操作符可以将所有数据累加(加减乘除)得到一个结果,如下所示:

kotlin 复制代码
    fun testReduce(){
        val result = listOf(1, 2, 3).reduce { a, b ->
            a + b
        }
        print("list reduce result:$result")
    }

查看测试结果输出如下:

注意⚠️:

1: 如果 flow 中没有数据,将抛出异常。如不希望抛异常,可使用 reduceOrNull 方法。

2: reduce 操作符不能变换数据类型。比如,Int 集合的结果不能转换成 String 结果。

5. fold操作符

fold 和 reduce 很类似,但是 fold 可以变换数据类型

有时候,我们不需要一个结果值,而是需要继续操 flow,可使用 runningFold :

kotlin 复制代码
flowOf(1, 2, 3).runningFold("a") { a, b ->
    a + b
}.collect {
    println(it)
}

查看输出结果:

bash 复制代码
a
a1
a12
a123

同样的,reduce 也有类似的方法 runningReduce:

kotlin 复制代码
flowOf(1, 2, 3).runningReduce { a, b ->
    a + b
}.collect {
    println(it)
}

查看输出结果:

bash 复制代码
1
3
6

6. debounce操作符

debounce 需要传递一个毫秒值参数,功能是:只有达到指定时间后才发出数据,最后一个数据一定会发出。

例如,定义 1000 毫秒,也就是 1 秒,被观察者发出数据,1秒后,观察者收到数据,如果 1 秒内多次发出数据,则重置计算时间。

kotlin 复制代码
flow {
    emit(1)
    delay(500)
    emit(2)
    delay(550)
    emit(3)
    delay(1000)
    emit(4)
    delay(1010)
}.debounce(
    1000
).collect {
    println(it)
}

查看输出结果,只有休眠1000和1010符合要求:

bash 复制代码
3
4

所以,rebounce 的应用场景是限流功能。

7. sample操作符

sample 和 debounce 很像,区别是:在规定时间内,只发送一个数据:

kotlin 复制代码
flow {
    repeat(4) {
        emit(it)
        delay(50)
    }
}.sample(100).collect {
    println(it)
}

输出结果:

bash 复制代码
1
3

所以,sample 的应用场景是截流功能。

8. flatmapMerge操作符

简单的说就是获得两个 flow 的乘积或全排列,合并并且平铺,发出一个 flow。

kotlin 复制代码
flowOf(1, 3).flatMapMerge {
    flowOf("$it a", "$it b")
}.collect {
    println(it)
}

输出结果:

bash 复制代码
1 a
1 b
3 a
3 b

注意⚠️:flatmapMerge 还有一个特性,flatmapMerge 可以设置并发量,可以理解为 flatmapMerge 是线程安全的,而 flatmapConcat 不是线程安全的。会在下一个操作符里提及。

9. flatmapConcat操作符

举个列子:

kotlin 复制代码
flowOf(1, 3).flatMapConcat {
    flowOf("a", "b", "c")
}.collect {
    println(it)
}

功能和 flatmapMerge 一致,不同的是 flatmapMerge 可以设置并发量,可以理解为 flatmapMerge 是线程安全的,而 flatmapConcat 不是线程安全的。

本质上,在 flatmapMerge 的并发参数设置为 1 时,和 flatmapConcat 基本一致,而并发参数大于 1 时,采用 channel 的方式发出数据,具体内容请参阅源码。

10. buffer操作符

介绍 buffer 的时候,先要看这样一段代码:

kotlin 复制代码
flowOf("A", "B", "C", "D")
.onEach {
    println("1 $it")
}
.collect { println("2 $it") }

我们注意查看一下输出结果:

bash 复制代码
1 A
2 A
1 B
2 B
1 C
2 C
1 D
2 D

如果我们加上 buffer 的代码:

kotlin 复制代码
flowOf("A", "B", "C", "D")
.onEach {
    println("1 $it")
}
.buffer()
.collect { println("2 $it") }

在查看一下加上 buffer 的代码的输出结果:

bash 复制代码
1 A
1 B
1 C
1 D
2 A
2 B
2 C
2 D

对比俩次输出结果,会发现输出内容有所不同,buffer 操作符可以改变收发顺序,像有一个容器作为缓冲似的,在容器满了或结束时,下游开始接到数据,onEach 添加延迟,效果更明显。

11 combine操作符

合并两个 flow,长的一方会持续接受到短的一方的最后一个数据,直到结束:

kotlin 复制代码
flowOf(1, 3).combine(
    flowOf("a", "b", "c")
) { a, b -> b + a }
.collect {
    println(it)
}

查看输出结果如下:

bash 复制代码
a1
b3
c3

12. zip操作符

也是合并两个 flow,结果长度与短的 flow 一致,很像木桶原理。

kotlin 复制代码
    fun testZip() {
        runBlocking {
            val time = measureTimeMillis {
                val flow1 = flow { emit("a") emit ("b") emit ("c") emit ("d") }
                val flow2 = flow { emit("1") emit ("2") } flow1 . zip (flow2) { sex, subject -> "$sex-->$subject" }.collect { println(it) }
            } println ("use time:$time")
        }
    }

查看输出结果:

bash 复制代码
a-->1
b-->2
use time:71

通过输出可以看出flow2先结束了,并且flow1没发送完成。

zip原理简单来说:

可以看出,zip的特点:

短的Flow结束,另一个Flow也结束。

13. flatMapLatest操作符

与 collectLatest 操作符类似,处理最新值,也有相对应的"最新"展平模式,在发出新流后立即取消先前流的收集。 这由 flatMapLatest 操作符来实现。

14. distinctUntilChanged操作符

和前一个数据不同,才能收到,和前一个数据相同,则会被过滤掉。

kotlin 复制代码
flowOf(1, 1, 2, 2, 3, 1).distinctUntilChanged().collect {
    println(it)
}

运行查看输出结果:

bash 复制代码
1
2
3
1

总结

Kotlin 拥有函数式编程的能力,同时Kotlin拥有很多操作符,其中就包括了flow。合理使用Kotlin操作符可以使我们的代码更加简化,层次清晰,利于阅读。因此,要灵活使用Kotlin操作符。

参考:

  1. 协程文档
  2. 协程中文网
  3. 这一次,让Kotlin Flow 操作符真正好用起来
  4. Kotlin 协程Flow主要操作符(一)
  5. Kotlin 协程Flow主要操作符(二)
相关推荐
百锦再24 分钟前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗36 分钟前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO42 分钟前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade2 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下2 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
喵手2 小时前
从 Java 到 Kotlin:在现有项目中迁移的最佳实践!
java·python·kotlin
青春给了狗4 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu4 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋6 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin