一、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 状态、表单等 事件广播、总线、合并多个源
相关推荐
yaoh.wang1 小时前
力扣(LeetCode) 111: 二叉树的最小深度 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·深度优先
沐雪架构师2 小时前
大模型Agent面试精选题(第六辑)-Agent工程实践
面试·职场和发展
Dolphin_海豚2 小时前
到底是选 merge 还是选 rebase
git·面试·程序员
不想秃头的程序员2 小时前
JS原型链详解
前端·面试
努力学算法的蒟蒻3 小时前
day42(12.23)——leetcode面试经典150
算法·leetcode·面试
不想秃头的程序员4 小时前
JS继承方式详解
前端·面试
摇滚侠5 小时前
面试实战 问题三十五 Spring bean 的自动装配 介绍一下熟悉的几种设计模式 Java 四种线程池是哪些
java·spring·面试
沐雪架构师5 小时前
大模型Agent面试精选题(第五辑)-Agent提示词工程
java·面试·职场和发展
踏浪无痕6 小时前
为什么 Spring Cloud Gateway 必须用 WebFlux?
后端·面试·架构
LYFlied7 小时前
前端工程化核心面试题与详解
前端·面试·工程化