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模式的阶段

系统会分阶段收紧限制:

  1. 轻度Doze:屏幕关闭后30分钟进入。网络访问被限制,但闹钟等优先级高的任务还能执行。
  2. 深度Doze:设备长时间静止后进入。几乎所有的网络和同步都被延迟。
  3. 维护窗口:系统会定期给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 知识体系总览

下面这张图,是我梳理的电源管理知识结构。你想想看,整个体系其实就三个层次:获取锁、适配休眠、上报信息。

Android电源管理知识体系 WakeLock机制 Doze模式 电池信息上报 PARTIAL SCREEN_DIM SCREEN_BRIGHT FULL 轻度Doze 深度Doze 维护窗口 白名单 BatteryManager JobScheduler WorkManager FCM推送 核心原则:能不拿锁就不拿,能晚点做就晚点做,能交给系统就交给系统

17.5 实战建议

嗯,这里要注意。电源管理不是写几行代码就完事的。我建议你在开发阶段就做好以下事情:

  • 用Battery Historian分析:这是Google提供的电池分析工具。把bugreport导进去,能清楚看到你的App在什么时间干了什么耗电的事。
  • 在真机上测试:模拟器测不出真实的电源行为。我习惯用Nexus或Pixel设备,因为它们的电源管理最接近原生。
  • 关注低电量模式:很多厂商(华为、小米、OPPO)有自己的省电策略。你的App在这些设备上可能表现不同。

一个小技巧:在开发阶段,可以用adb命令强制进入Doze模式来测试:adb shell dumpsys deviceidle force-idle。这样不用等30分钟,直接就能看到你的App在Doze下的表现。

电源管理这块,说白了就是和系统斗智斗勇。但只要你理解了它的设计思路——一切为了续航——很多问题就迎刃而解了。


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