十分钟速览 Kotlin Flow 操作符

实时的用户输入、多个网络请求的响应,再加上数据库的频繁更新,很容易让你的代码变得混乱不堪。而这,正是 Kotlin Flow 要帮你解决的问题。

作为基于协程构建的响应式流 API,Kotlin Flow 让你可以用声明式的方式优雅地处理异步数据流。但要想真正发挥它的强大能力,关键在于熟练掌握各种操作符。

本指南就是为你量身打造的速查手册------聚焦最核心的操作符,清晰说明它们的适用场景和使用方法,助你高效驾驭 Kotlin Flow


一对一转换

map

Flow 中发出的每个元素转换为新元素,实现一对一映射。

Kotlin 复制代码
fun main() = runBlocking {
    flowOf("Kotlin", "Flow")
        .map { "Length of '$it' is ${it.length}" }
        .collect { println(it) }
}

// output:
// Length of 'Kotlin' is 6
// Length of 'Flow' is 4

适用场景:需要将每个项转换为不同类型或格式,例如在 ViewModel 中将 Date 对象格式化为可显示的 String

filter

仅允许满足给定条件的元素通过。

Kotlin 复制代码
fun main() = runBlocking {
    (1..5).asFlow()
        .filter { it % 2 == 0 }
        .collect { println(it) }
}

// output:
// 2
// 4

适用场景:根据条件丢弃不需要的值,例如过滤空搜索词或无效数据。

take

限制性操作符,仅发出 Flow 的前 n 个元素,随后取消 Flow 执行。

Kotlin 复制代码
fun main() = runBlocking {
    (1..10).asFlow()
        .take(3)
        .collect { println(it) }
}

// output:
// 1
// 2
// 3

适用场景:只需要有限数量的项,例如分页加载,或从一系列操作中取第一个有效结果。


累积值

reduce

终端操作符,从 Flow 的第一个元素开始累积值,并对当前累加器与每个元素执行操作。若 Flow 为空则抛出异常。

Kotlin 复制代码
fun main() = runBlocking {
    val sum = (1..3).asFlow().reduce { accumulator, value -> accumulator + value }
    println(sum)
}

// output:
// 6

fold

类似于 reduce,但 fold 需要一个初始值,是更安全的选择。

即使 Flow 为空也会返回初始值。

Kotlin 复制代码
fun main() = runBlocking {
    val sum = (1..3).asFlow().fold(100) { accumulator, value -> accumulator + value }
    println(sum)
}

// output:
// 106

runningReduce / scan

中间操作符,在每个元素处理时都发出当前累积值。

中间操作符不会触发 Flow 的收集,所以需要 collect 触发 Flow 的收集,而 reduce 这种终端操作符会触发 Flow 的收集。

scan 是更通用的版本,支持指定初始种子值。

Kotlin 复制代码
fun main() = runBlocking {
    println("runningReduce:")
    (1..3).asFlow()
        .runningReduce { accumulator, value -> accumulator + value }
        .collect { println(it) } 

    println("scan:")
    (1..3).asFlow()
        .scan(0) { accumulator, value -> accumulator + value }
        .collect { println(it) }
}

// output:
// runningReduce:
// 1
// 3
// 6
// scan:
// 0
// 1
// 3
// 6

注意:他们会返回每一步的结果,而 reduce/fold 只返回最终结果。

适用场景:计算累计总和、跟踪进度,或在状态机中展示状态历史。


发出多个值

transform

高度灵活的操作符,可为每个输入元素发出零个、一个或多个值,对输出流有更强控制力。

Kotlin 复制代码
fun main() = runBlocking {
    (1..2).asFlow()
        .transform {
            emit("Item: $it")
            if (it % 2 != 0) {
                emit("...is an odd number")
            }
            emit("Square: ${it * it}")
        }.collect { println(it) }
}

// output:
// Item: 1
// ...is an odd number
// Square: 1
// Item: 2
// Square: 4

适用场景:执行复杂转换、引入副作用(如日志记录),或根据单个输入有条件地发出多个值。


扁平化嵌套

flatMapConcat

将每个元素转换为一个 Flow,然后依次连接这些 Flow,只有当前一个 Flow 完成后,下一个才开始。

Kotlin 复制代码
fun getNumbersFlow(id: Int): Flow<String> = flow {
    delay(100)
    emit("First-$id")
    delay(100)
    emit("Second-$id")
}

fun main() = runBlocking {
    (1..2).asFlow().flatMapConcat { id -> getNumbersFlow(id) }.collect { println(it) }
}

// output:
// First-1
// Second-1
// First-2
// Second-2

仔细看,第一个流的每个数字都会参与到第二个流中。

适用场景:顺序敏感的操作,例如依次上传多个文件,或执行依赖型网络请求。

flatMapMerge

并发合并由转换函数生成的多个 Flow,可通过 concurrency 参数控制并发数量。

Kotlin 复制代码
fun getNumbersFlow(id: Int): Flow<String> = flow {
    delay(100)
    emit("First-$id")
    delay(100)
    emit("Second-$id")
}

suspend fun main() {
    (1..2).asFlow()
        .flatMapMerge { id -> getNumbersFlow(id) }
        .collect { println(it) }
}

// output:
// First-1
// First-2
// Second-2
// Second-1

从结果上注意区分 flatMapConcatflatMapMerge 不会保证合并的顺序。

适用场景:顺序无关的并行操作,例如同时从多个数据源获取数据。

flatMapLatest

当新元素发出时,立即取消上一个元素对应的 Flow

Kotlin 复制代码
val searchQuery = flowOf("search", "search with new term").onEach { delay(200) }

fun searchApi(query: String): Flow<String> = flow {
    emit("Searching for '$query'...")
    delay(500) // 模拟网络延迟
    emit("Results for '$query'")
}

suspend fun main() {
    searchQuery
        .flatMapLatest { query -> searchApi(query) }
        .collect { println(it) }
}

// output:
// Searching for 'search'...
// Searching for 'search with new term'...
// Results for 'search with new term'

适用场景:实时搜索功能,或任何只需关注最新事件结果的场景。


上下文与缓冲

flowOn

更改用于执行上游 FlowCoroutineContext,是 Flow 中切换调度器的正确方式。

Kotlin 复制代码
fun heavyWork(): Flow<Int> = flow {
    println("Starting heavy work on ${Thread.currentThread().name}")
    for (i in 1..3) {
        // Simulate CPU-intensive work
        Thread.sleep(100)
        emit(i)
    }
}

fun main() = runBlocking {
    heavyWork()
        .flowOn(Dispatchers.IO) // Upstream runs on IO dispatcher
        .collect {
            println("Collected $it on ${Thread.currentThread().name}")
        }
    // Downstream runs on the collector's context (e.g., Main)
}

// output:
// Starting heavy work on DefaultDispatcher-worker-1
// Collected 1 on main
// Collected 2 on main
// Collected 3 on main

buffer

通过解耦生产者与消费者实现并发执行:生产者将项放入缓冲区,消费者从中取出。

Kotlin 复制代码
suspend fun main() {
    val time = measureTimeMillis {
        flow {
            for (i in 1..3) {
                delay(200) // Simulate slow emission
                emit(i)
            }
        }
        .buffer() // With buffer, the total time is closer to the slow collector's time
        .collect {
            delay(300) // Simulate slow collection
            println(it)
        }
    }
    println("Collected in $time ms")
}

// output:
// 1
// 2
// 3
// Collected in 1172 ms

得益于 buffer,最后整个数据的收集时间要小于 (200 + 300) * 3

使用场景:当生产者与消费者处理速度不一致时,提升性能。

conflate

一种缓冲形式,当收集器处理太慢时会丢弃中间值,确保始终获取最新值。

Kotlin 复制代码
suspend fun main() {
    flow {
        for (i in 1..5) {
            delay(100)
            emit(i)
        }
    }
    .conflate()
    .collect { value ->
        println("Started processing $value")
        delay(300)
        println("Finished processing $value")
    }
}

// output:
// Started processing 1
// Finished processing 1
// Started processing 3
// Finished processing 3
// Started processing 5
// Finished processing 5

适用场景:UI 更新中无需显示中间状态,如股票行情或 GPS 位置更新。

collectLatest

终端操作符,当新值发出时,取消对前一个值的收集逻辑。

Kotlin 复制代码
suspend fun main() {
    (1..3).asFlow()
        .onEach { delay(100) }
        .collectLatest { value ->
            println("Collecting $value")
            delay(300)
            println("Finished collecting $value")
        }
}

// output:
// Collecting 1
// Collecting 2
// Collecting 3
// Finished collecting 3

注意看这里的结果,Finished collecting 只收集了最后一次的值,一定要注意这个特性。

适用场景:某项操作耗时较长,且应在新项到达时被取消,例如将用户输入保存到数据库。


合并

zip

等待两个 Flow 各自发出一项后进行组合。任一源 Flow 结束,结果 Flow 即结束。

Kotlin 复制代码
suspend fun main() {
    val flowA = (1..3).asFlow()
    val flowB = flowOf("A", "B", "C", "D")

    flowA.zip(flowB) { number, letter -> "$number$letter" }
        .collect { println(it) } 
}

// output:
// 1A
// 2B
// 3C

combine

组合两个 Flow 的最新值。只要任一源 Flow 发出新值(且双方至少各发出过一次),就会触发一次发射。

Kotlin 复制代码
suspend fun main() {
    val flowA = (1..3).asFlow().onEach { delay(100) }
    val flowB = flowOf("A", "B").onEach { delay(150) }

    flowA.combine(flowB) { number, letter -> "$number$letter" }
        .collect { println(it) }
}

// output:
// 1A
// 2A
// 3A
// 3B

适用场景:响应多个数据源的变化。

merge

将多个 Flow 合并为一个,按发出顺序交错输出所有值。

Kotlin 复制代码
suspend fun main() {
    val flowA = flowOf("A1", "A2").onEach { delay(100) }
    val flowB = flowOf("B1", "B2").onEach { delay(50) }

    merge(flowA, flowB)
        .collect { println(it) }
}

// output:
// B1
// A1
// B2
// A2

适用场景:将来自不同 UI 组件的多个事件流合并为单一处理流。


错误与完成处理

catch

捕获上游 Flow(即 catch 之前的操作符)中发生的异常,但不捕获下游收集器中的异常。

Kotlin 复制代码
suspend fun main() {
    flow {
        emit(1)
        throw RuntimeException("Error!")
    }
    .catch { e ->
        println("Caught: ${e.message}")
        emit(-1) // Emit a fallback value
    }
    .collect { println(it) } // Emits 1, then -1
}

// output:
// 1
// Caught: Error!
// -1

适用场景:优雅地处理错误、提供默认值或记录失败信息。

onCompletion

Flow 完成时(无论成功或异常)执行指定操作。成功时 causenull

Kotlin 复制代码
suspend fun main() {
    (1..3).asFlow()
        .onCompletion { cause ->
            if (cause != null) println("Flow completed with error")
            else println("Flow completed successfully")
        }
        .collect { println(it) }
}

// output:
// 1
// 2
// 3
// Flow completed successfully

retryWhen

在发生异常时根据谓词(包含异常原因和重试次数)决定是否重试。

scss 复制代码
suspend fun main() {
    var attemptCount = 0
    flow {
        emit(1)
        if (attemptCount < 2) {
            attemptCount++
            throw RuntimeException("Transient error")
        }
        emit(2)
    }
    .retryWhen { cause, attempt ->
        println("Attempt $attempt: Retrying due to ${cause.message}")
        delay(100) // Add a delay before retrying
        attempt < 2 // Retry up to 2 times
    }
    .catch { println("Caught final error: ${it.message}") }
    .collect { println(it) }
}

// output:
// 1
// Attempt 0: Retrying due to Transient error
// 1
// Attempt 1: Retrying due to Transient error
// 1
// 2

retryWhen 的回调有两个参数:

  • cause:导致 Flow 失败的异常(Throwable 类型)。
  • attempt:当前是第几次重试,从 0 开始计数。

工具与副作用

onEach

Flow 中每个元素执行指定操作,但不修改元素本身。

Kotlin 复制代码
suspend fun main() {
    (1..3).asFlow()
        .onEach { println("About to process $it") }
        .map { it * it }
        .collect { println("Processed value: $it") }
}

// output:
// About to process 1
// Processed value: 1
// About to process 2
// Processed value: 4
// About to process 3
// Processed value: 9

适用场景:用于日志、调试或埋点等副作用场景,在不改变数据的前提下观察 Flow

debounce

过滤在指定超时内被新值取代的值,仅发出"突发"中的最后一个值。

Kotlin 复制代码
suspend fun main() {
    flow {
        emit(1)
        delay(90)
        emit(2)
        delay(90)
        emit(3)
        delay(500)
        emit(4)
        delay(90)
        emit(5)
    }.debounce(100).collect { println(it) } 
}
// output:
// 3
// 5

适用场景:处理快速用户输入(如搜索框),避免每次按键都触发 API 请求。

distinctUntilChanged

抑制与前一个值相同的重复发射。

Kotlin 复制代码
suspend fun main() {
    flowOf(1, 1, 2, 2, 1, 3)
        .distinctUntilChanged()
        .collect { println(it) } 
}

// output:
// 1
// 2
// 1
// 3

适用场景:防止 UI 因状态未变而进行不必要的重组或更新。


核心决策指南

  • 数据转换。用于修改流中的值:mapfilterdistinctUntilChanged
  • 累积值。用于跟踪状态或计算持续结果:scanrunningReducefoldreduce
  • 从单一输入发出多个值。用于自定义发射逻辑:transform
  • 处理嵌套或动态 Flow。根据内部 Flow 行为选择:flatMapConcat(顺序执行)、flatMapMerge(并发执行)、flatMapLatest(取消旧任务,保留最新)。
  • 性能与背压控制。用于优化收集效率与响应性:bufferconflatecollectLatestflowOn
  • 合并多个流。用于组合多个数据源:zip(按顺序配对)、combine(组合最新值)、merge(交错合并)。
  • 错误处理与完成逻辑。用于应对异常和生命周期事件:catchonCompletionretryWhen
  • 调试与副作用。用于插入日志或副作用操作:onEach
  • 处理高频发射。用于抑制过快的发射频率:debounce
  • 触发 Flow 执行。终端操作符:collectcollectLatestfirstsingletoListreducefold
相关推荐
モンキー・D・小菜鸡儿16 小时前
Android 系统TTS(文字转语音)解析
android·tts
2501_9159090616 小时前
iOS 反编译防护工具全景解析 从底层符号到资源层的多维安全体系
android·安全·ios·小程序·uni-app·iphone·webview
Swizard16 小时前
速度与激情:Android Python + CameraX 零拷贝实时推理指南
android·python·ai·移动开发
summerkissyou198717 小时前
Android13-Audio-AudioTrack-播放流程
android·音视频
里纽斯17 小时前
RK平台Watchdog硬件看门狗验证
android·linux·rk3588·watchdog·看门狗·rk平台·wtd
三七吃山漆18 小时前
攻防世界——comment
android·python·web安全·网络安全·ctf
用户4130798106118 小时前
终于懂了-ARouter原理初探
android
fatiaozhang952718 小时前
晶晨S905L3B芯片-2+8G-安卓9.0-ATV原生设置(深度精简优化)-通刷-线刷固件包
android·电视盒子·刷机固件·机顶盒刷机·晶晨s905l3b通刷包·e900v22c-s905l3
Railshiqian18 小时前
安卓如何查看settings是被哪个进程更新的
android
键来大师19 小时前
Android15 安装APK时监听且替换安装
android·framework·rk3588·android15