19、传感器功耗优化:传感器功耗模型、按需注册策略、采样率动态调整、Doze模式适配

做传感器开发,最头疼的问题是什么?

我个人觉得,不是数据不准,而是——电跑得太快了。

你想想看,一个加速度传感器,如果一直开着,一天下来能把手机电量吃掉 15% 以上。我早期做过一个运动追踪项目,上线后用户反馈「手机半天就没电了」,查了半天才发现是传感器没做功耗优化。嗯,从那以后,我把功耗优化列为了传感器开发的必修课。

19.1 传感器功耗模型:先搞清楚电都去哪了

要优化功耗,首先得知道功耗从哪来。说白了,传感器耗电主要分三块:

  • 硬件本身功耗:芯片工作就要耗电,不同传感器差异很大。比如计步器芯片功耗极低,而陀螺仪就相对费电。
  • 数据采样功耗:采样率越高,CPU/总线唤醒越频繁,功耗直线上升。采样率翻倍,功耗往往不止翻倍。
  • 应用层处理功耗:数据回调后,你的代码要做计算、写日志、发网络请求——这些才是真正的耗电大户。

我在项目中遇到过一种情况:明明传感器本身功耗很低,但每次回调都触发了一次网络上传,结果电量哗哗地掉。这就是典型的「传感器背锅,应用层才是元凶」。

核心结论:传感器功耗优化,70% 的收益来自减少不必要的处理逻辑,30% 来自降低采样率。

下面这张图,是我自己总结的传感器功耗模型,帮你快速定位问题出在哪一层:

传感器功耗模型 硬件层 芯片功耗 待机/工作电流 采样层 采样率 CPU唤醒频率 应用层 数据处理 网络/IO操作 典型功耗占比(经验值) 硬件层 15% 采样层 25% 应用层 60% 优化重点:应用层 > 采样层 > 硬件层

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);
}

注意:频繁调用 registerListenerunregisterListener 本身也有开销。建议在场景切换时调整,而不是每帧都调。

19.4 Doze模式适配:别在系统休眠时捣乱

Android 6.0 引入了 Doze 模式,说白了就是手机长时间不动时,系统会进入深度休眠,限制网络和传感器访问。如果你的 App 在 Doze 模式下还频繁请求传感器数据,系统会直接忽略你的请求——甚至可能把你列入「耗电应用」黑名单。

适配 Doze 模式,核心原则就一句话:尊重系统的休眠策略

具体怎么做?我总结了三点:

  1. 使用 JobScheduler 替代传感器轮询:需要定时获取传感器数据时,不要自己开循环,交给 JobScheduler 调度。系统会在合适的时机(比如充电时、网络空闲时)执行你的任务。
  2. 申请 WAKE_LOCK 要谨慎:如果你确实需要在 Doze 模式下保持传感器工作(比如计步器),可以申请 PARTIAL_WAKE_LOCK。但记得用完就释放,否则会被系统强制杀掉。
  3. 利用 setAlarmClock() 绕过 Doze:对于闹钟类应用,可以使用 AlarmManager 的 setAlarmClock() 方法,系统会保证在指定时间唤醒设备。但别滥用——这招相当于「强行叫醒手机」,很费电。

避坑指南:我曾经在 Doze 模式下用 Handler.postDelayed() 做传感器轮询,结果发现手机休眠后,回调完全收不到。后来改用 JobScheduler 配合 setRequiredNetworkType(),才解决了问题。记住:Doze 模式下,定时器不可靠

最后,别忘了在 AndroidManifest.xml 中声明 REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 权限。但说实话,我不建议你轻易申请这个权限——用户看到「允许忽略电池优化」的弹窗,第一反应往往是「这 App 肯定费电」,然后直接拒绝。

嗯,传感器功耗优化,说白了就是「该省省,该花花」。硬件层我们改不了,但采样层和应用层,我们完全可以做到极致。下次你的 App 被用户吐槽「太费电」时,不妨先从这三个方向排查一下。