2. Flow 核心概念速通:冷流与热流、发射与收集、背压机制简述
LiveData 是 Android 官方推荐的响应式数据容器,但它仅适合简单的 UI 状态订阅,缺乏对异步序列、背压、多路流的原生支持。Flow 作为 Kotlin 协程生态核心组件,提供了更强大的数据流模型。本章从冷流/热流、发射/收集、背压三个维度速通 Flow 核心概念,为后续迁移提供理论基础。
2.1 冷流 vs 热流
Flow 分为“冷流”与“热流”两种行为模式,理解它们的区别是使用 Flow 的第一步。
| 维度 | 冷流 (Cold Flow) | 热流 (Hot Flow) |
|---|---|---|
| 定义 | 每次收集时重新执行上游生产逻辑 | 数据流独立于收集器存在,多个收集器共享同一数据源 |
| 典型实现 | 普通 Flow,如 flow { emit(...) } | SharedFlow、StateFlow |
| 生命周期 | 随收集者开始/结束而创建/销毁 | 常驻内存,即使无收集者也可以活跃 |
| 数据重播 | 无缓存,每次收集从头开始 | 可配置重播个数(replay) |
| 适用场景 | 网络请求、数据库查询、文件读取 | 事件总线、UI 状态、传感器数据 |
关键迁移认知:LiveData 本质上是热流(带有粘性数据),迁移时通常用
StateFlow 替代 LiveData 实现状态持有,用 SharedFlow 替代点击事件/一次性事件。
2.2 发射与收集
Flow 的发射使用 emit 函数,收集使用 collect 终端操作。以下是一个典型的冷流示例:
// 构建并发射数据(冷流)
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(100) // 模拟异步工作
emit(i) // 发射一个值
}
}.flowOn(Dispatchers.IO) // 指定上游协程上下文
// 收集数据(在 ViewModel 或协程作用域中调用)
viewModelScope.launch {
simpleFlow().collect { value ->
// 在主线程处理每个值
_uiState.value = "Received: $value"
}
}
收集与取消:
collect 是挂起函数,当协程被取消时,Flow 会自动停止发射并清理资源。在 Android 中通常使用 viewModelScope.launch 或 lifecycleScope.launch 托管生命周期。
2.3 背压机制简述
当 Flow 生产者发射速度大于消费者处理速度时,就会产生背压。Flow 提供了多种策略来处理背压,保持系统稳定性。
| 操作符 | 行为 | 适用场景 |
|---|---|---|
buffer() | 将发射与收集分流到不同协程,提供缓冲区 | IO 密集型生产者 + UI 收集 |
conflate() | 丢弃旧值,只保留最新值 | UI 状态更新,无需处理中间值 |
collectLatest() | 当新值到达时取消当前收集操作 | 搜索去抖动、高频传感器事件 |
flowOn() | 改变上游协程上下文,间接控制线程切换 | 指定 I/O 线程执行发射 |
背压处理示例:
// 使用 conflate 跳过中间值,仅处理最新数据
fun fastProducer(): Flow<String> = flow {
while (true) {
emit("Tick ${System.currentTimeMillis()}")
delay(50)
}
}
viewModelScope.launch {
fastProducer()
.conflate() // 丢弃尚未处理的旧值
.collect { tick ->
// 即使生产者很快,UI 也只会按需更新
_debugText.value = tick
}
}
2.4 调试与常见错误
在迁移过程中需要留意以下适配点:
- 流未启动:冷流必须调用
collect才会产生数据,忘记收集会导致无输出。 - 背压崩溃:未处理背压时,如果生产速度远超消费速度且缓冲区满,协程会抛出
BufferOverflowException。建议使用buffer()或conflate()预防。 - 线程安全:
StateFlow默认值是线程安全的,但自定义MutableStateFlow的更新操作(.value = ...)应放在Dispatchers.Main上,避免并发写。
LiveData 迁移陷阱:LiveData 的
observe 会自动在 Activity/Fragment 的 onDestroy 取消订阅,而 Flow 需要依赖协程作用域(如 lifecycleScope)手动管理。忘记在适当的协程作用域中启动 collect 会导致内存泄漏或崩溃。
2.5 性能优化要点
- 冷流复用:对于昂贵操作(如数据库查询),使用
shareIn将冷流转为热流,避免多次创建数据源。 - 限制重播数:
SharedFlow(replay = 0)用于一次性事件,避免粘性事件导致重复消费。 - 背压策略选择:UI 场景优先使用
conflate或collectLatest;批量数据处理使用buffer(capacity)配合BUFFERED。 - 错误恢复:使用
catch操作符捕获上游异常,返回默认值或重试,避免整个流崩溃。
// 带错误恢复与背压优化的 Flow 示例
fun resilientFlow(): Flow<Result> = flow {
emit(fetchData()) // 可能抛异常
}.catch { e ->
emit(Result.Error(e)) // 发射错误结果
}.retry(3) { cause ->
cause is IOException // 网络错误自动重试
}.flowOn(Dispatchers.IO)
.conflate() // UI 端只关心最新结果