在 Kotlin 开发过程中,我们会遇到众多与flow相关的方法。在此,对这些方法进行汇总。以 Kotlin 1.6.1 的源码为例,flow的扩展方法被放置在不同的文件中,如下图所示。下面将依据不同的文件来介绍这些不同的扩展方法。
FlowKt_BuildersKt
在FlowKt_BuildersKt文件中,主要存放着与创建flow相关的方法,具体包括:flow、asFlow、flowOf、emptyFlow、channelFlow以及callbackFlow。这些扩展方法的代码示例如下:
// 使用 flow 创建一个 Flow 流
flow {
// 创建一个冷流 flow
emit(1)
// 内部不能使用 withContext
withContext(Dispatcher.IO) {
emit(2)
}
}
// 使用 asFlow 将集合转换为 Flow 流。除了集合外,还在序列、数组、有返回值的方法上使用
listOf(1, 2, 3).asFlow()
// 创建一个发射指定值的 Flow
flowOf(1, 2, 3)
// 创建一个空的 Flow
emptyFlow()
// 创建一个 channelFlow
channelFlow {
}
// 创建一个 callbackFlow
callbackFlow {
}
flow、asFlow、flowOf 、flowOf、 emptyFlow 这些扩展方法比较好理解。这里比较难理解的是 flow 、channelFlow 和 callbackFlow 方法。虽然它们都是创建一个 flow,但是所创建的 flow 实现和使用上都有很大的不同。channelFlow 和 callbackFlow的区别如下:
- channelFlow,默认带 64个缓存区;使用
send和trySend发送数据 - callbackFlow,可以在 flow 外部发送数据,同时支持
awaitClose
详细地可以看[译]轻松学习Kotlin的Flow、ChannelFlow和CallbackFlow
FlowKt_FlowChannelsKt
FlowKt_FlowChannelsKt 文件中主要存放了 channel 与 flow 相互转换的方法,具体包括 receiveAsFlow、consumeAsFlow 、produceIn。除此之外还有一个 emitAll。代码示例如下:
val sourceFlow = flow {
emit(1)
emit(2)
emit(3)
}
// emitAll 发射一个流
val targetFlow = flow {
emitAll(sourceFlow)
emit(4)
}
// receiveAsFlow 将 Channel 转换成一个 flow
val channel = Channel<Int>()
channel.receiveAsFlow()
// consumeAsFlow 也是将 Channel 转换成一个 flow
channel.consumeAsFlow()
// produceIn 将 flow 转化为 ReceiverChannel;
// ReceiverChannel 是用来接收数据的 channel
flow {
emit(0)
}.produceIn(coroutineScope)
从上面的代码示例中可以看到,receiveAsFlow 和 consumeAsFlow 都会把 Channel 转化为 Flow。但是receiveAsFlow 转化后的 Flow 能多次 collect;而 consumeAsFlow 转化后的 Flow 只能一次 collect,多次会 crash。代码示例如下:
val receiveAsFlow = channel.receiveAsFlow()
receiveAsFlow.collect {
// 当后面 collect 后,这里就不执行了
println("receiveAsFlow collect1 $it")
delay(20)
receiveAsFlow.collect { // 可以多次 collect
println("receiveAsFlow collect2 $it")
}
}
val consumeAsFlow = channel.consumeAsFlow()
consumeAsFlow.collect {
println("consumeAsFlow collect1 $it")
delay(20)
consumeAsFlow.collect { // 这里会 crash
println("consumeAsFlow collect2 $it")
}
}
FlowKt_FlowCollectionKt
FlowKt_FlowCollectionKt 文件中只有三个方法,分别是 toList、 toSet、 toCollection。它们的作用是把 Flow 转化为对应的集合类型。代码示例如下:
val numbersFlow = flowOf(1, 2, 3)
val listResult = numbersFlow.toList() // 转化为 List
val setResult = numbersFlow.toSet() // 转化为 Set
val customCollection = numbersFlow.toCollection(ArrayList()) // 将结果添加到集合
FlowKt_FlowCollectKt
FlowKt_FlowCollectKt 主要存放和收集流中元素的相关方法,具体包括 collect 、launchIn 、collectIndexed 、collectLatest 方法。代码示例如下:
// collect 触发流动
flowOf(1, 2, 3).collect()
// launchIn 在指定的协程作用域中启动一个协程来收集流中的元素
flow {
emit(1)
emit(2)
}.launchIn (lifecycleScope)
// 等同于下面的代码
scope.launch {
flow {
emit(1)
emit(2)
}.collect()
}
// 触发流动,除此之外 collectIndexed 会带有额外的 index 信息
val numbersFlow = flow {
emit(1)
emit(2)
emit(3)
}
numbersFlow.collectIndexed { index, number ->
println("Index: $index, Number: $number")
}
// collectLatest 只收集流中的最新元素,同时取消对旧元素的处理
// 如果 `emit(1)` 后 `emit(2)`;如果 1 的值没有处理完就有新的value 2,那么之前的操作会被取消。
flow {
emit(1)
emit(2)
}.collectLatest { // 这里只会打印 2
delay(500)
println(it)
}
FlowKt_ContextKt
FlowKt_ContextKt 文件主要存放处理中间操作的方法,具体包含 buffer、 conflate 、flowOn cancellable。代码示例如下:
val numbersFlow = flow {
for (i in 1..10) {
delay(100)
emit(i)
}
}
// buffer 会给 Flow 加上缓存,缓存大小默认是 64。
// 1. 当缓存没有满时,同时 collect 代码块还没执行完,会把 emit 的值保存在缓存中
// 2. 当缓存没有满时,同时 collect 代码块执行完了,会通知 collect 代码块处理下一个值
// 3. 当缓存满,同时 collect 代码块还没执行完,这时会挂起 emit 方法
// 4. 当缓存满, collect 代码块执行完了,会通知 collect 代码块处理缓存里的值
numbersFlow.buffer().collect { number ->
println(number)
delay(500)
}
// conflate 不会阻塞发送,也不会收集旧的值。它等同于 `buffer(CONFLATED)`
numbersFlow.conflate().collect { number ->
println(number)
delay(500)
}
// flowOn 指定上游操作的执行线程
withContext(Dispatchers.Main) {
val singleValue = intFlow
.map { ... } // 在 IO 线程执行
.flowOn(Dispatchers.IO)
.filter { ... } // 在 Default 线程执行
.flowOn(Dispatchers.Default)
.single() // 在主线程执行
}
// cancellable 让流可以取消,flow{} 和 shareflow 默认都是可以被取消的
numbersFlow.cancellable()
FlowKt_CountKt
FlowKt_CountKt 文件只有一个方法 count,用来返回流发送值的个数。代码示例如下:
// 发送值的个数,这里 count 为 2
val count = flow {
emit(1)
emit(2)
}.count()
// 满足条件的发送值的个数,这里 count 为 1
val count = flow {
emit(1)
emit(2)
}.count {
it > 1
}
FlowKt_DelayKt
FlowKt_DelayKt 文件有两个方法 debounce、 sample,它们都是用于 Flow 中时间的控制。debounce 可以获取给定时间内最新的值,可以用来防止抖动;sample,获取周期内发出的最新值,用来采样。代码示例如下:
// 使用 debounce 打印 3 4 5
flow {
emit(1)
delay(90)
emit(2)
delay(90)
emit(3)
delay(1010)
emit(4)
delay(1010)
emit(5)
}.debounce(1000)
.collect {
println(it)
}
// 使用 sample 打印 3 4
flow {
emit(1)
delay(90)
emit(2)
delay(90)
emit(3)
delay(1010)
emit(4)
delay(1010)
emit(5)
}.sample(1000)
.collect {
println(it)
}
FlowKt_DistinctKt
FlowKt_DistinctKt 文件有两个方法 distinctUntilChanged 、distinctUntilChangedBy ,它们的作用都是用来去重的。代码示例如下:
// distinctUntilChanged 如果连续两个值一样,则会跳过发送。
// 通过这种方式来确保流中连续发出的相同元素只被发射一次
val numbersFlow = flow {
emit(1)
emit(1)
emit(2)
emit(2)
emit(3)
}
numbersFlow.distinctUntilChanged().collect { number ->
println(number)
}
// distinctUntilChangedBy 可以指定用来判断的 key
val peopleFlow = flow {
emit(Person("Alice", 25))
emit(Person("Bob", 30))
emit(Person("Alice", 25))
emit(Person("Charlie", 35))
}
peopleFlow.distinctUntilChangedBy { person -> person.name }.collect { person ->
println(person.name)
}
FlowKt_EmittersKt
FlowKt_EmittersKt 文件主要存放 flow 的一些回调,比如 onStart、 onCompletion、 onEmpty,以及 transform 方法。代码示例如下:
flow {
emit(1)
emit(2)
1 / 0 // 抛出异常,onCompletion 还是会被调用
emit(3)
}.onStart {
// collect 执行前的回调
println("Flow start")
}.onCompletion {
// 在 flow 取消或者结束时调用,类似于 finally {}
println("Flow completed")
}.onEmpty {
// 当流没有 emit 任何值时调用
println("Flow Empty")
}.collect {
println("collect $it")
}
// 打印结果如下:
Flow start
collect 1
collect 2
Flow completed
异常
除了上面 flow 的回调相关方法外,还有一个 transform 方法,用来做 flow 数据的转换。除此之外,还有一个 map 方法,也是做转换的。这里就有一个问题,它们的区别是什么?
简单来说,就是在 transform 中使用 emit 来发送元素,因此可以增加、修改、删除(不发送)元素;而在 map 中只能映射元素为新的元素。代码示例如下:
flow {
emit(1)
emit(2)
emit(3)
}.map {
it * 2
}.transform { value ->
if (value > 1)
emit(value)
else
emit(-1)
}.collect()
可以看到 transform 可以对输入的元素做任意的转换,而 map 只能将一个元素转换为新的元素。实际上在源码中,map 就是通过 transform 来实现的。
FlowKt_TransformKt
FlowKt_TransformKt 文件主要存放转换相关的方法,具体包含 filter、 filterNot 、filterIsInstance、 filterNotNull 、map 、mapNotNull 、onEach 方法,它们内部都是通过 transform 来实现的。除此之外,这个文件还包含 withIndex、scan、runningFold runningReduce 方法,也是对数据做转化,但它们不是用 transform 实现的 。代码示例如下:
val numbersFlow = flow {
emit(1)
emit(2)
emit(3)
emit(4)
emit(5)
}
// filter 筛选出符合条件的值,这里获取偶数
val filteredFlow = numbersFlow.filter { number ->
number % 2 == 0
}
// filterNot 筛选出不符合条件的值,这里获取奇数
val filteredFlow = numbersFlow.filterNot { number ->
number % 2 == 0
}
// filterIsInstance 筛选出指定类型的值
val mixedFlow = flow {
emit(1)
emit("two")
emit(3)
emit("four")
}
val intFlow = mixedFlow.filterIsInstance<Int>()
// filterNotNull 筛选出不为null的值
val nullableFlow = flow {
emit(null)
emit(1)
emit(null)
emit(2)
}
val nonNullFlow = nullableFlow.filterNotNull()
// map 一对一转换; mapNotNull 会过滤掉转换后为 null 的值
flow {
emit(1)
emit(2)
emit(3)
}.map {
it * 2
}
// 在每个元素被 emit 时调用
numbersFlow.onEach { number ->
println("Processing $number")
}.collect { value ->
println(value)
}
val numbersFlow = flow {
emit(1)
emit(2)
emit(3)
}
// withIndex 获取带有 index 的流
numbersFlow.withIndex().collect { (index, number) ->
println("Index: $index, Number: $number")
}
// scan 从初始值开始 执行遍历,并将结果作为下个执行的 参数
// 打印的结果为 [0, 1, 3, 6]
val result = numbersFlow.scan(0) { accumulator, number ->
accumulator + number
}.toList()
println(result)
// runningFold 和 scan 的功能的一样的,scan 就是 runningFold 的别名
val result = numbersFlow.runningFold(0) { accumulator, number ->
accumulator + number
}.toList()
// runningReduce 和 scan 的作用是一样的,区别是 runningReduce 不需要设置初始值
val result = numbersFlow.runningReduce { accumulator, number ->
accumulator + number
}.toList()
println(result)
FlowKt_ErrorsKt
FlowKt_ErrorsKt 文件里的方法主要是处理异常的,具体有 catch、 retry 、retryWhen 方法。代码示例如下:
flow {
emit(1)
throw RuntimeException("Error in flow")
}.retry(3) { // 重试3次,并控制是否执行
if (it is IOException){
true
}else {
false
}
}.catch { e -> // 重试3次都失败后,会调用到这里。需要注意 catch 必须在 retry 后面
println("Caught exception: ${e.message}")
emit(0) // 返回默认值
}.collect{
}
// retryWhen 满足一定条件就重试,其中 cause 为异常原因,attempt 为重试次数,从0开始
flow<Int> {
emit(0)
throw IOException("")
}.retryWhen { cause, attempt ->
...
}
FlowKt_limitKt
FlowKt_limitKt 文件中存放对 flow 元素数量进行处理的方法,具体包含 drop、 dropWhile、 take
、takeWhile 、transformWhile 方法。代码示例如下:
val numbersFlow = flow {
emit(1)
emit(2)
emit(3)
emit(4)
emit(5)
}
// drop 跳过指定数量的值,如果参数为负数时,则报错
val droppedFlow = numbersFlow.drop(2)
// 这个操作符有点特别,和`filter` 不同,它是找到第一个不满足条件的值,返回其和其之后的值。
// 如果首项就不满足条件,则是全部返回。下面的例如会获取到 3 4 5
val droppedFlow = numbersFlow.dropWhile { number ->
number < 3
}
// take 获取指定数量的值,如果参数设置为负数,则报错
numbersFlow.take(3)
// 找第一个不满足条件的项,但是取其之前的值。和 dropWhile 相反。
// 如果第一项就不满足,则为返回空流。下面的例如会获取到 1 2
numbersFlow.takeWhile { number ->
number < 3
}
// transformWhile 和 transform 类似。
// 区别是返回值为 false 时不再进行后续变换; 为 true 时则继续执行
numbersFlow.transformWhile { number ->
val result = number * 2
emit(result)
number < 4
}
FlowKt_mergeKt
FlowKt_mergeKt 文件主要包含合并相关的方法,具体有 flattenMerge 、flatMapMerge 、flattenConcat 、flatMapConcat 、transformLatest 、flatMapLatest、 mapLatest 方法。代码示例如下:
// flattenConcat 以顺序方式将给定的流扁平化为单个流,而不交织嵌套流。
// 下面的示例打印结果为 1 2 3
val flow1 = flow { emit(1); delay(100); emit(2) }
val flow2 = flow { emit(3); delay(200) }
flow {
emit(flow1)
emit(flow2)
}.flattenConcat()
.collect { value ->
println(value)
}
// flattenConcat 的效果等同于下面的操作
flow {
emit(1)
delay(100)
emit(2)
emit(3)
delay(200)
}
// flattenMerge 设置参数 concurrency 为 1 时,内部使用 flattenConcat 实现,做顺序收集 ;
// concurrency 设置大于 1 时,做并发收集.
// 下面的示例打印结果为 1 3 2
flow {
emit(flow1)
emit(flow2)
}.flattenMerge(2)
.collect { value ->
println(value)
}
// flatMapConcat 等同于 map + flattenConcat,先执行 map,再执行 flattenConcat
flow {
emit(flow1)
emit(flow2)
}.flatMapConcat {
it.map {
it * 2
}
}.collect { value ->
println(value)
}
// flatMapMerge 等同于 map + flattenMerge
flow {
emit(flow1)
emit(flow2)
}.flattenMerge(2) {
it.map {
it * 2
}
}.collect { value ->
println(value)
}
// merge, 把多个 flow 并发合并成一个 flow
// 打印结果为 1 a b c 2
val numberFlow = flow {
emit(1)
delay(100)
emit(2)
}
val stringFlow = flow {
emit("a")
emit("b")
emit("c")
}
listOf(numberFlow, stringFlow).merge()
.collect { value ->
println(value)
}
// transformLatest 用来转换最新的值,如果 `emit(1)` 后执行 `emit(2)`;
// 如果 1 的值没有处理完就有新的value 2,那么之前的操作会被取消。
// 比如下面的示例只会打印 2
flow {
emit(1)
emit(2)
}.transformLatest {
delay(100)
emit(it)
}.collect {
println(it)
}
// mapLatest,映射最新的值,如果 `emit(1)` 后 `emit(2)`;
// 如果 1 的值没有处理完就有新的value 2,那么之前的操作会被取消。
// 比如下面的示例只会打印 2
flow {
emit(1)
emit(2)
}.map {
delay(100)
it
}.collect {
println(it)
}
// flatMapLatest 映射最新的flow,之前的值flow会被取消
// 比如下面的示例只会打印 2
flow {
emit(1)
emit(2)
}.flatMapLatest {
flow {
delay(100)
emit(it)
}
}.collect {
println(it)
}
FlowKt_MigrationKt
FlowKt_MigrationKt 内部存放的是被废弃的api,我们不需要关注。
FlowKt_reduceKt
FlowKt_reduceKt 文件主要包含获取 flow 中结果的方法,具体包含 reduce、 fold、 single 、singleOrNull 、first、 firstOrNull、 last 、lastOrNull。代码示例如下:
// reduce 从第一个元素开始累加值,并对当前累加器值和每个元素应用操作。
// 如果流为空,则抛出NoSuchElementException。reduce 与 scan 类似,区别是
// scan 返回的是流,而 reduce 直接返回的是结果
val numbersFlow = flow {
emit(1)
emit(2)
emit(3)
}
val reducedValue = numbersFlow.reduce { accumulator, value ->
accumulator + value
}
println(reducedValue)
// fold 和 reduce 类似,区别是需要提供一个初始值
numbersFlow.fold(0) { accumulator, value ->
accumulator + value
}
// single 要求流只能发射一个元素
// 对于空流抛出NoSuchElementException,对于包含多个元素的流抛出IllegalStateException。
val result = flow {
emit(1)
}.single()
// singleOrNull 接收流发送的第一个值 ,可以为空,发出多值的话除第一个,后面均被置为null
val result = flow {
emit(1)
emit(2)
emit(3)
}.singleOrNull()
// first 返回流发出的第一个元素;如果流为空,则抛出NoSuchElementException。
val result = flow {
emit(1)
emit(2)
emit(3)
}.first()
// firstOrNull 返回流发出的第一个元素,如果流为空,则返回null。
val result = flow {
emit(1)
emit(2)
emit(3)
}.firstOrNull()
// last 返回流发出的最后一个元素。如果流为空,则抛出NoSuchElementException。
val result = flow {
emit(1)
emit(2)
emit(3)
}.last()
// lastOrNull 返回流发出的最后一个元素,如果流为空,则返回null
val result = flow {
emit(1)
emit(2)
emit(3)
}.lastOrNull()
FlowKt_ShareKt
FlowKt_ShareKt 存放与共享流相关的方法,具体有 shareIn 、stateIn、 asSharedFlow 、asStateFlow、 onSubscription。代码示例如下:
// shareIn、stateIn 都会将一个冷流(Cold Flow)转换为热流
// 其中 stateIn 是转化为状态流;shareIn 是转化为共享流
val numbersFlow = flow {
emit(1)
emit(2)
emit(3)
}.stateIn(scope)
// asSharedFlow、asStateFlow 将现有的 SharedFlow 或者 StateFlow
// 转换为只读形式,增强封装性。
val sharedFlow = MutableStateFlow<Int>(0)
sharedFlow.asSharedFlow()
sharedFlow.asStateFlow()
// onSubscription SharedFlow、StateFlow 建立订阅之后的回调
sharedFlow.onSubscription {
}
FlowKt_zipKt
FlowKt_zipKt 文件存放 flow 元素组合的方法,具体有:combine、 combineTransform、 zip。代码示例如下:
// combine 将多个流的最新值结合起来,每当任何流发射新值时都会触发。
// 下面的代码示例打印结果为
// 1 - A
// 2 - A
// 2 - B
val flow1 = flow {
emit(1)
delay(100)
emit(2)
}
val flow2 = flow {
emit("A")
delay(200)
emit("B")
}
val combinedFlow = flow1.combine(flow2) { num, str ->
"$num - $str"
}
combinedFlow.collect { combinedValue ->
println(combinedValue)
}
// combineTransform 与 `combine` 类似,但提供了更灵活的方式来处理合并的流。
// 可以在合并过程中发射多个值。下面的代码示例打印结果为
// 1 - A
// true
// 2 - A
// true
// 2 - B
// true
val combinedFlow = flow1.combineTransform(flow2) { num, str ->
emit("$num - $str")
emit(true)
}
combinedFlow.collect { combinedValue ->
println(combinedValue)
}
// zip 将多个流的值一一对应地合并。只有在所有输入流都发射了新值时才会触发合并.
// 下面的代码示例打印结果为
// 1 - A
// 2 - B
val combinedFlow = flow1.zip(flow2) { num, str ->
"$num - $str"
}
combinedFlow.collect { combinedValue ->
println(combinedValue)
}