2. Flow 基础知识:创建 Flow 的三种姿势

说起 Flow,我刚开始学协程时最头疼的就是怎么把它“造”出来。其实说白了,Kotlin 官方给了我们三把钥匙:flow { }、flowOf()、.asFlow()。我个人习惯把它们看作手工作坊、预制菜和自动转化机——各有各的适用场景。

Flow 创建方式对比图 flow { } 构建器 flowOf() 工厂 asFlow() 扩展 核心特点 • 使用 emit() 发射数据 • 支持异步与 suspend 操作 • 可自定义发射逻辑 核心特点 • 直接传多个元素 • 内部自动调用 emit • 适用于固定/少量数据 核心特点 • 将集合/序列转为 Flow • 保留原有数据顺序 • 类型安全 使用原则:先定需求,再选方式 需要自定义发射 → flow { };固定值列表 → flowOf();已有集合 → asFlow()

2.1 flow { } —— 最灵活的手工搭建方式

flow { } 是最基础的构建器。你在花括号内部写逻辑,用 emit() 把数据一个个发出去。我当初写第一个 Flow 时就踩了一个坑:忘了 suspend 函数不能在协程外调用——emit 本身是挂起函数,必须在协程作用域里使用。

基本用法:

val myFlow: Flow<Int> = flow {
    for (i in 1..3) {
        delay(100)  // 模拟耗时操作
        emit(i)     // 发射数据
    }
}

你想想看,这个 flow { } 背后 = 一个冷数据流。什么叫冷?就是没人 collect 它就不干活。这叫“惰性发射”,跟 RxJava 的 Observable 有点像,但更轻量。

💡 个人经验: 如果要发射的数据需要从网络或本地数据库获取,我强烈推荐用 flow { }。因为你可以写 emit() 的同时加各种错误处理和取消逻辑。我曾经用 flow 配合 retry 操作符实现了网络请求自动重试,代码比传统回调优雅十倍。

2.2 flowOf() —— 最简洁的固定值工厂

当你手头就是一堆现成数据,不想写 emit 循环?直接用 flowOf()。它接受可变参数,内部帮你逐个 emit。

val numbers: Flow<Int> = flowOf(1, 2, 3, 4, 5)

说白了,这就是个语法糖。我一般在测试或 mock 数据时最喜欢用它——写单元测试时,一行代码就能构建一个包含预定值的 Flow,比 mock 框架还顺手。

⚠️ 避坑指南: 有一次我在生产代码里用 flowOf(listOf(...)),结果每次都只发射一个 List 对象,而不是列表里的每个元素。后来才意识到:flowOf() 是把每个参数作为一个独立元素发射,如果传一个集合进去,那发射的就是整个集合本身。要小心!

2.3 asFlow() —— 从现有集合/序列转化

你手头已经有 List、Set 或 Sequence 了,不想重复造轮子?调一下 .asFlow() 即可。这是 Kotlin 协程库提供的扩展函数,把集合里每个元素依次发射出去。

源类型 转换代码 结果 Flow 元素类型
List<String> list.asFlow() String
Set<Int> set.asFlow() Int
Sequence<Double> seq.asFlow() Double

示例:

val names = listOf("Alice", "Bob", "Charlie")
val nameFlow: Flow<String> = names.asFlow()

// 之后用 nameFlow.collect { println(it) } 即可

嗯,这里要注意:asFlow() 默认是逐个同步发射的。如果集合里有大量数据,它不会像 flow { } 那样给你加 delay 的机会——发射事件本身就是瞬时完成的。所以如果你需要控制发射间隔,还是老老实实用 flow { } 吧。

2.4 三种方式怎么选?一张表看明白

对比维度 flow { } flowOf() asFlow()
使用场景 自定义发射逻辑、异步数据源 固定列表、测试、简单数据 已有集合/序列,直接转化
灵活性 ★★★★★ ★★☆☆☆ ★★★☆☆
代码简洁性 ★★☆☆☆ ★★★★★ ★★★★☆
支持挂起函数 是(可任意调用) 否(仅发射) 否(同步转换)

2.5 我记的几个小经验

  • 调试时多用 flowOf —— 我习惯在测试里用 flowOf(1, 2, 3) 快速验证操作符行为,比写 flow { } 少两行大括号。
  • asFlow 和 flowOn 搭配有坑 —— 如果你用 list.asFlow() 然后在后面调了 flowOn,记住:asFlow 转换本身是在调用线程执行的,flowOn 只影响下游操作符的上下文。想控制发射线程?还是用 flow { } 吧。
  • 小心空指针 —— flow Of 不会自动做空安全检查,但 Kotlin 类型系统会帮你。如果传了一个 null 进去,Flow 元素类型就会变成 Type?,collect 时要注意判空。

总结一下: 创建 Flow 就这三种姿势。没有谁好,选合适的就行。我个人最喜欢 flow { },因为它给我最大的控制感;但要是赶工期写 demo,我肯定选 flowOf 或 asFlow——少写代码永远是对自己好。