Kotlin Flow 深度探索与实践指南——上部:基础与核心篇

138 阅读32分钟

第一部分:Flow 基础概念

第1章:初识 Kotlin Flow

快速入门:最简单的 Flow 示例(collect 为挂起函数,需在协程内调用,如 runBlockingscope.launch)。

runBlocking {
    flowOf(1, 2, 3)
        .map { it * 2 }
        .collect { println(it) }  // 输出 2, 4, 6
}

Flow 数据流完整链路

[数据源] → flow/flowOf/asFlow
    ↓
[操作符链] → map/filter/debounce/flatMapLatest/shareIn|stateIn/catch...
    ↓
[消费] → collect/collectLatest/launchIn

shareIn/stateIn 在操作符链中,多订阅共享时插入;冷流经其转为热流后,下游多处 collect 共享同一数据源。

1.1 Flow 是什么:官方定义与设计哲学

官方定义
Kotlin Flow 是 kotlinx.coroutines 中用于处理异步数据流的 API。它是一个基于协程构建的响应式流处理框架,专为异步、按顺序、连续的数据流设计。Flow 与协程深度集成,提供声明式、可组合的方式处理随时间变化的值序列。

设计哲学

  1. 声明式编程:使用函数式操作符表达数据转换,而不是命令式控制流
  2. 结构化并发:与协程作用域深度绑定,自动管理资源生命周期
  3. 冷流优先:默认采用按需生产、零共享的冷流模型,确保资源安全
  4. 可组合性:操作符可链式组合,构建复杂的数据处理管道
  5. 协程原生:无缝集成协程上下文,支持挂起函数和结构化取消

1.2 Flow 的生态位:对比 LiveData、SharedFlow、StateFlow、Channel

五者关系简图

        ┌─ Flow(冷流)──── 按需、独立
        │
数据流 ─ ┼─ SharedFlow(热流)─ 事件、广播、可配 replay
        │
        ├─ StateFlow(热流)─ SharedFlow 特化,状态、必有初始值
        │
        ├─ LiveData(Android)─ 生命周期感知,传统 UI
        │
        └─ Channel ── 点对点,单次消费,协程间通信
特性Flow (冷流)SharedFlow (热流)StateFlow (热流)LiveDataChannel
数据模型连续数据流事件流/广播状态容器生命周期感知数据持有者点对点通信管道
温度冷流热流热流热流热流
生命周期依赖收集者独立于收集者独立于收集者绑定观察者生命周期独立于收发者
数据共享每个收集者独立数据多收集者共享数据多观察者共享状态多观察者共享数据单次消费
初始值可选(通过 replay)必须有可有可无
背压处理内置(协程挂起)可配置缓冲区策略最新值替换可配置缓冲区
适用场景网络请求、数据库查询事件总线、实时数据广播UI 状态管理Android UI 状态协程间通信

1.3 Flow 的核心价值:为什么 Google 将其作为异步首选?

四大核心价值

  1. 协程原生

    • 无缝集成协程作用域,自动处理取消和资源清理
    • 支持挂起函数,可直接在流操作中使用异步操作
  2. 结构化并发安全

viewModelScope.launch {
    repository.dataFlow()
        .onEach { updateUI(it) }
        .launchIn(this)  // 自动绑定到 viewModelScope
}
  1. 声明式与可组合性
searchFlow
    .debounce(300)
    .filter { it.length > 2 }
    .distinctUntilChanged()
    .flatMapLatest { query ->
        flow { emit(api.search(query)) }  // api.search 返回 List 等,整份发射
    }
    .catch { emit(SearchResult.Error(it)) }
  1. 平台无关性
    • 纯 Kotlin 实现,可在 Android、iOS、后端等多平台使用
    • 摆脱 Android 特定框架(如 LiveData)的依赖

1.4 Flow 的三大基石:基于协程、冷流特性、顺序执行

基石一:基于协程

  • 所有 Flow 操作都在协程上下文中执行
  • 支持挂起函数,可安全执行 I/O 操作
  • 自动传播取消信号

基石二:冷流特性(默认)

val coldFlow = flow {
    println("开始生产数据")  // 只在收集时执行
    emit(1)
    emit(2)
    emit(3)
}

// 多次收集会多次执行
coroutineScope {
    launch { coldFlow.collect { println(it) } }  // 输出:开始生产数据
    launch { coldFlow.collect { println(it) } }  // 再次输出:开始生产数据
}

基石三:顺序执行

  • 每个元素按顺序通过整个处理链
  • 操作符按声明顺序执行
  • 保证数据处理的一致性

1.5 Flow 的三大定律:上下文保留、异常透明性、数据顺序保证

定律一:上下文保留
Flow 构建器中的代码运行在调用者的上下文中,除非使用 flowOn 显式切换:

flow {
    // 这里的上下文是调用者的上下文
    emit(1)
}.flowOn(Dispatchers.IO)  // 切换上游上下文
 .collect {
    // 这里恢复为原始上下文
 }

定律二:异常透明性
异常必须通过 catch 操作符处理,或向上游传播:

flow {
    throw RuntimeException("错误")
}.catch { cause ->
    // 捕获异常并恢复
    emit(RecoveryValue)
}.collect()

定律三:数据顺序保证
在没有并发操作符(如 buffer)的情况下,数据按顺序处理:

flowOf(1, 2, 3)
    .map { it * 2 }      // 1→2, 2→4, 3→6
    .filter { it > 3 }   // 2 被过滤,4 通过,6 通过
    .collect { print(it) }  // 输出:4 6(顺序保持不变)

小结:Flow 基于协程、默认为冷流、元素顺序保证;异常用 catch、上下文用 flowOn。


第二部分:冷流与热流

第2章:冷与热:Flow 的两种形态

2.1 理解数据流的「温度」

核心差异

  • 冷流:按需生产,每个收集者获得独立的数据流
  • 热流:主动生产,多个收集者共享相同的数据源

温度类比

  • 冷流 像「视频点播」:每个观众独立观看,从头开始播放
  • 热流 像「电视直播」:所有观众同时观看相同的内容

冷流 vs 热流 对比图

┌─────────────────────────────────────┬─────────────────────────────────────┐
│            冷流 (Cold)               │            热流 (Hot)                │
├─────────────────────────────────────┼─────────────────────────────────────┤
│  collect₁ ──→ 生产₁ ──→ 数据₁        │         生产 ──→ 共享缓冲区           │
│  collect₂ ──→ 生产₂ ──→ 数据₂        │              │                      │
│  collect₃ ──→ 生产₃ ──→ 数据₃        │      collect₁──┼──→ 数据             │
│  (每订阅独立生产)                     │      collect₂──┘                    │
│                                     │      collect₃──→ 数据 (共享)          │
├─────────────────────────────────────┼─────────────────────────────────────┤
│  按需、懒加载、取消即停                 │  持续、多订阅共享、生产独立于消费      │
└─────────────────────────────────────┴─────────────────────────────────────┘

2.2 冷流(Cold Flow)本质解析

核心定义:冷流是 Flow 的默认形态。它像一个「懒人配方」——只有在有人订阅(collect)时才开始执行生产逻辑;每次订阅都会触发一次独立的生产过程;订阅取消时,生产也随之停止。

冷流有三大本质特征:

2.2.1 惰性执行(Lazy)

含义:定义 flow 时,内部的 emit、网络请求、数据库查询等逻辑不会立刻执行,只有调用 collect 时才会真正开始。

意义:避免无谓计算。若定义后从不收集,则不会有任何资源消耗。

val coldFlow = flow {
    println("开始生产")  // 定义时不会执行
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

// 只有 collect 时才执行(scope 为 CoroutineScope,如 viewModelScope)
scope.launch { coldFlow.collect { println(it) } }  // 此时才输出 "开始生产"
2.2.2 点对点传输(每个收集者独立副本)

含义:同一个冷流被多次 collect 时,每次都会从头执行一遍生产逻辑,各收集者得到的是彼此独立的数据序列,互不影响。

意义:适合「每次请求都要重新计算」的场景(如网络请求、查询数据库),不会出现多个 UI 共享同一份过时数据的问题。

val dataFlow = flow {
    repeat(3) {
        delay(100)
        emit(System.currentTimeMillis())
    }
}

coroutineScope {
    launch { dataFlow.collect { println("A: $it") } }
    launch { dataFlow.collect { println("B: $it") } }
}
// A 和 B 会各自收到 3 个不同的时间戳,因为生产逻辑执行了 2 次
2.2.3 生命周期与收集者绑定

含义:冷流的生产协程由 collect 所在的协程作用域驱动。收集被取消时,生产协程会被取消,finally 块会执行,便于做资源清理。

意义:可以在 flow 内部安全地持有数据库连接、文件句柄等,收集结束或取消时自动释放。

fun observeUserData(userId: String): Flow<UserData> = flow {
    val connection = openDatabaseConnection()
    try {
        while (true) {
            emit(fetchUserData(userId))
            delay(1000)
        }
    } finally {
        connection.close()  // collect 取消时自动清理
    }
}

与热流的本质区别:热流的生产是独立于收集者的,不会因为「没人订阅」而停止;冷流则完全相反——没人收集就不生产,收集取消就停止。

2.3 热流(Hot Flow)特性详解

核心定义:热流(SharedFlow、StateFlow)的生产与消费是解耦的。生产者独立运行,不依赖是否有收集者;数据发出后存入共享缓冲区,多个收集者从同一份数据源接收。就像「电视直播」——节目在播,观众随时加入、随时离开,看到的是同一时段的内容。

热流有三大本质特征:

2.3.1 独立存在性(生产不依赖收集者)

含义:生产者独立于收集者运行。可以先启动生产,再启动收集;也可以在生产进行中,随时有新收集者加入。生产不会因为「没人订阅」而暂停。

意义:适合需要持续运行的场景(传感器、WebSocket、全局状态),数据始终在流动,收集者按需订阅即可。

val hotFlow = MutableSharedFlow<Int>()

scope.launch {
    // 生产者立刻开始,不等人订阅
    repeat(10) {
        delay(100)
        hotFlow.emit(it)
    }
}
// 延迟 300ms 才订阅,仍能收到 3~9 的数据(0~2 已错过)
scope.launch {
    delay(300)
    hotFlow.collect { println("收到: $it") }
}
2.3.2 广播机制(一发多收)

含义:一次 emit 发出的数据会同时送达所有当前订阅者,每个订阅者都能收到同一份数据。新订阅者加入时,根据 replay 配置决定能否收到历史数据。

意义:适合事件总线、通知推送——一处发送,多处响应。

val eventBus = MutableSharedFlow<Event>(replay = 0)

scope.launch { eventBus.emit(Event("USER_LOGIN")) }

// 多个收集者同时订阅,都会收到后续的同一事件
scope.launch { eventBus.collect { handleEvent(it) } }
scope.launch { eventBus.collect { logEvent(it) } }
2.3.3 多订阅者共享同一数据源

含义:多个收集者订阅同一个热流时,共享同一份生产逻辑和缓冲区。生产者只执行一次,发出的数据被复制给每个订阅者,而不是像冷流那样「每个收集者触发一次独立生产」。

意义:节省资源。传感器数据、实时股价等只需生产一次,多个 UI 组件共享。

class SensorManager(private val scope: CoroutineScope) {
    private val _sensorData = MutableSharedFlow<SensorData>(replay = 1)
    val sensorData = _sensorData.asSharedFlow()

    fun startMonitoring() {
        scope.launch {
            while (true) {
                _sensorData.emit(readSensor())  // 只读一次传感器
                delay(100)
            }
        }
    }
}

// 多个界面同时订阅,共享同一份传感器数据
scope.launch { sensorData.collect { updateScreenA(it) } }
scope.launch { sensorData.collect { updateScreenB(it) } }

与冷流的本质区别:冷流是「按需生产、每订一份产一份」;热流是「持续生产、多订共享一份」。热流适合「状态/事件」场景,冷流适合「请求/查询」场景。

2.4 Flow 的三元模型

三元模型指 Flow 数据流中的三个角色:生产者(用 flow {}emit() 等产生数据)→ 操作符mapfilter 等组成处理管道)→ 消费者collect 等接收并处理数据)。数据按此顺序流动。

┌─────────────┐     ┌─────────────────────────────┐     ┌─────────────┐
│   生产者     │     │         操作符(可链式)       │     │   消费者     │
│ flow/emit   │ ──→ │ map→filter→debounce→...     │ ──→ │  collect    │
└─────────────┘     └─────────────────────────────┘     └─────────────┘
       │                        │                              │
   产生原始数据              转换、过滤、组合                 接收并处理
// 生产 → 处理 → 消费
flow { listOf(1, 2, 3).forEach { emit(it) } }
    .filter { it > 1 }
    .map { "值: $it" }
    .collect { updateUI(it) }  // 输出 "值: 2", "值: 3"

2.5 典型场景选择策略

一、StateFlow 场景(UI 状态)

场景推荐选择选择理由配置要点
UI 状态管理StateFlow必有初始值、自动去重,新订阅者立即可见初始值 + Eagerly(全局)/ Lazily(页面)/ WhileSubscribed
页面级数据(按需加载)冷流 + stateIn(Lazily)进入页面才加载,离开可释放stateIn(SharingStarted.Lazily)

二、SharedFlow 场景(事件 / 实时推送)

场景推荐选择选择理由配置要点
事件总线 / 一次性事件SharedFlow无重播,不堆积,一发即过replay=0onBufferOverflow=SUSPEND
Toast / Snackbar 等一次性 UI 提示SharedFlow事件型,不重播,多界面可监听replay=0
实时数据推送(股价、传感器)SharedFlow持续生产、多订阅共享,新订阅者需最新值replay=1WhileSubscribed

三、冷流 Flow 场景(请求 / 查询)

场景推荐选择选择理由配置要点
网络请求 / API 调用普通 Flow(冷流)每次收集重新请求,按需执行flow {} + flowOn(IO) + catch
数据库查询 / 分页加载普通 Flow(冷流)每次进入页面重新查询,取消即停止flow {} 或 Room flow 扩展
资源敏感 / 需及时释放冷流无人收集不执行,收集取消即释放flow {} + finally 清理

四、冷流 + 热流转换场景

场景推荐选择选择理由配置要点
搜索建议 / 防抖输入冷流 + shareIn冷流防抖后转热流共享结果debounce + flatMapLatest + shareIn(replay=1)
多订阅者共享同一数据热流(SharedFlow/StateFlow)生产一次、多人消费,省资源冷流转热流用 shareIn / stateIn

快速记忆:状态用 StateFlow,事件用 SharedFlow(replay=0),请求/查询用冷流,实时共享用 SharedFlow(replay=1)。

场景选择流程图

需要处理什么数据?
        │
        ▼
┌───────────────┐
│ 是 UI 状态?   │──是──→ StateFlow
└───────────────┘
        │ 否
        ▼
┌───────────────┐
│ 是一次性事件? │──是──→ SharedFlow(replay=0)
└───────────────┘
        │ 否
        ▼
┌───────────────┐
│ 是多订阅共享? │──是──→ shareIn/stateIn 转热流
└───────────────┘
        │ 否
        ▼
┌───────────────┐
│ 是请求/查询?  │──是──→ 冷流 Flow
└───────────────┘
        │ 否
        ▼
   参考 2.5 典型场景表

2.6 转换魔法:shareIn / stateIn

将冷流转为热流,使多订阅者共享同一份数据,避免重复执行生产逻辑。


1. 为什么需要转换?

问题:冷流每次 collect 都会重新执行生产逻辑。若标题栏、详情区、侧边栏都要用户信息,各自 collect 会触发 3 次网络请求,且可能不同步。

解决:用 shareIn/stateIn 转成热流,上游只在一个协程里收集一次,下游多订阅者共享结果。

冷流:collect₁ → 请求₁   collect₂ → 请求₂   collect₃ → 请求₃
热流:collect₁ ─┐
               ├→ 一次请求 → 共享给 collect₁、collect₂、collectcollect₂ ─┤
      collect₃ ─┘
// 冷流:每个 collect 都请求
val cold: Flow<User> = flow { emit(api.getUser()) }

// 热流:只请求一次
val hot = flow { emit(api.getUser()) }
    .shareIn(scope, SharingStarted.Lazily, replay = 1)

2. 核心概念
函数返回类型作用
shareInSharedFlow冷流 → 热流,replay 可配
stateInStateFlow冷流 → 状态流,必有初始值,自动去重

3. 选 shareIn 还是 stateIn?
对比项shareInstateIn
用途事件流、需自定义 replayUI 状态、当前值
初始值必须有
replay可配,默认 0固定 1
自动去重

口诀:当前状态 → stateIn;事件或需 replay≠1 → shareIn

shareIn / stateIn 选择流程图

冷流需多订阅共享?
        │
        ├──否──→ 保持冷流
        │
        └──是──→ 需要「当前状态」+ 自动去重?
                        │
                        ├──是──→ stateIn
                        │
                        └──否──→ 需要 replay=0(事件)或 replay=N?
                                        │
                                        └──→ shareIn

4. 参数说明

shareIn

Flow<T>.shareIn(
    scope: CoroutineScope,   // 收集上游的作用域
    started: SharingStarted, // 何时开始/停止
    replay: Int = 0         // 新订阅者收到最近 N 个值
): SharedFlow<T>

stateIn

Flow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T         // 必须有
): StateFlow<T>

SharingStarted

策略行为适用
Eagerly立即开始,不停止全局配置、主题
Lazily首个订阅时开始,之后持续页面数据、按需加载
WhileSubscribed(stop, expire)有订阅运行,无订阅延迟停止页面切换频繁

SharingStarted 生命周期示意

Eagerly:   创建 ──→ [======== 一直运行 ========] ──→ scope 销毁
Lazily:    创建 ──→ 等订阅 ──→ [=== 运行 ===] ──→ scope 销毁
WhileSub:  创建 ──→ 等订阅 ──→ [运行] ──→ 无订阅 ──→ 延迟 stop ──→ 停止
WhileSubscribed 参数默认含义
stopTimeoutMillis0最后订阅离开后多久停止
replayExpirationMillisLong.MAX_VALUE停止后多久清除 replay 缓存(0=立即)

5. 场景配置示例
场景配置说明
UI 状态stateIn(Eagerly, init)立即有值,持续更新
页面数据stateIn(Lazily, init)有订阅才加载
事件总线shareIn(Eagerly, replay=0)不重播,一发即过
实时数据shareIn(WhileSubscribed(5s), replay=1)新订阅拿最新,离开 5s 后停
一次性数据不转换保持冷流
// UI 状态
val uiState = repo.dataFlow().stateIn(vmScope, SharingStarted.Eagerly, UiState())

// 页面数据
val product = repo.getProduct(id).stateIn(vmScope, SharingStarted.Lazily, null)

// 事件
val events = eventFlow.shareIn(vmScope, SharingStarted.Eagerly, replay = 0)

6. 常见注意
  • scope 销毁时收集会取消,转成的热流不再更新
  • Lazily 下无订阅者时不运行,首次订阅后才开始
  • replay=0 时新订阅者收不到历史,只收订阅后的值

2.7 快速参考表

选择标准选择冷流选择热流
数据独立性每消费者独立数据多消费者共享
资源消耗敏感、需及时释放可共享、避免重复
数据实时性每次需最新计算持续更新
生命周期精确控制开始/结束生产独立于消费者
使用场景网络、DB、一次性UI 状态、事件、实时
典型实现flow{}、flowOf()、asFlow()StateFlow、SharedFlow
转换方法-shareIn()、stateIn()

一句话:要独立、按需、省资源 → 冷流;要共享、实时、多订阅 → 热流。


第三部分:Flow 核心操作

第3章:Flow(冷流)- 基础构建与消费

3.1 构建器

构建器适用场景执行方式
flow{}自定义生产逻辑惰性执行,collect 时才开始
flowOf()固定值序列轻量,适合静态数据
asFlow()集合转流依赖原集合
channelFlow{}多协程并发发送、高吞吐立即执行,支持 launch + send
callbackFlow{}回调 API 转流见 3.3 节
flow { emit(1); emit(2) }
flowOf(1, 2, 3)
listOf(1, 2, 3).asFlow()
channelFlow {
    launch { send(1) }
    launch { send(2) }
}

构建器选择:单协程顺序生产 → flow;多协程并发 → channelFlow;回调转流 → callbackFlow

channelFlow 说明:内部用 Channel,可用 launch 并发 send,适合多协程并发生产。与 flow 不同,flowemit 是顺序挂起的,不能在不同协程里并发 emit。


3.2 消费:collect 家族

方法作用适用
collect挂起收集,对每个值依次执行通用消费
collectLatest新值到来取消上一处理,只处理最新搜索建议、防抖后请求
launchIn(scope)在 scope 中启动收集,不阻塞ViewModel/Fragment「启动即忘」
flowOf(1, 2, 3).collect { println(it) }

searchQueryFlow.debounce(300).collectLatest { fetchSuggestions(it) }

dataFlow.onEach { updateUI(it) }.launchIn(viewModelScope)

自定义 Collectorcollect 可传入实现 FlowCollector<T> 的对象,用于批量处理等自定义逻辑。

步骤

  1. 实现 FlowCollector<T>,重写 emit
  2. emit 中决定何时处理、如何聚合(如攒够一批再处理)
  3. 将对象传给 collect
flow.collect(object : FlowCollector<Int> {
    val batch = mutableListOf<Int>()
    override suspend fun emit(value: Int) {
        batch.add(value)                    // 1. 收集到缓冲区
        if (batch.size >= 10) {              // 2. 满一批时处理
            processBatch(batch.toList())
            batch.clear()
        }
    }
})  // 3. 流发出的每个值会调用 emit

3.3 callbackFlow:回调转流

将基于回调的 API 转为 Flow。trySend 发数据,close() / close(Throwable) 手动关闭(完成或出错时),awaitClose 在流关闭或收集取消后执行清理(如取消注册、释放资源)。

callbackFlow 执行流程

开始 → 1.创建回调(trySend/close) → 2.注册 API → 3.awaitClose 挂起
                                          │
                                    close()或收集取消
                                          │
                                          ▼
                                    执行 awaitClose 块(清理)
fun fetchUserFlow(userId: String): Flow<User> = callbackFlow {
    val callback = object : ApiCallback<User> {
        override fun onSuccess(data: User) { trySend(data); close() }
        override fun onError(e: Throwable) { close(e) }
    }
    api.getUser(userId, callback)
    awaitClose { api.cancel(userId) }
}

3.4 错误处理

操作符作用链中顺序
retry(N)异常时按条件重试,最多 N 次在 catch 之前
catch捕获上游异常,可 emit 恢复在 retry 之后
onCompletion流结束时回调,类似 finally任意位置

推荐顺序retrycatchonCompletioncollect

错误处理流程图

流执行
   │
   ▼
┌─────────────┐
│ 上游 emit   │
└─────────────┘
   │ 异常?
   ├──是──→ retry 重试?──是──→ 回到「上游 emit」
   │               │
   │               └──否──→ catch 捕获?──emit 恢复值──→ 继续
   │                               │
   │                               └──不处理──→ 异常向上传播
   │
   └──否──→ 继续
   │
   ▼
流结束 ──→ onCompletion(无论成功/失败都执行)
flow { emit(1); throw RuntimeException() }
    .catch { emit(-1) }
    .collect { println(it) }  // 1, -1

flow { /* 可能失败 */ }
    .retry(2) { it is IOException }
    .catch { emit("默认") }
    .collect { println(it) }

flow { emit(1); emit(2) }
    .onCompletion { cause -> println(if (cause == null) "正常" else "异常") }
    .collect { println(it) }

第4章:Flow 操作符全景概览

操作符分类总图

转换: map, transform, scan, mapLatest     ── 1→1 或 1→N
过滤: filter, take, drop, distinctUntilChanged, debounce, sample
组合: zip, combine, flatMapConcat/Merge/Latest
调度: flowOn, withContext
背压: buffer, conflate, collectLatest
冷转热: shareIn, stateIn
错误: catch, retry, onCompletion
操作符作用输入→输出适用场景
map一对一转换1 个值 → 1 个值简单类型转换、属性提取
transform灵活转换1 个值 → 0~N 个值条件发射、展开数据
scan累积计算流 → 所有中间结果实时统计、进度计算
mapLatest转换最新值最新值 → 转换结果取消过时的异步转换
// map:每个值乘 2,结果 2, 4, 6
flowOf(1, 2, 3).map { it * 2 }

// transform:每个值发射原值+平方,结果 1, 1, 2, 4
flowOf(1, 2).transform {
    emit(it)
    emit(it * it)
}

// scan:累积求和,发射每步中间结果,结果 0, 1, 3, 6
flowOf(1, 2, 3).scan(0) { acc, v -> acc + v }

// mapLatest:新查询到来时取消旧请求,只保留最新结果
queryFlow.mapLatest { api.search(it) }

转换操作符选择:1→1 类型转换 → map;1→0~N 展开 → transform;累积中间结果 → scan;新值取消旧转换 → mapLatest。

4.2 过滤系操作符

操作符作用效果适用场景
filter条件过滤保留符合条件的值数据筛选
take取前 N 个只取前 N 个后完成限制数据量
drop跳过前 N 个跳过前 N 个值忽略初始数据
distinctUntilChanged去重过滤连续重复UI 状态更新
debounce防抖稳定期后取最后值搜索框输入
sample采样定期取最新值高频数据降频

filter:保留符合条件的值。

flowOf(1, 2, 3, 4).filter { it % 2 == 0 }  // 2, 4

take / drop:取前 N 个或跳过前 N 个。

flowOf(1, 2, 3, 4).take(2)   // 1, 2
flowOf(1, 2, 3, 4).drop(2)   // 3, 4

distinctUntilChanged:过滤连续重复。

flowOf(1, 1, 2, 2, 1).distinctUntilChanged()  // 1, 2, 1

debounce / sample:时间相关,防抖取最后、采样取最新。

inputFlow.debounce(300)   // 搜索防抖(ms)
sensorFlow.sample(1000)   // 高频降频(ms)

过滤操作符选择:按条件筛 → filter;限制数量 → take/drop;去连续重 → distinctUntilChanged;输入防抖 → debounce;高频降采样 → sample。

4.3 组合系操作符

操作符作用组合方式适用场景
zip一对一配对两值都到才组合合并相关数据
combine动态组合任一更新即重算UI 多状态合并
flatMapConcat顺序展开一个完成再下一个保证顺序
flatMapMerge并发展开多路并发(默认 16)提高吞吐
flatMapLatest最新值展开新值取消旧展开搜索建议

zip:两流一一配对,等待两边都到。

flowOf("A", "B").zip(flowOf(1, 2)) { a, b -> "$a$b" }  // A1, B2

combine:任一更新就重新组合,适合多输入表单、筛选。

queryFlow.combine(filterFlow) { q, f -> SearchParams(q, f) }

zip vs combine:zip 需两边都到才配对,适合等长合并;combine 任一更新即重算,适合多状态联动。

flatMap 三兄弟对比图

输入: 1, 2, 3

flatMapConcat(顺序):
  1→流₁→等流₁完→2→流₂→等流₂完→3→流₃
  结果顺序: 流₁所有值, 流₂所有值, 流₃所有值

flatMapMerge(并发):
  1→流₁  \
  2→流₂  ─→ 同时执行(最多N个),结果交错
  3→流₃  /

flatMapLatest(最新):
  1→流₁──→2 到来,取消流₁→2→流₂──→3 到来,取消流₂→3→流₃
  结果: 只保留流₃的值
flowOf(1,2,3).flatMapConcat { fetch(it).asFlow() }   // 顺序
flowOf(1,2,3).flatMapMerge(2) { fetch(it).asFlow() } // 最多 2 并发
queryFlow.flatMapLatest { flow { emit(api.search(it)) } }  // 只保留最新

flatMap 选择:保证顺序 → Concat;提高吞吐 → Merge;只保留最新(搜索)→ Latest。

4.4 线程调度

操作符/函数作用适用场景
flowOn指定其上游的执行上下文(不影响下游)网络请求、数据库查询等 IO 操作
withContext临时切换上下文collect 内切主线程更新 UI
// flowOn:上游在 IO 线程执行,收集在调用方上下文
flow { emit(fetch()) }.flowOn(Dispatchers.IO)

// withContext:collect 内临时切主线程更新 UI
dataFlow.collect { data ->
    withContext(Dispatchers.Main) { updateUI(data) }
}

4.5 背压处理

背压:生产速度 > 消费速度时的数据积压。不处理可能内存暴涨或阻塞。


一、冷流 vs 热流

对比项冷流(Flow)热流(SharedFlow)
配置方式操作符 buffer()conflate()构造参数 extraBufferCapacityonBufferOverflow
默认缓冲无 buffer 则无缓冲replay=0、extraBufferCapacity=0 → 总容量 0
默认表现emit 挂起等 collect 取走emit 挂起等收集者取走
相同点onBufferOverflow 语义一致;collectLatest 均可用于收集

结论:默认都是无缓冲,emit 挂起等消费。不丢数据、不堆积,生产被消费拖慢。需要缓冲时:冷流加 buffer(),热流设 extraBufferCapacityreplay

默认参数:冷流 buffer() 默认 capacity=64、SUSPEND;热流 SharedFlow 默认 replay=0、extraBufferCapacity=0、SUSPEND。使用 DROP_* 策略时需 extraBufferCapacity ≥ 1 才有实际效果。


二、冷流背压

emit ──→ 用了 buffer?──否──→ 挂起等消费 ──→ 消费端
   │         └──是──→ 入队 ──→ 队列满?──是──→ onBufferOverflow
   │                              └──否──→ 消费端
操作符说明示例
buffer()64,SUSPEND,不丢flow.buffer().collect { }
conflate()容量 1,DROP_OLDEST,只留最新sensorFlow.conflate().collect { }
buffer(N, DROP_OLDEST)同 conflate,容量可调flow.buffer(16, BufferOverflow.DROP_OLDEST)
collectLatest { }消费端:新值到来取消当前queryFlow.collectLatest { api.search(it) }
flow { emit(1); emit(2) }.buffer().collect { slowProcess(it) }      // 不丢数据
sensorFlow.conflate().collect { updateUI(it) }                       // 只要最新
queryFlow.collectLatest { api.search(it) }                           // 消费端只处理最新

三、热流背压

emit/tryEmit ──→ 入缓冲(replay+extraBufferCapacity) ──→ 满?──是──→ onBufferOverflow
                                              └──否──→ 多 collect 取

extraBufferCapacity:额外缓冲;onBufferOverflow:满时策略(SUSPEND / DROP_OLDEST / DROP_LATEST)。

MutableSharedFlow<Int>()   // 默认无额外缓冲
MutableSharedFlow<Int>(extraBufferCapacity = 64)   // 提高吞吐
MutableSharedFlow<Int>(replay = 1, extraBufferCapacity = 16, onBufferOverflow = BufferOverflow.DROP_OLDEST)   // 只要最新

四、onBufferOverflow(冷热通用)

策略行为
SUSPEND挂起等消费,不丢
DROP_OLDEST丢最旧,放最新
DROP_LATEST丢最新,保留队列

数据流buffer → [生产]─[队列]─[消费];conflate → [生产]─[最新]─[消费];collectLatest → 新值到来取消当前。


五、速查

需求冷流热流
不丢数据buffer() 或默认extraBufferCapacity 足够 + SUSPEND
只要最新conflate()extraBufferCapacity + DROP_OLDEST
消费端只处理最新collectLatest { }collectLatest { }

组合bufferconflate 二选一;collectLatest 可与任一边搭配。

4.6 批量数据处理

注意:Kotlin 1.9+ 的 kotlinx-coroutines 提供了 chunked(size) 操作符(标记为 @ExperimentalCoroutinesApi),用于按大小分批。若需时间窗口分批,需自定义实现。

// 方式一:chunked(需 @OptIn(ExperimentalCoroutinesApi::class))
@OptIn(ExperimentalCoroutinesApi::class)
dataFlow
    .chunked(50)
    .collect { batch ->
        database.insertBatch(batch)
    }

// 方式二:buffer + collect 内攒批(需定义 batchBuffer,流结束用 onCompletion 处理剩余)
val batchBuffer = mutableListOf<Item>()
dataFlow
    .buffer(100)
    .onCompletion { if (batchBuffer.isNotEmpty()) database.insertBatch(batchBuffer) }
    .collect { item ->
        batchBuffer.add(item)
        if (batchBuffer.size >= 50) {
            database.insertBatch(batchBuffer.toList())
            batchBuffer.clear()
        }
    }

4.7 实战速查:搜索场景(经典组合)

searchQueryFlow
    .debounce(300)           // 防抖
    .filter { it.length >= 3 }
    .distinctUntilChanged()
    .flatMapLatest { flow { emit(api.search(it)) } }  // search 返回 List,整份发射
    .flowOn(Dispatchers.IO)  // 若 search 为 IO,上游切 IO
    .catch { emit(emptyList()) }

4.8 操作符组合黄金法则

  1. 顺序很重要:操作符按声明顺序执行,flowOn 只影响其上游
  2. 尽早过滤:先用 filter 减少后续数据量
  3. 合理防抖:用户输入用 debounce,实时数据用 sample
  4. 线程优化:IO 操作用 flowOn(Dispatchers.IO)
  5. 错误恢复:用 catch 在适当位置恢复,retry 在 catch 前
  6. 资源清理:无限流要确保能被取消,callbackFlow 务必 awaitClose

典型顺序filter/mapdebounceflatMapLatestflowOn(IO)catchcollectflowOn 影响其上游,故放靠近耗时操作前;搜索场景(4.7)将 flowOn 置于 flatMapLatest 之后,使 api.search 在 IO 执行。

操作符链数据流程图(与典型顺序一致,flowOn 影响其上游):

  生产者(flow)
       │
       ▼
  filter/map  ──── 过滤、转换
       │
       ▼
  debounce   ──── 防抖(用户输入场景)
       │
       ▼
  flatMapLatest ──── 展开,新值取消旧(耗时操作在此)
       │
       ▼
  flowOn(IO)  ──── 以上游在 IO 执行
       │
       ▼
  catch      ──── 异常恢复
       │
       ▼
  collect    ──── 消费,通常主线程

常见问题速查

问题建议
多次 collect 重复请求用 shareIn/stateIn 转热流
UI 不更新检查 copy()、主线程更新
页面销毁仍在收集repeatOnLifecycle、flowWithLifecycle
Toast 重复显示用 SharedFlow(replay=0),不用 StateFlow
回调转 FlowcallbackFlow,用 trySend/close、awaitClose

第四部分:SharedFlow 与 StateFlow

第5章:SharedFlow(热流)- 事件与广播专家

SharedFlow 是热流,生产与消费解耦,多订阅者共享同一数据源。适用于事件总线、实时推送、广播等「一发多收」场景。与 StateFlow 区别:无初始值、replay 可配、无自动去重。


5.1 配置三要素

背压关系extraBufferCapacityonBufferOverflow 是背压处理的一环,与冷流 buffer(capacity, onBufferOverflow) 语义一致。完整背压流程见 4.5 节。

参数作用默认值
replay新订阅者立即可收到的历史值数量0
extraBufferCapacity生产快于消费时的额外缓冲(背压容量)0
onBufferOverflow缓冲区满时的处理策略(背压溢出策略)SUSPEND

replay 取值

  • 0:新订阅者只收订阅后的值,适合事件(Toast、点击)
  • 1:新订阅者拿到最新 1 个,适合实时状态
  • N:新订阅者拿到最近 N 个,适合聊天记录等

onBufferOverflow

策略行为适用
SUSPEND挂起生产者直到有空间重要数据(消息、订单)
DROP_OLDEST丢最旧,放最新只要最新(传感器、股价)
DROP_LATEST丢最新,保留队列顺序敏感

5.2 参数组合示例

场景replayextraBufferCapacityonBufferOverflow
事件总线(Toast、点击)00 或小值SUSPEND
实时数据(股价、位置)110~50DROP_OLDEST
聊天消息50~10050SUSPEND

事件总线 + tryEmitextraBufferCapacity=0 且无订阅者时,tryEmit 返回 false,事件会丢。可设小缓冲或接受「无订阅者时丢失」。

// 事件总线
MutableSharedFlow<Event>(replay = 0, extraBufferCapacity = 0)

// 实时数据
MutableSharedFlow<Price>(replay = 1, extraBufferCapacity = 10, onBufferOverflow = BufferOverflow.DROP_OLDEST)

5.3 发射策略:emit() vs tryEmit()

特性emit()tryEmit()
执行方式挂起,可能等待非挂起,立即返回
调用位置必须在协程内任意线程(含回调)
缓冲区满时挂起等待返回 false,数据可能丢

emit vs tryEmit 选择流程图

需要发射数据?
   │
   ├── 在协程内?──否──→ 只能用 tryEmit()
   │
   └── 在协程内?──是──→ 数据必须送达?
                           │
                           ├──是──→ emit()
                           └──否──→ 可丢?──是──→ tryEmit() + DROP_OLDEST
                                        └──否──→ emit()

选择速查

场景推荐
重要数据、用户操作emit()
回调中、非协程环境tryEmit()
高频数据、可丢失tryEmit() + DROP_OLDEST
// 协程内:保证送达
viewModelScope.launch { eventFlow.emit(ClickEvent()) }

// 回调内:只能用 tryEmit
button.setOnClickListener {
    eventFlow.tryEmit(ClickEvent())
}

5.4 常见陷阱与对策

陷阱表现对策
replay 过大内存持续增长,新订阅收到大量旧数据replay 按需设,用 WhileSubscribed(replayExpirationMillis) 过期清除
缓冲区满emit 长时间挂起,tryEmit 频繁返回 false增大 extraBufferCapacity,或改用 DROP_OLDEST
多订阅重复处理同一事件被多个收集者各处理一次事件加 id,收集端用 Set 去重
生命周期不当页面销毁后仍在收集,内存泄漏或崩溃用 repeatOnLifecycle(STARTED)、flowWithLifecycle
冷流重复请求多处 collect 同一冷流,重复请求网络/DB用 shareIn 转热流,多订阅共享
背压忽略生产远快于消费,内存暴涨设合理缓冲区,或 DROP_OLDEST,消费者用 collectLatest
异常未捕获发射时抛异常导致流终止在 emit 前 try-catch,或上游用 catch 转错误值
配置变更重复订阅屏幕旋转后 Activity 重建,重复收集在 ViewModel 中收集,或 repeatOnLifecycle 自动处理
事件与状态混用用 SharedFlow(replay=1) 做事件,丢失或重复事件用 replay=0,状态用 StateFlow

5.5 SharingStarted 速查

策略行为适用场景
Eagerly立即开始收集上游,永不停止全局配置、主题、语言
Lazily首个订阅者出现时开始,之后持续运行页面级数据、按需加载
WhileSubscribed有订阅时运行,最后订阅者离开后延迟停止页面切换频繁、省电省资源

WhileSubscribed 参数

参数默认含义
stopTimeoutMillis0最后订阅者离开后,延迟多久停止
replayExpirationMillisLong.MAX_VALUE停止后多久清除 replay 缓存(0=立即)
SharingStarted.Eagerly
SharingStarted.Lazily
SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000, replayExpirationMillis = 60000)

选择建议:全局且持续 → Eagerly;进页面才要 → Lazily;频繁进出页面 → WhileSubscribed。


第6章:StateFlow(热流)- 新一代状态容器

StateFlow 专为 UI 状态设计,等价于 SharedFlow(replay=1) + 必有初始值 + 自动去重。适合 ViewModel 暴露状态,配合 Compose 或 View 使用。跨平台,可替代 LiveData。


6.1 核心特性

特性说明示例
必有初始值新订阅者立即可见,无需空判断MutableStateFlow("")
自动去重equals() 相同不发射,减少无效重组连续 value = 1 只触发一次
固定 replay=1新订阅者立即拿到当前值后加入的订阅者先收当前状态
线程安全支持多线程更新IO 线程 value = x 安全
val state = MutableStateFlow(0)
state.value = 1
state.value = 1  // 相同,不触发
state.value = 2  // 不同,触发

// 新订阅者立即可见(collect 挂起等待后续更新,会先收到当前值 2)
state.collect { println(it) }  // 先打印 2,再等待后续

6.2 原子更新:update() / value

直接赋值:简单场景用 state.value = newValue
原子读写:并发场景用 update { },保证 read-modify-write 原子性。

// 简单更新
_state.value = UserState(loading = false, data = user)

// 原子更新(推荐)
_state.update { it.copy(name = newName, age = newAge) }

// 条件更新
_state.update { if (it.count > 0) it.copy(count = it.count - 1) else it }

注意update 的 lambda 可能重试,逻辑需幂等。


6.3 从 LiveData 迁移到 StateFlow

ViewModel

// Before
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user

// After
private val _state = MutableStateFlow<UserState>(UserState())
val state: StateFlow<UserState> = _state.asStateFlow()

状态更新:用 copy() 创建新对象,一次赋值。

_state.value = _state.value.copy(loading = true, error = null)
_state.value = _state.value.copy(loading = false, user = result)

UI 层(View)

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.state.collect { updateUI(it) }
    }
}

UI 层(Compose)

val state by viewModel.state.collectAsStateWithLifecycle()

6.4 UI 层安全消费

核心问题:直接在 lifecycleScope.launch { flow.collect {} } 时,页面进入后台或旋转重建后,收集可能泄漏或重复。需用生命周期感知的方式,在 STARTED 时收集、STOPPED 时停止。

方式形式区别适用
repeatOnLifecycle作用域函数,包裹收集块块内可启动多个 collect,统一受生命周期控制Activity/Fragment,需同时收集多个流
flowWithLifecycleFlow 操作符,返回新 Flow单个流做生命周期过滤,写法更短Activity/Fragment,只收集一个流
collectAsStateWithLifecycleCompose 收集为 StateCompose 专用,自动随生命周期启停,避免重组时重复收集Compose
SavedStateHandle.getStateFlow从 Handle 获取 StateFlow用于创建进程死亡后可恢复的流,不是收集方式ViewModel 内需持久化的状态

生命周期与收集流程图

Activity/Fragment 生命周期
   onCreate → onStart → onResume → onPause → onStop → onDestroy
                      │                    │
repeatOnLifecycle(STARTED) 收集区:         │
                      [======== 收集运行 ========]
                                                [停止收集]

进入 STARTED → 启动 collect
离开 STARTED → 取消 collect,避免泄漏

repeatOnLifecycle:块内协程随生命周期取消,进入 STARTED 时启动,离开时取消。

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        launch { viewModel.state.collect { updateUI(it) } }
        launch { viewModel.events.collect { showToast(it) } }
    }
}

flowWithLifecycle:流在生命周期外不发射,等同于「单流版 repeatOnLifecycle」。

lifecycleScope.launch {
    viewModel.state
        .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
        .collect { updateUI(it) }
}

collectAsStateWithLifecycle:在 Compose 中收集为 State,生命周期感知,避免 LaunchedEffect(Unit) 导致重复收集。

@Composable
fun Screen(viewModel: MyVM) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    Text(state.toString())
}

SavedStateHandle.getStateFlow:用于状态持久化,与收集方式配合使用。

class VM(handle: SavedStateHandle) : ViewModel() {
    val state = handle.getStateFlow("key", defaultValue)
}

6.5 复杂 UI 状态

当界面要展示多种数据(商品、评价、库存等),或状态之间有依赖时,需要用不同方式组织 StateFlow。


1. 状态合并(combine)

场景:商品详情页要同时展示商品信息、评价列表、库存,三者来自不同接口。若各自用 StateFlow,UI 可能先看到商品、再看到评价,出现闪烁。

做法:用 combine 把多个流合成一个,任一变化就重新组合,UI 只订阅一个流,一次拿到完整数据。

val uiState = combine(productFlow, reviewsFlow, stockFlow) { product, reviews, stock ->
    ProductDetailState(product, reviews, stock)
}.stateIn(scope, SharingStarted.WhileSubscribed(5000), ProductDetailState())

// UI:uiState.collect { 一次拿到 product、reviews、stock }

2. 状态分片

场景:整个页面用一个超大 StateFlow,主题、用户、通知都在一起。改个通知数字,主题组件也会重组,浪费性能。

做法:按业务拆成多个小 StateFlow,每个组件只订阅自己需要的。主题改只影响主题组件,用户改只影响用户组件。

val themeState = MutableStateFlow(Theme.LIGHT)      // 主题组件只 collect 这个
val userState = MutableStateFlow<User?>(null)      // 用户区域只 collect 这个
val notificationCount = MutableStateFlow(0)        // 通知图标只 collect 这个

3. 派生状态

场景:购物车状态只有 List<Item>,但 UI 还要显示总价、是否为空。每次在 UI 里算一遍,代码重复。

做法:用 map + stateIn 从已有状态算出新状态,ViewModel 统一维护,UI 直接订阅。

val cartState = MutableStateFlow<List<Item>>(emptyList())

val totalPrice = cartState.map { it.sumOf { i -> i.price * i.qty } }
    .stateIn(scope, SharingStarted.Eagerly, 0.0)

val isEmpty = cartState.map { it.isEmpty() }
    .stateIn(scope, SharingStarted.Eagerly, true)

// UI:totalPrice.collect { 显示总价 };isEmpty.collect { 显示空车提示 }

4. 状态 + 事件分离

场景:Toast、弹窗、导航这类「一次性」反馈,若用 StateFlow 存「当前要显示的 Toast」,新订阅者会重复看到旧的 Toast。

做法状态(列表、加载中)用 StateFlow;事件(Toast、导航)用 SharedFlow(replay=0),只发给当前订阅者,不重播。

复杂状态管理流程图

多数据源?──是──→ combine 合并
    │
    └──否──→ 大状态导致过度重组?──是──→ 状态分片
                    │
                    └──否──→ 需从已有状态计算?──是──→ 派生状态(map+stateIn)
                                    │
                                    └──否──→ 有一次性反馈?──是──→ 状态+事件分离
                                                    └──否──→ 单一 StateFlow 即可
private val _state = MutableStateFlow(UiState())   // 状态:可重播
private val _events = MutableSharedFlow<UiEvent>()  // 事件:一次性

6.6 常见陷阱与对策

陷阱表现对策
不用 copy()改可变属性,UI 不更新用不可变数据类,更新时 copy()
非主线程更新 UI崩溃或闪烁耗时操作用 withContext(IO),结果在主线程赋给 state
Compose 错误收集每次重组都 collect,泄漏用 collectAsStateWithLifecycle
状态事件混用事件丢失或重复状态用 StateFlow,事件用 SharedFlow(0)
忽略取消资源泄漏collect 内 try-finally 或 ensureActive()
大状态对象copy 开销大、重组频繁分片、或 Compose 内 remember 缓存
密封类未覆盖when 漏分支,运行异常补全所有分支
Eagerly 拿到旧缓存shareIn/stateIn 立即开始时,上游若有缓存,新订阅者可能收到过时值WhileSubscribed(replayExpirationMillis=0) 或 Lazily

简单示例

// 1. 不用 copy():可变属性改后 UI 不更新
// ❌ state.value.items.add(item)
// ✅ state.update { it.copy(items = it.items + item) }

// 2. 非主线程更新:IO 线程直接赋值可能崩溃
// ❌ withContext(Dispatchers.IO) { _state.value = fetch() }
// ✅ val r = withContext(Dispatchers.IO) { fetch() }; _state.value = r

// 3. Compose 错误收集:每次重组都 collect 导致泄漏
// ❌ LaunchedEffect(Unit) { vm.state.collect { s = it } }
// ✅ val s by vm.state.collectAsStateWithLifecycle()

// 4. 状态事件混用:用 StateFlow 做一次性事件易丢或重复
// ❌ _toastState = MutableStateFlow<String?>(null)
// ✅ _toastEvents = MutableSharedFlow<String>(replay = 0)

// 5. 忽略取消:collect 内开资源未在取消时释放
// ❌ state.collect { val f = openFile(); use(f) }
// ✅ state.collect { val f = openFile(); try { use(f) } finally { f.close() } }

// 6. 大状态对象:整块 copy 开销大
// ❌ state.update { it.copy(wholeBigList = newList) }
// ✅ 分片:themeState、userState 分开;或 Compose 内 remember(key) { }

// 7. 密封类未覆盖:漏分支导致运行时异常
// ❌ when(s) { is Success -> ... }  // 漏 is Error、is Loading
// ✅ when(s) { is Success -> ; is Error -> ; is Loading -> }  // 补全所有分支

// 8. Eagerly 拿到旧缓存:立即开始时,上游若有缓存会传给新订阅者
// ❌ shareIn(Eagerly) 且上游有缓存
// ✅ WhileSubscribed(replayExpirationMillis = 0) 或 Lazily

6.7 StateFlow vs SharedFlow 速查

对比

维度StateFlowSharedFlow
初始值必须有无(replay 决定新订阅是否拿历史)
自动去重
replay固定 1可配 0、1、N
典型场景UI 状态事件、广播、实时数据

选择速查

需求选择示例
UI 状态(列表、详情、加载)StateFlowMutableStateFlow(UiState())
一次性事件(Toast、Snackbar、导航)SharedFlow(replay=0)MutableSharedFlow<UiEvent>()
实时数据(股价、位置)SharedFlow(replay=1)MutableSharedFlow<Price>(replay=1)
需最近 N 条(聊天记录)SharedFlow(replay=N)MutableSharedFlow<Message>(replay=50)

StateFlow vs SharedFlow 决策流程图

需要热流?
   │
   ├──否──→ 用冷流 Flow
   │
   └──是──→ 需要初始值 + 自动去重?
               │
               ├──是──→ StateFlow(UI 状态)
               │
               └──否──→ replay=0?──是──→ SharedFlow(0)(事件)
                               │
                               └──否──→ SharedFlow(1或N)(实时/历史)

混合使用:ViewModel 内常见「状态 + 事件」模式。

private val _state = MutableStateFlow(UiState())
val state: StateFlow<UiState> = _state.asStateFlow()

private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()

附录 A:四大数据流终极对比表

整体对比图

                    Flow          StateFlow       SharedFlow      LiveData
                 ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
        温度     │  冷流    │    │  热流    │    │  热流    │    │  热流    │
        初始值   │  无     │    │  必须有   │    │  可选    │    │  可选    │
        去重     │  无     │    │  有      │    │  无     │    │  无     │
        场景     │ 请求/查询│    │ UI 状态  │    │ 事件/实时│    │Android UI│
                 └─────────┘    └─────────┘    └─────────┘    └─────────┘

基础属性

维度FlowStateFlowSharedFlowLiveData
温度冷流热流热流热流
数据共享每订阅独立多订阅共享多订阅共享多观察者共享
初始值必须有可选(replay)可选
生命周期依赖收集者独立,需配合 Lifecycle独立,需配合 Lifecycle内置感知

重播与去重

维度FlowStateFlowSharedFlowLiveData
replay/重播固定 1可配 0/1/N无标准
自动去重(equals)

发射与消费

维度FlowStateFlowSharedFlowLiveData
发射方式emit(构建器内)value= / update()emit() / tryEmit()postValue / setValue
消费方式collectcollectcollectobserve
是否挂起collect 挂起collect 挂起collect 挂起

背压与线程

维度FlowStateFlowSharedFlowLiveData
背压处理内置挂起DROP_OLDEST 语义可配 SUSPEND/DROP_*
线程安全依赖 flowOn高并发安全高并发安全主线程安全

平台与生态

维度FlowStateFlowSharedFlowLiveData
平台Kotlin 多平台Kotlin 多平台Kotlin 多平台仅 Android
协程集成深度深度深度有限
操作符丰富继承 Flow继承 Flow

适用场景

维度FlowStateFlowSharedFlowLiveData
典型场景网络请求、DB 查询、一次性UI 状态、应用状态事件总线、实时推送、广播Android UI(传统)
冷转热-stateInshareInasLiveData

内存与性能

维度FlowStateFlowSharedFlowLiveData
内存特点按需执行,无订阅无消耗常驻最新 1 值可配缓冲,replay 影响中等
多订阅开销每订阅独立生产共享 1 份共享 1 份共享 1 份
测试便利性易于单测易测易测易测

创建方式

类型创建方式
Flowflow { }flowOf()asFlow()
StateFlowMutableStateFlow(init)、冷流 .stateIn()
SharedFlowMutableSharedFlow()、冷流 .shareIn()
LiveDataMutableLiveData()Transformations

选择口诀:请求/查询用 Flow,UI 状态用 StateFlow,事件用 SharedFlow(0),实时共享用 SharedFlow(1)。


全文核心速查

选型总流程图

数据需求?
   │
   ├── 请求/查询(每次独立)──→ 冷流 Flow(若需多订阅共享则加 shareIn/stateIn)
   │
   ├── UI 状态(必有当前值)──→ StateFlow
   │
   ├── 一次性事件(Toast、导航)──→ SharedFlow(replay=0)
   │
   ├── 实时共享(多订阅、要最新)──→ SharedFlow(replay=1) 或 shareIn
   │
   └── 其他/组合场景 ──→ 参考 2.5 典型场景表
场景选型关键配置
网络/DB 请求Flow(冷流)flowOn(IO) + catch
UI 状态StateFlow必有初始值,copy() 更新
Toast/导航等事件SharedFlow(replay=0)tryEmit 或 emit
实时数据共享SharedFlow(replay=1)shareIn(WhileSubscribed)
多订阅共享冷流shareIn / stateIn选 SharingStarted
搜索防抖debounce + flatMapLatest300ms debounce
生命周期收集repeatOnLifecycleSTARTED 时收集
回调转流callbackFlowtrySend + close + awaitClose