一、Kotlin Flow源码结构

一、顶层接口

Flow

  • 语义:冷流,只有一个挂起函数:
kotlin 复制代码
interface Flow<out T> {
    suspend fun collect(collector: FlowCollector<T>)
}
  • 约束(非常重要):

    1. emit 串行、不可并发;

    2. 异常透明性 :下游运算只捕获上游 抛出的异常,不能吞掉下游自身的异常;

    3. 上下文保守 :除非使用 flowOn,否则生产与收集在同一协程上下文。

FlowCollector

  • 职责:下游的"接收端",只有一个挂起函数:
kotlin 复制代码
interface FlowCollector<in T> {
    suspend fun emit(value: T)
}
  • SafeCollector (后文详解)会包住真实的 collector,做上下文一致性异常透明性校验。

二、构建器与安全包装

Builders( flow {}flowOfasFlow )

  • 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 时做两件事:

    1. 上下文校验 :保证 emit 时使用的 CoroutineContext 与创建 SafeCollector 的上下文等价(尤其是 ContinuationInterceptor 一致)。否则抛 IllegalStateException(典型:在 flow { withContext(Dispatchers.IO) emit(x) } 被禁止------应改用 flowOn)。

    2. 异常透明性 :利用内置标记保证 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 用生产-消费模型 实现线程切换背压,严格保持流语义。

八、异常语义与常见坑

  1. 异常透明性(超高频面试点)
scss 复制代码
flow {
    emit(1)
    // do something
}.map { v ->
    check(v > 1)       // 这里抛出的异常,不会被上游的 catch 吞掉
    v
}.catch { e ->
    // 只能收到 "上游"(flow{} 或 其之上的 map/filter 等)抛出的异常
}
  1. 这靠 SafeCollector 标记与校验实现。

  2. take(n) 的温和中断****

    • 向上游抛 AbortFlowException,外层识别后吞掉,并取消上游。你在日志会看到异常,但不会向下游传播为错误。
  3. withContext 误用

  4. 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 状态、表单等 事件广播、总线、合并多个源
相关推荐
uhakadotcom1 小时前
NPM与NPX的区别是什么?
前端·面试·github
绝无仅有2 小时前
服务器Docker 安装和常用命令总结
后端·面试·github
王六岁3 小时前
JavaScript值和引用详解:从栈堆内存到面试实战
javascript·面试
Code_Artist3 小时前
[Java并发编程]3.同步锁的原理
java·后端·面试
天天摸鱼的java工程师4 小时前
如何实现数据实时同步到 ES?八年 Java 开发的实战方案(从业务到代码)
java·后端·面试
iccb10134 小时前
独立开发在线客服系统 5 年,终于稳如老狗了:记录我踩过的坑(一)
面试
Java水解4 小时前
Java开发实习超级详细八股文
java·后端·面试
似水流年流不尽思念4 小时前
描述一下 Spring Bean 的生命周期 ?
后端·面试
顾林海4 小时前
网络江湖的两大护法:TCP与UDP的爱恨情仇
网络协议·面试·性能优化