17、电源管理:WakeLock机制、Doze模式、电池信息上报
电源管理,说白了就是让设备在性能和续航之间找到平衡点。Android系统在这方面做了很多工作,但作为嵌入式开发者,我们得主动去配合它。今天聊聊三个核心话题:WakeLock、Doze模式,还有电池信息怎么上报。
核心要点:Android 6.0之后,电源管理策略变得非常激进。如果你的App不主动适配,很可能在后台被干掉,或者网络请求被延迟。这不是Bug,是特性。
17.1 WakeLock:别让系统睡过去
WakeLock是一种锁机制。它告诉系统:“嘿,我现在有事要做,别休眠。” 我刚开始做嵌入式开发时,总觉得这玩意儿很简单,直到有一次设备在播放音频时突然黑屏……嗯,从那以后我再也不敢小看WakeLock了。
17.1.1 WakeLock的类型
| 类型 | CPU | 屏幕 | 键盘灯 | 说明 |
|---|---|---|---|---|
| PARTIAL_WAKE_LOCK | 保持唤醒 | 可关闭 | 可关闭 | 最常用,后台任务专用 |
| SCREEN_DIM_WAKE_LOCK | 保持唤醒 | 变暗 | 可关闭 | 适合需要屏幕可见但不需交互的场景 |
| SCREEN_BRIGHT_WAKE_LOCK | 保持唤醒 | 保持亮 | 可关闭 | 视频播放等场景 |
| FULL_WAKE_LOCK | 保持唤醒 | 保持亮 | 保持亮 | 尽量少用,非常耗电 |
注意:从Android 9开始,应用在后台无法获取FULL_WAKE_LOCK。系统会直接忽略这个请求。我曾经有个项目因此踩坑,调试了两天才发现是权限被系统限制了。
17.1.2 正确使用WakeLock
获取WakeLock需要权限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
代码示例:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"MyApp:BackgroundTask"
);
// 获取锁
wl.acquire();
// 做你的后台工作
doHeavyWork();
// 释放锁——这一步千万别忘!
wl.release();
个人习惯:我一般会在acquire时设置超时时间,比如wl.acquire(30000)。这样即使代码异常没释放,30秒后系统也会自动释放。算是兜底策略。
17.1.3 WakeLock的避坑指南
- 成对出现:acquire和release必须成对调用。我曾经在项目中见过release被放在finally块里,但acquire却放在try外面——结果异常时锁没释放,设备一晚上没休眠。
- 不要滥用:能用JobScheduler或WorkManager,就别自己搞WakeLock。系统对这些组件有统一的电源管理策略。
- 注意进程死亡:如果进程被杀死,WakeLock会自动释放。但别指望这个,还是老老实实手动管理。
17.2 Doze模式:系统教你做人
Doze模式是Android 6.0引入的。说白了就是:设备静止且屏幕关闭一段时间后,系统进入深度休眠。这时候你的网络访问、同步任务都会被延迟。
为什么会这样?因为Google发现,很多App在后台偷偷干活,耗电严重。Doze模式就是用来管这些“不听话”的App的。
17.2.1 Doze模式的阶段
系统会分阶段收紧限制:
- 轻度Doze:屏幕关闭后30分钟进入。网络访问被限制,但闹钟等优先级高的任务还能执行。
- 深度Doze:设备长时间静止后进入。几乎所有的网络和同步都被延迟。
- 维护窗口:系统会定期给App一个短暂的“放风”时间,让你处理积压的任务。
17.2.2 如何适配Doze模式
我个人建议,优先使用系统提供的API,而不是试图绕过Doze:
- 使用WorkManager:它会自动适配Doze模式,你只需要设置约束条件。
- 使用高优先级FCM:如果必须实时推送,用Firebase Cloud Messaging的高优先级消息。
- 申请白名单:通过Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS让用户把你加入白名单。但注意,Google Play对滥用此权限的应用有严格限制。
// 检查是否在白名单中
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
我曾经踩过的坑:有个项目需要每隔5分钟上报GPS位置。在Doze模式下,AlarmManager设置的定时任务被延迟了最多15分钟。后来改用WorkManager加位置更新请求,才解决问题。记住:AlarmManager在Doze模式下不可靠。
17.3 电池信息上报:让系统知道你在干嘛
电池信息上报,说白了就是告诉系统:“我用了多少电,你看着办。” Android提供了BatteryManager和JobScheduler来帮你做这件事。
17.3.1 获取电池状态
BatteryManager bm = (BatteryManager) getSystemService(Context.BATTERY_SERVICE);
int level = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
int status = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS);
// level: 0-100 的百分比
// status: BatteryManager.BATTERY_STATUS_CHARGING 等
17.3.2 上报策略
我个人习惯,上报电池信息时遵循以下原则:
- 不要频繁上报:电池信息变化很慢,每5分钟上报一次就足够了。
- 只在充电时上报:如果设备在充电,说明用户不关心续航,你可以多上报一些数据。
- 使用JobScheduler:让系统来决定什么时候上报最合适。
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, serviceComponent)
.setRequiresCharging(true) // 只在充电时执行
.setPeriodic(15 * 60 * 1000) // 每15分钟
.build();
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);
17.4 知识体系总览
下面这张图,是我梳理的电源管理知识结构。你想想看,整个体系其实就三个层次:获取锁、适配休眠、上报信息。
17.5 实战建议
嗯,这里要注意。电源管理不是写几行代码就完事的。我建议你在开发阶段就做好以下事情:
- 用Battery Historian分析:这是Google提供的电池分析工具。把bugreport导进去,能清楚看到你的App在什么时间干了什么耗电的事。
- 在真机上测试:模拟器测不出真实的电源行为。我习惯用Nexus或Pixel设备,因为它们的电源管理最接近原生。
- 关注低电量模式:很多厂商(华为、小米、OPPO)有自己的省电策略。你的App在这些设备上可能表现不同。
一个小技巧:在开发阶段,可以用adb命令强制进入Doze模式来测试:adb shell dumpsys deviceidle force-idle。这样不用等30分钟,直接就能看到你的App在Doze下的表现。
电源管理这块,说白了就是和系统斗智斗勇。但只要你理解了它的设计思路——一切为了续航——很多问题就迎刃而解了。