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 替代点击事件/一次性事件。
冷流 vs 热流 数据流模型 冷流(Cold Flow) 收集者 A Flow 生产者 每次收集重新执行 emit 收集者 B Flow 生产者 独立实例,互不干扰 热流(Hot Flow) SharedFlow 数据源常驻 收集者 1 收集者 2 收集者 3 热流:数据源独立,收集者共享同一流

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 性能优化要点

  1. 冷流复用:对于昂贵操作(如数据库查询),使用 shareIn 将冷流转为热流,避免多次创建数据源。
  2. 限制重播数:SharedFlow(replay = 0) 用于一次性事件,避免粘性事件导致重复消费。
  3. 背压策略选择:UI 场景优先使用 conflate 或 collectLatest;批量数据处理使用 buffer(capacity) 配合 BUFFERED。
  4. 错误恢复:使用 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 端只关心最新结果