一句话总结
Flow 是协程的异步数据流水线。想象 Flow 是一条“传送带”,数据像包裹一样一个一个从起点(生产者)传送到终点(消费者),中间可以经过多个加工站(操作符),整个过程不阻塞主线程。
一、核心概念:冷流与热流的本质
理解 Flow 的本质,需要区分两种不同的数据流类型。
-
冷流(Cold Flow) :
- 本质: “拉模式(Pull-based)” 。数据只有在有人订阅时才会开始生产。每次新的订阅都会触发生产者从头开始发射数据。
- 比喻:像“点播视频”。每个观众都可以独立播放,互不影响。
- 实现:通过
flow { ... }构建。
-
热流(Hot Flow) :
- 本质: “推模式(Push-based)” 。数据生产者与订阅者是独立的,即使没有订阅者,数据也在持续发射。所有订阅者共享同一个数据流。
- 比喻:像“直播”。所有观众都看到同一时间点的内容,错过的内容无法回看(除非配置了缓存)。
- 实现:主要通过
SharedFlow和StateFlow实现。
二、Flow 的工作机制:生产、加工与消费
一个完整的 Flow 流程由三个核心部分组成。
- 生产者(Producer) :使用
emit()函数发射数据。 - 操作符(Operator) :在数据流经操作符时,可以对数据进行转换、过滤、映射等操作。
- 消费者(Consumer) :通过
collect()函数接收数据。
核心特性:
- 挂起友好:Flow 的所有操作符都支持协程挂起,这意味着它可以在不阻塞底层线程的情况下处理耗时操作。
- 按需触发:冷流的生产是懒加载的。只有当
collect()被调用时,整个数据流才会开始运作。
三、背压处理:应对生产与消费的速度不匹配
背压(Backpressure) 是指生产者发射数据的速度,远快于消费者处理数据的速度,导致数据积压。Flow 默认使用**“挂起-发送”**的策略来处理背压,即当消费者未准备好时,生产者会挂起。但对于不同的场景,有更高效的策略。
| 策略 | 操作符 | 行为 | 适用场景 |
|---|---|---|---|
| 缓冲(Buffer) | buffer() | 增加一个缓冲区,允许生产者和消费者在一定程度上异步工作。 | 生产者和消费者速度都很快,但存在微小差异。 |
| 丢弃(Conflate) | conflate() | 丢弃所有中间值,只保留最新值。 | 实时数据流,如UI事件、GPS定位,只需要最新状态。 |
| 取消(Cancel) | collectLatest | 当生产者有新数据到来时,取消消费者当前正在处理的任务,并立即开始处理最新数据。 | 快速更新场景,如搜索框输入联想,只关心最新输入的结果。 |
四、性能优化:如何构建高效数据流
-
线程切换:使用
flowOn()操作符将 Flow 的上游(生产者)切换到另一个线程,这能将耗时计算从主线程移到后台,同时不影响下游的collect。flowOn(Dispatchers.IO):适用于 I/O 密集型操作。flowOn(Dispatchers.Default):适用于 CPU 密集型操作。
-
共享状态:当多个消费者需要订阅同一个数据流时,使用热流(
StateFlow或SharedFlow)能有效避免重复计算和资源浪费。 -
操作符组合:Flow 的强大之处在于其丰富的操作符。通过组合
map、filter、debounce等操作符,可以构建出满足复杂业务需求的数据处理管道。