第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 被唤醒的频次。
Flow collect 耗电的三大根源 电池电量消耗 ⚡ CPU 唤醒次数过高 线程长期保持活跃 传感器/网络持续监听 内核频繁唤醒 线程池无法缩容 驱动持续上报 根本衡量指标:CPU 在 C0(运行态)的停留时间 / CPU 被唤醒的次数 优化方向就是减少不必要的 state → event 转换,让系统在无数据时能稳定待在深度休眠中。