深入理解 Flow 的终端操作符

我们来系统学习一下 Flow 的终端操作符。

Flow 是冷流,只有在调用了终端操作符之后,才会启动流的收集流程(collect),上游的代码才会开始执行。

获取单个元素

这类操作符用于从流中获取特定的值。

注意:它们都是挂起函数。

first() 和 firstOrNull()

first():它会等待并返回 Flow 发射的第一个 元素,然后立即取消流的收集。

kotlin 复制代码
fun main(): Unit = runBlocking {
    // 获取第一个值
    val flow = flowOf(1, 2, 3)
    println("First value: ${flow.first()}") // 输出: 1

    // 异常:流为空
    val emptyFlow = flowOf<Int>()
    try {
        emptyFlow.first()
    } catch (e: NoSuchElementException) {
        println("Error: ${e.message}") // 输出: Expected at least one element
    }
}

它的变体是 first{ predicate },会返回第一个满足条件的元素。

kotlin 复制代码
val flow = flowOf(1, 2, 3)
// 查找第一个大于等于 2 的元素
val value = flow.first { it >= 2 }

firstOrNull() 的行为和 first() 类似,区别在于当 Flow 为空时,它不会抛出异常,而是会返回 null

使用场景

对于不需要持续监听变化的 Flow,我们会经常使用此操作符。

例如:读取 DataStore 的当前快照、将一次性的网络请求封装为 Flow。

读取完毕后,流就会自动关闭,不会造成资源泄漏。

last() 和 lastOrNull()

这两个操作符必须等待流完全结束后,才会返回流的最后一个元素。

千万注意 :如果在无限流 上调用 last(),会导致协程永远挂起,进而引出内存泄漏或界面无响应。

无限流场景:

  • SharedFlow
  • 通过 interval 创建的时间流
  • Room 数据库的查询 Flow,只要数据表变动,它就会发射新数据,所以它永远不会主动结束。因此,不要在 Dao 返回的 Flow 上调用 last()

single() 与 singleOrNull()

这类操作符不仅仅是获取值,还有检验的性质。

single():期待流仅仅发射一个元素。

  • 如果流只发射了一个元素,会返回该元素。
  • 如果流为空,会抛出 NoSuchElementException
  • 如果流的发射了多于一个元素,会抛出 IllegalStateException

singleOrNull():同上,区别在于当流为空或是元素多于一个时,会返回 null

注意:使用 singleOrNull(),你将无法区分"没有数据"或是"数据超出一个"的情况。如果要区分数据类型,应该使用 single()

集合转换与统计

这类操作符会收集流中的所有数据,需要等待流的结束。所以它也同样存在无限挂起的风险。

count()

作用:获取流中元素的总个数。

变体:count { predicate } 会统计满足条件的元素个数。

toList(), toSet(), toCollection()

  • toList():将所有元素收集到 List 中。
  • toSet():将所有元素收集到 Set 中。
  • toCollection(destination):将所有元素添加到指定的集合实例中。

注意:Flow 的优势是流式处理,使用这些操作符会将所有数据一次性加载到内存中。如果数据量很大,极易导致 OOM。

流与通道的桥梁

produceIn(scope) 并不是一个挂起函数,而是一个普通函数。

它可以将一个 Flow(冷流)转为 ReceiveChannel(热通道)。

原理:其实在内部它只是利用传入的 CoroutineScope 启动一个新的协程来收集这个 Flow,并将数据发送到 Channel 中。

在 MVVM 架构中,将冷流转为热流暴露给 UI,我们通常会使用 stateInshareIn。但在需要利用 Channel 特有的并发能力时,produceIn 还是首选的工具。

相关推荐
踏雪羽翼6 小时前
android TextView实现文字字符不同方向显示
android·自定义view·textview方向·文字方向·textview文字显示方向·文字旋转·textview文字旋转
lxysbly6 小时前
安卓玩MRP冒泡游戏:模拟器下载与使用方法
android·游戏
夏沫琅琊8 小时前
Android 各类日志全面解析(含特点、分析方法、实战案例)
android
程序员JerrySUN9 小时前
OP-TEE + YOLOv8:从“加密权重”到“内存中解密并推理”的完整实战记录
android·java·开发语言·redis·yolo·架构
TeleostNaCl10 小时前
Android | 启用 TextView 跑马灯效果的方法
android·经验分享·android runtime
TheNextByte111 小时前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn12 小时前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪13 小时前
聊聊2026年Android开发会是什么样
android
编程大师哥13 小时前
Android分层
android
极客小云14 小时前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试