14、电量优化:Battery Historian工具、JobScheduler与WorkManager、AlarmManager优化、传感器管理

电量优化,说白了就是让App在用户不知不觉中把活干了,别偷偷摸摸吃电。我见过太多App,功能做得挺花哨,结果用户晚上充完电,早上起来发现掉了20%——这种App基本活不过两周就被卸载了。

今天咱们聊聊Android电量优化的四个核心方向。嗯,都是我在实际项目中踩过坑、填过坑的东西。

14.1 Battery Historian:先诊断,再下药

优化电量之前,你得先知道电都去哪了。Battery Historian就是干这个的。

我个人习惯,每次接手一个新项目,第一件事就是跑一遍Battery Historian。看看系统怎么说。

怎么用?

  1. 手机连电脑,断开充电器
  2. 重置电池数据:adb shell dumpsys batterystats --reset
  3. 正常使用App几分钟(或者放后台跑一晚上)
  4. 导出数据:adb bugreport bugreport.zip
  5. 上传到 https://bathistorian.appspot.com/ 或本地Docker部署

你会看到一张时间轴图,上面标着各种系统事件:

  • Top App:哪个App在前台
  • JobScheduler:后台任务执行时间
  • Alarm:闹钟唤醒次数
  • Sensor:传感器活跃时段
  • Wakelock:持锁时长

关键指标:看「Wakeup Reason」那一栏。如果某个App每30秒就唤醒一次系统,那基本可以断定它在乱用AlarmManager。

我的经验:Battery Historian的图表里,最怕看到密密麻麻的红色竖线。那代表频繁的Alarm唤醒。我曾经优化过一个项目,把Alarm频率从每分钟一次降到每15分钟一次,续航直接提升了30%。

14.2 JobScheduler vs WorkManager:后台任务怎么选?

很多开发者还在用Service做后台任务。说实话,这年头还这么干,基本等于在用户的电池上开了一个洞。

Android从5.0开始推荐用JobScheduler,后来Google又推出了WorkManager。怎么选?我直接给结论:

场景 推荐方案 原因
需要精确时间执行 AlarmManager(谨慎) JobScheduler不保证精确时间
网络变化后同步数据 WorkManager 自带约束条件,省电省心
定期上传日志 WorkManager + PeriodicWorkRequest 系统会批量合并任务
需要兼容低版本 WorkManager 底层自动适配JobScheduler或AlarmManager

WorkManager的正确姿势

// 定义任务
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 在这里做同步操作
        return Result.success()
    }
}

// 设置约束条件
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)  // 只在有网时执行
    .setRequiresBatteryNotLow(true)                 // 电量不低时执行
    .build()

val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueue(syncRequest)

避坑指南:我曾经见过一个团队,用WorkManager每5分钟执行一次任务,结果因为约束条件设置不当,导致手机在飞行模式下也疯狂尝试联网。嗯,那款App的耗电量直接排到了用户手机的第一名。

14.3 AlarmManager优化:别做那个「闹钟狂魔」

AlarmManager是电量杀手排行榜的常客。你想想看,每次Alarm触发,系统都要从深度睡眠中醒来,CPU要启动,屏幕可能亮起——这一套下来,耗电可不是闹着玩的。

优化原则

  • 能合并就合并:多个Alarm尽量对齐到同一个时间点触发
  • 能用setInexactRepeating就别用setRepeating:系统可以帮你合并唤醒
  • 能用setAndAllowWhileIdle就别用setExact:除非你真的需要精确到秒
  • 能用JobScheduler就别用AlarmManager:后台同步类任务,WorkManager更合适
// 错误示范:精确重复闹钟,每5分钟唤醒一次
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, 5 * 60 * 1000, pendingIntent)

// 正确示范:不精确重复,允许系统合并
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, 
    AlarmManager.INTERVAL_FIFTEEN_MINUTES, pendingIntent)

核心原则:能用setInexactRepeating就别用setRepeating,能用set就别用setExact。每少一次精确唤醒,用户的电池就能多撑一会儿。

14.4 传感器管理:别让陀螺仪一直转

传感器这块,我见过最离谱的案例:一个天气App,后台一直开着GPS定位,就为了「实时更新当前位置的天气」。结果用户发现手机半天就没电了。

传感器耗电的真相:

  • GPS:最耗电,每秒耗电约50mA
  • 加速度计:中等,每秒约5mA
  • 陀螺仪:中等,每秒约8mA
  • 光线传感器:低功耗,约0.5mA

优化策略

  1. 用完就注销:在onPause或onStop中调用unregisterListener()
  2. 降低采样频率:不是所有场景都需要SENSOR_DELAY_FASTEST
  3. 用被动定位代替GPS:能走Network定位就别开GPS
  4. 批量处理传感器数据:用SensorManager.registerListener()的第三个参数设置延迟
// 错误示范:最高频率采样,还忘了注销
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_FASTEST)

// 正确示范:按需设置采样频率,及时注销
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)

override fun onPause() {
    super.onPause()
    sensorManager.unregisterListener(this)
}

我的习惯:在开发阶段,我会在Logcat里打一个传感器注册/注销的日志。如果发现某个页面退出后传感器还在跑,那基本就是bug。我曾经用这个方法抓出过三个「幽灵传感器」问题。

14.5 知识体系总览

下面这张图,是我自己总结的电量优化知识框架。你照着这个思路走,基本不会漏掉关键点。

电量优化 Battery Historian • 导出bugreport • 分析Wakeup Reason • 定位高频唤醒源 JobScheduler/WorkManager • 设置约束条件 • 批量合并任务 • 避免精确时间要求 AlarmManager • 使用setInexactRepeating • 合并Alarm时间点 • 避免setExact滥用 传感器管理 • 用完即注销 • 降低采样频率 • 被动定位代替GPS

电量优化没有银弹。你得先拿Battery Historian诊断,再针对性地优化后台任务、闹钟和传感器。说白了,就是让App在用户看不见的地方,少干活、干巧活。

最后提醒一句:别为了省电把功能砍没了。用户要的是「省电且好用」,不是「省电但啥也干不了」。平衡,才是优化的艺术。


公众号:蓝海资料掘金营,微信deep3321