它到底干啥?
- 核心:当下游很慢时,**跳过中间值,只保留“最新值”**再交给下游处理。
- 本质实现:等价于在这里放了一个 Channel.CONFLATED 的缓冲边界(近似 buffer(capacity = Channel.CONFLATED))。
- 效果:上游不会被下游速度拖慢;但会丢中间值(非等价过滤,是直接替换)。
upstream (快) --[ 只存1个最新值 | 覆盖旧值 ]--> downstream (慢)
何时用 / 何时不用
✅ 适合
-
状态类数据:只关心“此刻状态”,不在乎中间过渡(如进度、位置、UI 状态)。
-
高频轻量事件:想解耦上下游,让上游跑满速,下游慢慢“追最新”。
❌ 不适合
-
每个事件都必须处理的场景(支付点击、打点、可靠消息)。这些用 buffer(SUSPEND) 或持久队列。
与其它操作符怎么区分?
-
buffer(1, DROP_OLDEST) :语义非常接近(保最新、丢最老)。conflate()可视作其语义化简写;CONFLATED 在实现上更“替换式”,一般更省事。
-
collectLatest { ... } / mapLatest { ... } :会取消前一个“下游处理”。适合重计算/可中断的耗时任务(如搜索联想渲染)。
conflate()不取消下游当前处理,只是跳过中间输入。
-
distinctUntilChanged() :按相等性去重;conflate()不比较相等性,只是因背压跳过中间值。二者可搭配:conflate().distinctUntilChanged()。
-
debounce()/sample() :按时间取样;conflate()是按背压合并,无时间窗口概念。
放置位置(非常关键)
- 把 conflate() 放在慢步骤之前,让慢步骤只处理“最新值”:
source
.conflate() // ← 切断背压、合并中间值
.map { slow(it) }
.collect { render(it) }
- 若慢步骤在 conflate() 之前:
source
.map { slow(it) } // ← 这里已经慢了,conflate 放后面救不了它
.conflate()
.collect { render(it) }
此时上游慢步骤仍会对每个值运行,达不到“省工”的目的。
若你希望来新值就中断慢计算,应改用 mapLatest/flatMapLatest/collectLatest。
与flowOn的关系
- flowOn 会引入一个缓冲边界;conflate()再引入一个**“只留最新”**的边界。
- 组合示例(IO 侧狂喷、UI 侧只拿最新):
source
.map { ioHeavy(it) }
.flowOn(Dispatchers.IO) // 上下文切换处已有缓冲
.conflate() // 再次压扁,只给下游“最新”
.collect { uiRender(it) }
典型模式
- 只关心最新状态(UI 渲染)
viewModel.state // 频繁 setState
.conflate()
.collect { render(it) } // 渲染总是“最新屏幕状态”
- 进度/位置流
download.progressFlow() // 0..100 高频
.conflate()
.collect { updateProgressBar(it) }
- 配合去重防抖
textChanges() // 高频输入
.debounce(250) // 时间降噪
.distinctUntilChanged() // 相同文本不触发
.flatMapLatest { query -> // 新查询取消旧查询
repo.search(query)
}
.conflate() // 后续 UI 只取最新结果帧
.collect { showResult(it) }
与 StateFlow / SharedFlow
-
StateFlow天生是“最新值”容器,更新本就具备合并语义;对它再 conflate()通常收益不大/冗余。
-
SharedFlow可通过 replay=0/1 + extraBufferCapacity + onBufferOverflow=DROP_OLDEST 配成“只留最新”的效果;此时再 conflate()也多半冗余,除非你刻意再加一层边界。
它会“乱序”吗?
-
conflate()不会打乱保留下来的元素的相对顺序,但会跳过中间若干个,所以表现像“突然跳到最新”。
小对比示例
1. conflate:不取消下游,只跳过中间输入
(1..100).asFlow()
.onEach { delay(10) } // 上游很快
.conflate()
.collect { value ->
delay(100) // 下游很慢
println("got $value") // 打印数目 << 100,且是“跳号”的
}
2. collectLatest:直接取消上一个处理
(1..100).asFlow()
.onEach { delay(10) }
.collectLatest { value ->
delay(100) // 来新值就取消旧的 delay
println("only latest finishes: $value")
}
易坑与建议
-
易坑:业务需要“每条必达/必处理”时误用 conflate() → 丢事件。
-
建议容量:conflate()无需你设置容量;若你需要“保最新 + 再多留几条”,用
buffer(n, onBufferOverflow = DROP_OLDEST)(如 n=8/16)更灵活。
-
重计算省不下来? 把 conflate()放到重计算之前,或改用 mapLatest 实现“来新就中断旧”。
一句话总结
conflate() = “只给下游最新值”的背压解法:不取消下游处理、跳过中间输入;适合状态/进度/UI,不适合必须逐条处理的事件流。