19、传感器功耗优化:传感器功耗模型、按需注册策略、采样率动态调整、Doze模式适配
做传感器开发,最头疼的问题是什么?
我个人觉得,不是数据不准,而是——电跑得太快了。
你想想看,一个加速度传感器,如果一直开着,一天下来能把手机电量吃掉 15% 以上。我早期做过一个运动追踪项目,上线后用户反馈「手机半天就没电了」,查了半天才发现是传感器没做功耗优化。嗯,从那以后,我把功耗优化列为了传感器开发的必修课。
19.1 传感器功耗模型:先搞清楚电都去哪了
要优化功耗,首先得知道功耗从哪来。说白了,传感器耗电主要分三块:
- 硬件本身功耗:芯片工作就要耗电,不同传感器差异很大。比如计步器芯片功耗极低,而陀螺仪就相对费电。
- 数据采样功耗:采样率越高,CPU/总线唤醒越频繁,功耗直线上升。采样率翻倍,功耗往往不止翻倍。
- 应用层处理功耗:数据回调后,你的代码要做计算、写日志、发网络请求——这些才是真正的耗电大户。
我在项目中遇到过一种情况:明明传感器本身功耗很低,但每次回调都触发了一次网络上传,结果电量哗哗地掉。这就是典型的「传感器背锅,应用层才是元凶」。
核心结论:传感器功耗优化,70% 的收益来自减少不必要的处理逻辑,30% 来自降低采样率。
下面这张图,是我自己总结的传感器功耗模型,帮你快速定位问题出在哪一层:
19.2 按需注册策略:别让传感器一直开着
很多新手容易犯一个错误:在 onCreate 里注册传感器,在 onDestroy 里注销。看起来没问题,但实际场景中,Activity 可能只是被遮挡了,用户并没有离开——这时候传感器还在跑,白白耗电。
我个人的习惯是:只在需要的时候注册,不需要的时候立刻注销。具体来说:
- Activity 可见时:在
onResume注册,onPause注销。用户切到后台或锁屏,传感器立即停止。 - 特定场景触发:比如只有用户点击「开始计步」按钮后才注册加速度传感器,点击「停止」后立即注销。
- 使用
SensorManager.unregisterListener():记得传 null 参数,一次性注销所有监听器,避免遗漏。
小技巧:如果你需要频繁开关传感器,可以考虑用 Handler 做延迟注销。比如用户停止操作后 3 秒再注销,避免频繁注册/注销带来的额外开销。
我曾经在一个导航项目里,因为没在 onPause 里注销陀螺仪,导致用户接电话时传感器还在工作,电量直接崩了。后来加了一行代码,问题就解决了——有时候优化就是这么简单。
19.3 采样率动态调整:能低就别高
采样率是功耗的「放大器」。你想想看,SENSOR_DELAY_NORMAL(约 200ms 一次)和 SENSOR_DELAY_FASTEST(可能 5ms 一次),功耗差距是几十倍。
但问题来了:有些场景确实需要高采样率,比如游戏中的手势识别。怎么办?
我的做法是:动态调整采样率。
| 场景 | 推荐采样率 | 说明 |
|---|---|---|
| 屏幕关闭 / 后台 | SENSOR_DELAY_NORMAL |
最低功耗,只做粗略监测 |
| 普通 UI 交互 | SENSOR_DELAY_UI |
约 60ms 一次,够用 |
| 游戏 / 手势识别 | SENSOR_DELAY_GAME |
约 20ms,平衡性能与功耗 |
| 高精度运动追踪 | SENSOR_DELAY_FASTEST |
慎用,仅在必要时开启 |
代码实现也很简单:
// 根据场景动态调整采样率
public void adjustSamplingRate(String scene) {
SensorManager manager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
int samplingRateUs;
switch (scene) {
case "background":
samplingRateUs = SensorManager.SENSOR_DELAY_NORMAL; // 200ms
break;
case "ui_interaction":
samplingRateUs = SensorManager.SENSOR_DELAY_UI; // 60ms
break;
case "game":
samplingRateUs = SensorManager.SENSOR_DELAY_GAME; // 20ms
break;
case "high_precision":
samplingRateUs = SensorManager.SENSOR_DELAY_FASTEST; // 最快
break;
default:
samplingRateUs = SensorManager.SENSOR_DELAY_NORMAL;
}
// 重新注册监听器,更新采样率
manager.unregisterListener(myListener);
manager.registerListener(myListener, sensor, samplingRateUs);
}
注意:频繁调用 registerListener 和 unregisterListener 本身也有开销。建议在场景切换时调整,而不是每帧都调。
19.4 Doze模式适配:别在系统休眠时捣乱
Android 6.0 引入了 Doze 模式,说白了就是手机长时间不动时,系统会进入深度休眠,限制网络和传感器访问。如果你的 App 在 Doze 模式下还频繁请求传感器数据,系统会直接忽略你的请求——甚至可能把你列入「耗电应用」黑名单。
适配 Doze 模式,核心原则就一句话:尊重系统的休眠策略。
具体怎么做?我总结了三点:
- 使用
JobScheduler替代传感器轮询:需要定时获取传感器数据时,不要自己开循环,交给 JobScheduler 调度。系统会在合适的时机(比如充电时、网络空闲时)执行你的任务。 - 申请
WAKE_LOCK要谨慎:如果你确实需要在 Doze 模式下保持传感器工作(比如计步器),可以申请 PARTIAL_WAKE_LOCK。但记得用完就释放,否则会被系统强制杀掉。 - 利用
setAlarmClock()绕过 Doze:对于闹钟类应用,可以使用 AlarmManager 的setAlarmClock()方法,系统会保证在指定时间唤醒设备。但别滥用——这招相当于「强行叫醒手机」,很费电。
避坑指南:我曾经在 Doze 模式下用 Handler.postDelayed() 做传感器轮询,结果发现手机休眠后,回调完全收不到。后来改用 JobScheduler 配合 setRequiredNetworkType(),才解决了问题。记住:Doze 模式下,定时器不可靠。
最后,别忘了在 AndroidManifest.xml 中声明 REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 权限。但说实话,我不建议你轻易申请这个权限——用户看到「允许忽略电池优化」的弹窗,第一反应往往是「这 App 肯定费电」,然后直接拒绝。
嗯,传感器功耗优化,说白了就是「该省省,该花花」。硬件层我们改不了,但采样层和应用层,我们完全可以做到极致。下次你的 App 被用户吐槽「太费电」时,不妨先从这三个方向排查一下。