一、顶层接口
Flow
- 语义:冷流,只有一个挂起函数:
kotlin
interface Flow<out T> {
suspend fun collect(collector: FlowCollector<T>)
}
-
约束(非常重要):
-
emit 串行、不可并发;
-
异常透明性 :下游运算只捕获上游 抛出的异常,不能吞掉下游自身的异常;
-
上下文保守 :除非使用 flowOn,否则生产与收集在同一协程上下文。
-
FlowCollector
- 职责:下游的"接收端",只有一个挂起函数:
kotlin
interface FlowCollector<in T> {
suspend fun emit(value: T)
}
- SafeCollector (后文详解)会包住真实的 collector,做上下文一致性 与异常透明性校验。
二、构建器与安全包装
Builders( flow {} 、flowOf 、asFlow )
- flow {} 返回实现类 SafeFlow(内部记下 block):
kotlin
fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> =
SafeFlow(block)
-
flowOf(1,2,3) 返回一个简单的 Flow,collect 时顺序 emit。
-
asFlow() 把 Iterable/Sequence/Array 转 Flow。
SafeCollector.kt
-
SafeCollector:包装下游 FlowCollector,在 emit 时做两件事:
-
上下文校验 :保证 emit 时使用的 CoroutineContext 与创建 SafeCollector 的上下文等价(尤其是 ContinuationInterceptor 一致)。否则抛 IllegalStateException(典型:在 flow { withContext(Dispatchers.IO) emit(x) } 被禁止------应改用 flowOn)。
-
异常透明性 :利用内置标记保证 catch 只能拦截上游 异常,不能误拦下游异常。
-
结论:不要在 flow{} 里 withContext(...) 去切线程;要切就用 flowOn。
三、中间操作(操作符)
1) 变换(Transform.kt)
- map/filter/onEach/transform 等都实现为包裹上游 的新 Flow,collect 时形成调用链。
- 伪流程:
scss
upstream.collect { value ->
val v2 = map(value)
if (filter(v2)) downstream.emit(v2)
}
- 没有 Rx 式"算子融合" ,但内联 + inline class + 简单状态机让开销很低。
2) 合并与扁平化(Merge.kt)
-
flatMapConcat:串行收集内部流(上游每来一个,先把前一个收完)。
-
flatMapMerge(concurrency):并发收集多个内部流,交织发射;concurrency 限并发个数。
-
flatMapLatest:切换最新 ,新上游值到来时取消旧的内部流。
-
combine:等待多源各自至少来一次,之后任一源更新就输出一次组合值。内部用数组保存最新值 + 活跃计数 + select/channel 协调。
3) 限制(Limit.kt)
-
take(n) / takeWhile 用 AbortFlowException 实现温和中断:
- 自己抛出一个内部可识别 的异常,外层算子(或框架)吞掉 它,不当作错误传播,但会取消上游协程,从而尽快停止生产。
4) 错误/完成(Catch.kt)
-
catch { e -> }:只捕上游 异常;对下游(emit 之后)的异常不干涉(异常透明性)。
-
onCompletion { cause -> }:无论正常/异常结束都会回调;cause == null 表示正常完成。
-
retry(retries) { cause -> }:在上游失败 时按策略重试。
5) 上下文(Context.kt)
- flowOn(context):只影响上游 ,本质是把上游搬到一个新协程里跑(见 ChannelFlow)。
- 常见写法:
scss
repository.flow()
.flowOn(Dispatchers.IO) // 上游 IO
.map { ... } // 下游仍在原上下文(比如 Main)
6) 背压(Buffer.kt)
- buffer(capacity, onBufferOverflow):在上/下游之间插入一个 Channel 缓冲,解除背压耦合。
- conflate():capacity=1 + 丢弃中间值保留最新(慢消费时自动跳帧)。
- collectLatest{}:消费体被新值打断(取消上一个 collect 的 block),常用于 UI 渲染。
四、基于 Channel 的实现(ChannelFlow.kt)
-
ChannelFlow / ChannelFlowOperator 是 flowOn、buffer、conflate 等的底层支柱:
- collect 时会启动一个生产协程,把上游的 emit 写入一个 Channel;
- 下游在当前协程里从这个 Channel 读;
- 这样实现线程切换 (flowOn)与缓冲(buffer)。
-
关键好处:
- 切线程只影响上游(满足 flow 语义);
- 背压可调(capacity/overflow),慢消费者不会阻塞上游生产。
五、热流(StateFlow/SharedFlow)
StateFlow.kt
- 粘性单值 ,保存最新值,新订阅者立刻收到当前值。
- 接口:
csharp
interface StateFlow<out T> : Flow<T> { val value: T }
class MutableStateFlow<T>(initial: T) : StateFlow<T> { var value: T }
-
特性:
-
始终有值;
-
value 写入是幂等去重(相同值不发射);
-
适合 UI 状态(Compose/Livedata 替代)。
-
SharedFlow.kt
- 多值广播,可配置 replay(回放 N 条历史)。
kotlin
interface SharedFlow<out T> : Flow<T> { val replayCache: List<T> }
class MutableSharedFlow<T>(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = SUSPEND
)
-
特性:
-
可无初始值;
-
多个订阅者共享一个生产源;
-
replay>0 时,新订阅者立刻收到最近 N 条历史;
-
不会自动完成(除非作用域取消)。
-
shareIn/stateIn(Share.kt)
-
把冷流 变成热流:
- shareIn(scope, started, replay) → SharedFlow
- stateIn(scope, started, initial) → StateFlow
-
started 策略:
- Eagerly:立刻启动收集;
- Lazily:有第一个订阅者时启动;
- WhileSubscribed(stopTimeout, replayExpiration):有订阅时运行,超时无订阅则停止。
-
常见用途:上游冷流昂贵时,避免每个收集者都触发一次上游(比如网络/数据库)。
六、回调桥接(channelFlow/callbackFlow)
- channelFlow {} :给你一个 ProducerScope(可 send/trySend),可以在内部启动多个子协程并发发射,生命周期和外部 collect 绑定。
- callbackFlow {} :封装回调式 API:
kotlin
callbackFlow {
val listener = object : XxxCallback {
override fun onEvent(v: V) { trySend(v).isSuccess }
}
api.addListener(listener)
awaitClose { api.removeListener(listener) } // 结束时清理
}
-
注意:
- awaitClose 必须调用清理资源;
- 不要阻塞 send;慢消费者时注意 buffer/conflate。
七、
flowOn
的真正工作方式
- 逻辑等价于:
scss
upstream --(producer in Dispatchers.IO)--> [Channel(capacity)] --(consumer in current ctx)--> downstream
- 因为 SafeCollector 禁止在 flow{} 里随意 withContext,flowOn 用生产-消费模型 实现线程切换 与背压,严格保持流语义。
八、异常语义与常见坑
- 异常透明性(超高频面试点)
scss
flow {
emit(1)
// do something
}.map { v ->
check(v > 1) // 这里抛出的异常,不会被上游的 catch 吞掉
v
}.catch { e ->
// 只能收到 "上游"(flow{} 或 其之上的 map/filter 等)抛出的异常
}
-
这靠 SafeCollector 标记与校验实现。
-
take(n) 的温和中断****
- 向上游抛 AbortFlowException,外层识别后吞掉,并取消上游。你在日志会看到异常,但不会向下游传播为错误。
-
withContext 误用
- 在 flow{} 里 withContext(Dispatchers.IO) 再 emit → 被 SafeCollector 拦截;
- 正确:在链上用 .flowOn(Dispatchers.IO)。
-
collectLatest vs conflate
- conflate:丢中间值,保留最新值;
- collectLatest:会取消上一个"收集体"块(适合 UI 渲染/图片加载)。
九、性能 & 设计建议
- 生产端尽量早 flowOn(Dispatchers.IO) ,避免在 Main 线程做 I/O。
- 慢消费者用 buffer(capacity) 或 conflate(),不要让生产阻塞。
- 复杂并发合流优先 flatMapMerge(concurrency);需要切最新就 flatMapLatest。
- UI 层首选 StateFlow 表示状态,SharedFlow 表示事件(replay=0)。
- 要把"昂贵冷流"热化复用 → shareIn/stateIn + 合理 started 策略。
- 桥接回调用 callbackFlow{} + awaitClose{};不要在 block 里阻塞。
十、几个关键实现片段(帮助你读源码)
flow {} → SafeFlow.collect
kotlin
class SafeFlow<T>(
private val block: suspend FlowCollector<T>.() -> Unit
) : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
val safe = SafeCollector(collector, coroutineContext = currentContext)
try {
safe.block() // 这里执行你的 emit
} finally {
safe.releaseIntercepted()
}
}
}
flowOn → ChannelFlow(示意)
kotlin
fun <T> Flow<T>.flowOn(ctx: CoroutineContext): Flow<T> =
ChannelFlowOperator(upstream = this, context = ctx)
// collect 时:
// launch(ctx) { upstream.collect { channel.send(it) } }
// current coroutine { for (v in channel) downstream.emit(v) }
take(n)(示意)
kotlin
var emitted = 0
upstream.collect { v ->
if (emitted++ < n) downstream.emit(v)
else throw AbortFlowException(this)
}
// 外层 catch AbortFlowException 并识别后吞掉
StateFlow 去重(示意)
kotlin
var value: T = initial
fun tryEmit(new: T) {
if (new != value) {
value = new
notifyCollectors(new)
}
}
十一、快速对照:StateFlow vs SharedFlow
特性 | StateFlow | SharedFlow |
---|---|---|
初始值 | 必须 | 可无 |
粘性 | 粘性(始终保存最新) | 由 replay 决定 |
去重 | 相等不发射 | 不去重(你自己处理) |
场景 | UI 状态、表单等 | 事件广播、总线、合并多个源 |