第2章:collect耗电的根本原因:CPU唤醒次数、线程保持、传感器/网络持续监听
Flow 本身只是一种数据订阅范式,电量开销的根源在于不恰当的收集策略迫使系统硬件模块长时间无法进入深度休眠。本章从底层原理拆解三大核心耗电路径,并提供可量化定位的方法。
2.1 根源一:CPU唤醒次数过高
每次 Flow 发射数据时,如果处于空闲态的 CPU 正在深度休眠(C3/C4),硬件必须接受中断并切换回运行状态 C0 来处理数据。高频发射意味着短时间内反复经历 休眠→唤醒→处理→休眠 循环。
- 原理链路:
data emit→内核 wakelock 获取→CPU 从 C4 切换至 C0→协程调度并执行 collect→释放 wakelock→CPU 重新进入 C4。 - 量化对比:一次完整唤醒耗时约 100‑200 μs,瞬时电流可达 30‑50 mA,而深度休眠电流仅 0.1‑0.5 mA。若 1 秒内唤醒 10 次,平均功耗将增加数十倍。
- 典型诱因:在
Dispatchers.Default上发射的细粒度状态流(如滚动坐标),被 UI 层直接收集并每次触发完整布局。
2.2 根源二:线程长期保持
collect 所在的协程会一直占用调度线程,直到 Flow 结束。如果收集逻辑中有阻塞操作(如文件写入、数据库事务),该线程将长期处于活跃状态,导致线程池无法缩容,CPU 无法降频。
- 反面案例:将高耗时 JSON 解析直接放在
collect中,即使使用flowOn(IO),解析期间线程仍处于 Busy 态,阻碍 CPU 进入 C2+ 深度休眠。 - 关键区分:
flowOn只改变上游发射所在的上下文,collect默认仍运行在收集启动时的调度器。若未显式指定,可能意外占用主线程或共享线程池。
2.3 根源三:传感器/网络持续监听
传感器采样率直接决定了 Flow 的发射频率。例如 50 ms 上报一次的加速度计,如果收集端未做缓冲或限频,驱动层会因缓冲区满而持续产生中断并唤醒 CPU。
| 传感器类型 | 高频采样(50 ms) | 低频采样(1 s) | CPU 唤醒次数 / min |
|---|---|---|---|
| 加速度计 | 1200 次 | 60 次 | 高频多 20 倍 |
| GPS | 持续锁定 | 间隔上报 | GPS 芯片常开 |
| 网络轮询 | 长连接心跳 | 定时拉取 | 每次都会唤醒 RIL 层 |
callbackFlow 与 channelFlow 未正确设置 BufferOverflow 策略时,会导致数据堆积在通道中,底层传感器/网络驱动因反压不断重试上报,形成恶性循环。
调试与定位工具
使用
使用
adb shell dumpsys battery_stats 或 Battery Historian 查看 Kernel Wakeup 与 Sensor Power 明细;在 Profiler 中观察 CPU Core States 时间占比,若 C0 时间异常增加且与 Flow 发射时间吻合,即可定位问题。
2.4 代码反面教材(高耗电模式)
// 传感器高频发射 + 直接收集 = 持续唤醒
val sensorFlow = callbackFlow<SensorEvent> {
registerListener(Sensor.TYPE_ACCELEROMETER, ... ) // 50ms 上报一次
awaitClose { unregisterListener() }
}
// ViewModel 中直接收集,未做限频或缓冲区
viewModelScope.launch(Dispatchers.Default) {
sensorFlow.collect { event ->
// 每次回调都触发 CPU 唤醒
// 且 1200 次/分钟的发射导致线程始终保持活跃
}
}
2.5 性能优化与避坑
不可滥用 conflate
conflate 会丢弃中间值,若用于必须处理每个事件的场景(如传感器校准数据、支付状态),将导致数据丢失而产生逻辑错误。它的省电效益是以牺牲可靠性为代价的。
推荐方案:buffer + flowOn
使用
使用
.buffer(Channel.CONFLATED) 或 .buffer(64, BufferOverflow.DROP_OLDEST) 将高频发射与低频收集解耦,并配合 .flowOn(Dispatchers.IO) 将发射侧迁移到专用线程,减少对主线程或 Default 线程池的占用。
- 非关键流:使用
sample(定期采样)或debounce(防抖)减少无用发射。 - 生命周期感知:前台才需要的更新使用
flowWithLifecycle,应用切后台后自动取消收集。 - 批量聚合:多次发射合并为一次处理,降低 CPU 被唤醒的频次。