0) 心智模型速记
- Channel:热的、点对点队列(单播)。有容量与溢出策略(SUSPEND/DROP_*),可 close/cancel,适合工作队列/管线/Actor。
- Flow:冷的按需序列(每个 collect 都从头跑)。默认无缓冲,逐元素串行;用 buffer/flowOn/*Latest 改执行/背压。
- SharedFlow / StateFlow:热的多播。SharedFlow(replay, extraBufferCapacity, onBufferOverflow);StateFlow = “总是最新” (像 CONFLATED + replay=1),不会完成。
1) 互通 API 一览(怎么“接上/转过来”)
Channel → Flow
-
channel.receiveAsFlow():把 ReceiveChannel 暴露为 冷 Flow,收集时开始从通道取;通道 close → Flow 完成。
-
channel.consumeAsFlow():同上,但收集结束时会取消通道(“消费型”)。仅在你“独占该通道”且收集完就应终止时使用;否则更常用 receiveAsFlow()。
Flow → Channel
-
flow.produceIn(scope, capacity = Channel.BUFFERED):在 scope 启一个协程收集 Flow,把元素送进一个 ReceiveChannel 并返回它。
-
手工:flow.onEach { ch.send(it) }.launchIn(scope)(更显式,也能配 trySend 与限频/背压策略)。
Flow → SharedFlow / StateFlow
-
flow.shareIn(scope, started, replay):变 SharedFlow(热、多播)。started 常用 WhileSubscribed(timeout),replay 视需求(0~N)。
-
flow.stateIn(scope, started, initial):变 StateFlow(总是最新,有初始值)。
Channel → SharedFlow
-
直接:channel.receiveAsFlow().shareIn(scope, WhileSubscribed(), replay=0);
-
或手工收集:launch { for (e in ch) shared.emit(e) }。
SharedFlow/StateFlow → Flow/Channel
-
SharedFlow/StateFlow 本身就是 Flow(热)。
-
要接 Channel:shared.produceIn(scope, capacity=...) 或 shared.onEach { ch.send(it) }.launchIn(scope)。
广播通道已废弃:BroadcastChannel/ConflatedBroadcastChannel → 请用 SharedFlow/StateFlow。
2) 背压/容量的“对应关系”(很重要)
| 目标语义 | Channel 配置 | Flow/SharedFlow 对应法 |
|---|---|---|
| 必达 + 真背压 | capacity=N, onBufferOverflow=SUSPEND(或 RENDEZVOUS) | Flow 默认即“无缓冲逐元素”,或 buffer(N, SUSPEND);SharedFlow 用 replay=N, SUSPEND(所有订阅者都跟得上才不挂)。 |
| 只要最新 | CONFLATED(或 capacity=1 + DROP_OLDEST) | StateFlow(天然“最新”);SharedFlow(replay=1, onBufferOverflow=DROP_OLDEST);Flow 链上用 conflate()。 |
| 滑窗保近 | capacity=32/64 + DROP_OLDEST | SharedFlow(replay=0, extraBufferCapacity=32/64, onBufferOverflow=DROP_OLDEST);或 Flow 上 buffer(32, DROP_OLDEST)。 |
| 多播(一发多收) | Channel 不适合(单播) | SharedFlow/StateFlow。 |
| 单播工作池 | ✅ Channel(多个消费者竞争同一队列) | Flow/SharedFlow 不是天然单播;如需“每条只处理一次”,还是 Channel。 |
emit/send 挂起差异
- Channel:是否挂起取决于容量×策略。
- SharedFlow:emit 也会挂起(当 replay+extraBufferCapacity 满、且慢订阅者未消费导致无空位时);tryEmit 非挂起。
- Flow:默认逐元素串行;是否背压取决于有没有 buffer/flowOn 等边界。
3) 完成/错误/关闭 语义差异
-
Channel:close() 后不再接收新元素;接收方读完缓冲即结束;cancel() 直接终止(可能丢未读)。
-
Flow:可完成/可失败;异常向下游传播。
-
SharedFlow/StateFlow:不会完成/失败;结束要靠外部信号(例如特殊事件 Completed)或取消 scope。
- 需要“带完成”的多播:用 事件类型携带完成/错误,或拆成“数据 SharedFlow + 状态 SharedFlow”。
4) 多播/单播的根本区别(避免错用)
- Channel 是单播:每条消息只给一个接收者。你开 N 个消费者就是“工作池/抢占消费”。
- SharedFlow 是多播:同一条数据同时给所有收集者;慢订阅者会滞后甚至在溢出时丢(取决于配置)。
- 想“广播”:用 SharedFlow;想“排队分发”:用 Channel。
5) 场景选型矩阵(怎么选最顺手)
| 场景 | 推荐 |
|---|---|
| 工作队列/流水线/限并发(一条任务只处理一次) | Channel(SUSPEND 背压),配扇入/扇出/管线模式 |
| UI 状态(总是最新) | StateFlow(或 flow.stateIn(...)) |
| UI/多订阅者事件(可能需要错过期间也能“补一条”) | SharedFlow(replay=1, extraBufferCapacity=...) |
| 高频行情/传感器(只要最新 + 限频) | MutableSharedFlow(replay=0, extraBufferCapacity=64, DROP_OLDEST) + Flow 端 sample()/conflate() |
| 将冷 Flow 变热并复用 | shareIn(scope, WhileSubscribed(...), replay) |
| 将热流喂给老代码(阻塞式或选择器模型) | shared.produceIn(scope) 得到 ReceiveChannel |
6) 可抄用代码片段
A) Channel → Flow(并保留通道生命周期)
val ch: ReceiveChannel<Event> = ...
val flow: Flow<Event> = ch.receiveAsFlow()
flow.onEach(::handle).launchIn(scope)
B) Channel → Flow(消费即终止通道)
val flow = ch.consumeAsFlow() // 收集结束/取消时会 cancel channel
flow.collect { ... }
C) Flow → Channel(老组件只会收 Channel)
val out: ReceiveChannel<Item> = upstreamFlow.produceIn(scope, capacity = Channel.BUFFERED)
launch { for (i in out) legacyConsume(i) }
D) Flow → SharedFlow(热、多播、可重放)
val shared: SharedFlow<Item> =
upstreamFlow
.shareIn(
scope = appScope,
started = SharingStarted.WhileSubscribed(5_000),
replay = 1 // 新订阅者先收到最近一条
)
E) “只要最新”的多播:SharedFlow ≈ Conflated
val hot = MutableSharedFlow<State>(
replay = 1,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.DROP_OLDEST // 与 replay=1 搭配呈“最新值”语义
)
// 更新
scope.launch { hot.emit(latestState()) }
// 收集(多处 UI)
hot.collect { render(it) }
F) Channel → SharedFlow(把单播源升级为多播)
val inbox: ReceiveChannel<Tick> = ...
val bus: SharedFlow<Tick> =
inbox.receiveAsFlow()
.shareIn(scope, SharingStarted.WhileSubscribed(5_000), replay = 0)
G) SharedFlow → Channel(给 select/通道架构用)
val sf: SharedFlow<Event> = ...
val ch: ReceiveChannel<Event> = sf.produceIn(scope) // 然后你可以 select { ch.onReceive { ... } }
7) 易坑与建议
- 把 Channel 当广播:会丢给大部分订阅者(单播特性)。广播请用 SharedFlow/StateFlow。
- 把 SharedFlow 当工作队列:一条事件会被每个订阅者处理一次,非你想要的“只处理一次”。队列请用 Channel。
- 忽略 SharedFlow 的背压:emit 也会因为慢订阅者而挂起;若不希望卡住生产者,用 extraBufferCapacity + onBufferOverflow=DROP_OLDEST 或 tryEmit。
- 随意 UNLIMITED:通道/SharedFlow 大容量会隐藏压力→高延迟/内存爆。容量从 64 起步,监控再调。
- 完成/关闭误解:SharedFlow/StateFlow 不会完成;需要完结信号请自定义事件类型或关闭上游 Channel/取消 scope。
- consumeAsFlow 误用:它会在收集结束时取消通道;当通道还会被别人用时不要用它,换 receiveAsFlow。
- 冷→热复用忘了 started 策略:shareIn 默认 Eagerly 可能过早启动;多数 UI 场景用 WhileSubscribed(timeout)。
一句话总结
- 单播队列/工作池:Channel;多播状态/事件:SharedFlow/StateFlow;一次性管道或需要按需计算:Flow。
- 互通很简单:channel.receiveAsFlow()、flow.produceIn(scope)、flow.shareIn/stateIn();关键是背压/容量/完成语义要匹配你的业务(必达 vs 只要最新、多播 vs 单播)。