Flow 的操作符

reduce

终止操作符,用于对 flow 中的元素进行连续累加操作,其最终返回的是对所有元素操作后的值。参数含义如下

kotlin 复制代码
// accumulator 第一次时为 flow 的第一个元素
// 其余时间为 lambda 表达式的返回值
// value 第一次时为 flow 的第二个元素,
// 其余时间为 flow 中的其它元素
reduce { accumulator, value ->
      accumulator + value
}

fold

与 reduce 类似,只不过 accumulator 第一次的值是 fold 中指定的参数

buffer

缓存上游数据。 因为 flow 是串行的,无论 emit 还是 collect 中发生挂起,都会延迟整个链路的执行。通过 buffer 可以在上游执行过快时,缓存上游数据,不阻塞上游的执行,然后依次传递给下游

conflate

只缓存上游最新数据,会丢弃中间值。buffer 会缓存上游所有数据,但 conflate 只缓存最新的,新数据到来时会删除旧的数据

collectLatest

collect 变种。当上游有新数据到来时,会取消当前 collect 的执行,转而执行最新的数据。conflate 会缓存最新数据直到 collect 执行完,collectLatest 会直接取消 collect 执行,这是两者最大的区别

combine

合并 flow:当某个 flow 下发新数据时,会结合其余 flow 的最新数据一起下发至下游。当 flowA 有新数据时,会和 flowB 中的最新数据组合发给下游;如果 flowA 又有新数据,还是会有 flowB 的最新数据组合发给下游。如下

kotlin 复制代码
val f = flow {  
    emit(1)  
    delay(10)  
    emit(2)  
}  
val b = flow {  
    emit("A")  
    delay(1000)  
    emit("B")  
}  
val c = f.combine(b) { a, b ->  
    Log.e("TAG", "test3: $a  $b")  
    "$a  $b"  
}
// output
// 1 A
// 2 A  
// 2 B   B 发送时 f 的最新数据是 2,所以会和 2 组合一起发至下游

在有多个判断条件的场景时可使用 combine 简化操作:每一个条件发生变化时都往下游发送数据,下游根据这些条件的最新数据执行逻辑操作

zip

与 combine 类似,但只有所有 flow 中都有新数据时才会往下游发送,combine 是只有要新数据(任意一个流中有就行)就会发送数据。如下:

kotlin 复制代码
val flow = flow {
    emit(1)
    delay(1000)
    emit(2)
}
val flow2 = (10..20).asFlow()
lifecycleScope.launch {
    flow.zip(flow2) { f, s ->
        "f = $f, s = $s"
    }.collect {
        // 只有 1,10;2,11 两组数据
        // 因为 flow 只 emit 了两个数据
        // 只有两个 flow 都有变化时才会更新,所以下游只能收到两个数据
        e("it = $it")
    }
}

merge

将多个 flow 合成一个,要求这些 flow 必须 emit 相同类型的数据,一旦这些 flow 中任何一个有新数据到来,都会往下游发送

combine 会向下游发送一个数据组(数据的个数与 flow 个数相同),但 merge 只会一次往下游发送一条数据

kotlin 复制代码
val flow = flow {
    emit(1)
    delay(1000)
    emit(2)
}
val flow2 = (10..12).asFlow()
val flow3 = (30..33).asFlow()
lifecycleScope.launch {
    merge(flow, flow2, flow3.onEach {
        delay(10)
    }).collect {
        // 1,10-12,30-33,2
        // 可以发现只要有任何一个 flow emit 了数据,下游就会收到
        e("it = $it")
    }
}

flat 系列

flat 系列一共有三个操作符:flatMapConcatflatMapMergeflatMapLatest

三个操作符中 flat 指将一个二维数据拍平成一维数据,即将 List<Flow<Int>> 转换成 List<Int>,map 指对数据转换,第三个指数据合并策略

  1. flatMapConcat:按顺序将 map 出的多个 flow 合成一个 flow,第一个 flow 中的元素一定会最先传递给下游
kotlin 复制代码
// flow 会按顺序 emit 1,2
flow.flatMapConcat {  
    flow {  
        emit(it * 2)  
        delay(2)
        emit(it * 2 + 1)  
    }  
}.collect {  
    // 最终会依次输出 2,3,4,5
    Log.e("TAG", "test2: $it")  
}
  1. flatMapMerge:无序地将 map 出的多个 flow 合成一个 flow。这些 map 出的 flow 哪一个先 emit 了,处理时就直接将它 emit 的值 emit 给下游。可以指定参数 concurrency:表示并发处理多少个 flow。如果将值指定成 1,其效果与 flatMapConcat 一样
kotlin 复制代码
// 同样逻辑,将 flatMapConcat 换成 flatMapMerge
flow.flatMapMerge {  
    flow {  
        emit(it * 2)  
        delay(2)  
        emit(it * 2 + 1)  
    }  
}.collect {  
    // 输出的 list 将是无序的,有可能是 2,4,3,5
    Log.e("TAG", "test2: $it")  
}
  1. flatMapLatest:后 map 出的 flow 会取消前 map 出的 flow。比如上游 emit 了两个元素,会 map 出两个 flow。当第二个 flow 到来时,就会取消第一个 flow,哪怕它并没有处理完
kotlin 复制代码
flow {  
    emit(1)  
    delay(1)  
    emit(2)  
}.flatMapLatest {  
    flow {  
        emit(it * 2)  
        delay(2)  
        emit(it * 2 + 1)  
    }  
}.collect {  
    // 依次输出 2,4,5
    // 2 对应的 flow 到来时,1 的 flow 还没有执行完,但会被取消
    // 所以 3 就没法 emit 到最终的 collect 
    Log.e("TAG", "test2: $it")  
}

while 系列

flow 的有一些操作符会有一个加 while 的同构操作符,如 transform 有 transformWhile,take 有 takeWhile 等

while 的作用是:只有满足条件的数据才会发往下游,不满足时会取消整个 flow,前半部分作用与 filter 类似,后半部分是 filter 不具备的功能。

kotlin 复制代码
flow {
    repeat(6) {
        e("it = $it")
        emit(it)
    }
}.transformWhile {
    emit("$it - $it")
    // 当 it = 4 时,不满足条件,会取消上游的 flow
    // 因此上游的 repeat 5 并不会输出
    it < 3
}.collect {
    e("collect = $it")
}

// output
it = 0
collect = 0 - 0
it = 1
collect = 1 - 1
it = 2
collect = 2 - 2
it = 3
collect = 3 - 3

flow 取消原理

在 flow 的操作符中经常有一些满足某个条件后,取消上游不再下发操作的操作。其实现原理主要是在满足条件后抛出一个指定异常,从而退出 flow 中的后续生产流程。比如上面的 while 系列,take 方法

kotlin 复制代码
// take 方法源码

public fun <T> Flow<T>.take(count: Int): Flow<T> {
    return flow {
        // 下游 collect 会执行到该 lambda 表达式
    
        var consumed = 0
        try {
            collect { value ->
                // 统计次数
                if (++consumed < count) {
                    // 未满足次数,直接下发至下游
                    return@collect emit(value)
                } else {
                    // 最后一条数据,emit() 后会抛出异常
                    return@collect emitAbort(value)
                }
            }
        } catch (e: AbortFlowException) {
            // 这里并不是直接 catch 住 emitAbort 中的抛出的异常
            // collect 中的 Lambda 表达式是 suspend 函数
            // 其抛出异常后会在恢复时再次抛出,即 emit 方法中会再次抛出
            // 然后由 catch 抓住 emit() 再次抛出的异常
            e.checkOwnership(owner = this)
        }
    }
}

private suspend fun <T> FlowCollector<T>.emitAbort(value: T) {
    emit(value)
    throw AbortFlowException(this)
}
相关推荐
过期动态25 分钟前
JDBC高级篇:优化、封装与事务全流程指南
android·java·开发语言·数据库·python·mysql
没有了遇见3 小时前
Android 音乐播放器之MotionLayout实现View流畅变换
android
TheNextByte13 小时前
在 PC 和Android之间同步音乐的 4 种方法
android
君莫啸ོ4 小时前
Android基础-Activity属性 android:configChanges
android
TimeFine4 小时前
Android AI解放生产力(七):更丰富的AI运用前瞻
android
保持低旋律节奏4 小时前
linux——进程状态
android·linux·php
明川4 小时前
Android Gradle - ASM + AsmClassVisitorFactory插桩使用
android·前端·gradle
csdn12259873365 小时前
Android将应用添加到默认打开方式
android
百锦再5 小时前
京东云鼎入驻方案解读——通往协同的“高架桥”与“快速路”
android·java·python·rust·django·restful·京东云
成都大菠萝5 小时前
1-2-3 Kotlin与C++基础-JNI原理与使用
android