
你有没有遇到过这样的情况:数据源发送得太快,你的应用根本处理不过来,结果要么卡顿要么崩溃?
针对这种情况,Kotlin Flow 提供了一些内置的方法,让你的生产者和消费者能步调一致。在这篇文章里,我们会聊聊:
- 背压(Backpressure)是什么意思?
- 默认的"等一等"模式是怎么工作的?
- 什么时候用
buffer()加个小队列? conflate()怎么跳过旧数据?collectLatest { }为什么会在新数据到来时停止旧任务?- 如何根据自己的情况选择合适的方案?
背压(Backpressure)是什么
背压 就是确保快速的数据发送方不会把慢速的接收方给压垮。要是没有这玩意儿,内存里可能会堆满未处理的数据,或者花时间处理已经过时的信息。
举个简单的例子。
我们知道,Android 屏幕的刷新率是一个固定值,60 fps,90 fps,好一点的收集 120 fps,想象一下我们写的 UI 从流那里收集数据,如果这个数据发送发每秒钟超过了 200 次,那么其实其中有好几十次是没用的,因为这些数据根本不会渲染在屏幕上。
实际上接收方的数据,只要保持 60 次/秒就很够用了。
此时,你就需要背压!
背压的好处有:
- 控制内存使用
- 避免做无用功
- 让应用性能更稳定
默认的"等一等"模式

默认情况下,当你这么写代码时:
Kotlin
suspend fun main() {
flow {
(1..3).forEach {
timeLog("Send $it")
emit(it)
delay(100) // 快速发送方
}
}.collect { value ->
timeLog("Processing $value")
delay(300) // 慢速处理方
}
}
// output:
// 20:22:19.647 Send 1
// 20:22:19.701 Processing 1
// 20:22:20.142 Send 2
// 20:22:20.143 Processing 2
// 20:22:20.564 Send 3
// 20:22:20.564 Processing 3
发送方 (emit)会暂停,直到 处理方 (collect)处理完上一个值。这里没有队列,每个值都是一次发送、一次处理。
buffer:加个小队列

如果你想让发送方稍微提前一点,可以这样:
Kotlin
flow {
//...
}
.buffer(capacity = 2)
.collect { value ->
//...
}
// output:
// 20:27:03.368 Send 1
// 20:27:03.418 Processing 1
// 20:27:03.529 Send 2
// 20:27:03.639 Send 3
// 20:27:03.732 Processing 2
// 20:27:04.470 Processing 3
整体时间变快了!
- 现在发送方最多可以把
2个元素放入一个小队列中。 - 一旦队列满了,它就会再次暂停。
这就给了你一个 有限队列:你仍然会处理每一个元素,但可以平滑处理速度上的突发波动。
conflate:跳过旧数据

当你只关心最新数据时(比如更新进度条),可以这样写:
Kotlin
flow { ... }
.conflate()
.collect { value -> ... }
// output:
// 20:30:45.745 Send 1
// 20:30:45.813 Processing 1
// 20:30:45.930 Send 2
// 20:30:46.039 Send 3
// 20:30:46.133 Processing 3
- 如果处理方正忙,只有 最新的 未处理项会被保留。
- 旧的项会被丢弃,所以你永远不会处理过时的更新。
注意 :
conflate()不会停止当前正在进行的工作;它只是在下一次读取时跳过旧值。
collectLatest:取消旧任务

如果你希望在新数据到来时立即取消正在进行的任务,可以这样:
Kotlin
suspend fun main() {
flow {
(1..3).forEach {
timeLog("Send $it")
emit(it)
delay(100)
}
}.collectLatest { value ->
timeLog("Start process $value")
delay(300)
timeLog("Complete process $value")
}
}
// output:
// 09:31:32.919 Send 1
// 09:31:32.979 Start process 1
// 09:31:33.090 Send 2
// 09:31:33.093 Start process 2
// 09:31:33.195 Send 3
// 09:31:33.195 Start process 3
// 09:31:33.498 Complete process 3
你会发现,只有 3 完成了任务。
- 每当有新的
emit发生时,正在处理前一个值的代码块会立刻被丢弃。 - 只有当发送方停止发送后,你才会完成对 最后一个 值的处理。
这种模式非常适合 "边输入边搜索" 的场景------用户继续输入时,你可以立即放弃之前的搜索请求。
抉择指南
collect
作用 :发送方和处理方逐个等待,一一对应
适用场景 :你需要按顺序处理 每一个 值buffer
作用 :创建一个容量为n的小队列;不会丢弃任何元素
适用场景:你想有一点缓冲能力,但仍需处理所有元素conflate
作用 :如果处理方正忙,只保留 最新的 元素
适用场景:你需要最新数据,但仍希望完成当前正在处理的任务collectLatest
作用 :新数据一到就立即取消正在进行的任务
适用场景 :只有 最新结果 有意义;其他一切都可以立刻丢弃
下次当你觉得 Flow 太快或太慢时,问问自己:
- 我是否需要处理每一个值?
- 一个小队列会有帮助吗?
- 是否只有最新数据才重要?
- 新数据到来时,我是否应该取消旧任务?
选择最简单的、能满足需求的选项,Kotlin Flow 会替你处理其余的一切。