三分钟搞懂 Kotlin Flow 中的背压

你有没有遇到过这样的情况:数据源发送得太快,你的应用根本处理不过来,结果要么卡顿要么崩溃?

针对这种情况,Kotlin Flow 提供了一些内置的方法,让你的生产者和消费者能步调一致。在这篇文章里,我们会聊聊:

  1. 背压(Backpressure)是什么意思?
  2. 默认的"等一等"模式是怎么工作的?
  3. 什么时候用 buffer() 加个小队列?
  4. conflate() 怎么跳过旧数据?
  5. collectLatest { } 为什么会在新数据到来时停止旧任务?
  6. 如何根据自己的情况选择合适的方案?

背压(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 太快或太慢时,问问自己:

  1. 我是否需要处理每一个值?
  2. 一个小队列会有帮助吗?
  3. 是否只有最新数据才重要?
  4. 新数据到来时,我是否应该取消旧任务?

选择最简单的、能满足需求的选项,Kotlin Flow 会替你处理其余的一切。

相关推荐
XD74297163626 分钟前
科技早报晚报|2026年5月15日:无摄像头空间感知、Android 设备实验室与视频检索代理,今天更值得跟进的 3 个技术机会
android·科技·音视频·开源项目·边缘ai·开发者工具
应用市场38 分钟前
Android Verified Boot 2.0 安全启动原理详解
android·安全
只可远观39 分钟前
Android XML命令式和Jetpack Compose声明式UI
android·xml
他是龙5511 小时前
DVWA 靶场深度解析:文件包含 & 文件上传(Low → Impossible)
android
_李小白1 小时前
【Android车载学习笔记】第一天:Android Automotive OS介绍
android·笔记
aqi001 小时前
FFmpeg开发笔记(一百零一)跨平台的开源音视频移动框架MobileFFmpeg
android·ffmpeg·音视频·直播·流媒体
2301_811130542 小时前
【保姆级教程】Android Studio完整安装步骤(2026最新版,新手零踩坑)
android·java
帅次2 小时前
Android 高级工程师面试参考答案:项目经历、自我介绍与实战案例表达
android·面试·职场和发展
小猫爱游戏2 小时前
theone陪伴ai手机版免费版下载安装教程附带最新邀请码theone陪伴ai设定教程接入下载使用教程手机版安卓版app鸿蒙版苹果版IOS电脑版安装包下载地址
android·人工智能·智能手机·theone陪伴ai·theone陪伴ai下载·免费下载安装·接入微信教程