17、功耗管理:Suspend/Resume、Wakelock、Doze 模式、功耗调试工具

功耗管理,说白了就是让设备在不干活的时候尽量少耗电,在需要干活的时候能快速醒过来。我做了这么多年嵌入式Android,见过太多因为功耗问题导致产品翻车的案例。有一次客户反馈设备待机一晚上掉电30%,查到最后发现是一个外设的驱动没做suspend/resume,整晚都在轮询。嗯,这种坑踩过一次就再也不会忘了。

17.1 Suspend/Resume 机制:系统的睡眠与唤醒

Suspend/Resume 是Linux内核最基础的电源管理机制。Android设备按下电源键灭屏后,系统并不会完全关机,而是进入某种低功耗状态。

内核支持的几种睡眠状态:

状态 描述 功耗 唤醒延迟
Suspend-to-Idle 仅CPU空闲,外设保持供电 中等 极短
Standby (Power-on Suspend) CPU暂停,内存自刷新 较低
Suspend-to-RAM (STR) CPU掉电,内存保持 很低 中等
Suspend-to-Disk (Hibernate) 系统镜像写入磁盘,完全断电

Android设备通常使用 Suspend-to-RAM。流程是这样的:

  1. 用户空间通过 /sys/power/state 写入 "mem"
  2. 内核冻结用户态进程和内核线程
  3. 逐个调用设备的 suspend 回调函数
  4. 关闭非唤醒源的中断
  5. CPU 进入深度睡眠

唤醒时反向操作:唤醒源(比如电源键、RTC闹钟)触发中断,CPU恢复执行,设备 resume 回调被调用,系统恢复运行。

关键点:每个设备驱动都必须正确实现 suspend/resume 回调。我曾经遇到过触摸屏驱动在suspend时没有关闭中断,导致设备刚睡下就被触摸中断唤醒,一晚上反复睡眠唤醒几百次,电池直接崩了。

17.2 Wakelock:谁在阻止系统睡眠?

Wakelock 是Android特有的机制,用来阻止系统进入suspend状态。说白了就是一个"别睡,我还有事"的锁。

Wakelock 分为两种类型:

  • Partial Wakelock:阻止CPU进入suspend,但屏幕可以灭。常用于后台音乐播放、下载任务。
  • Full Wakelock:阻止CPU和屏幕都进入低功耗。现在基本被废弃了,因为太耗电。

在应用层获取 Wakelock 的代码示例:

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(
    PowerManager.PARTIAL_WAKE_LOCK,
    "MyApp:BackgroundTask"
);
wl.acquire(10 * 60 * 1000L); // 最多持有10分钟
// 执行后台任务...
wl.release();

注意:Wakelock 必须成对使用 acquire/release。忘记 release 会导致系统无法睡眠,这是最常见的功耗问题之一。我建议在开发阶段开启 adb shell dumpsys power 来监控 Wakelock 持有情况。

内核侧也有 Wakelock 机制,通过 /sys/power/wake_lock/sys/power/wake_unlock 控制。驱动开发者可以用 wake_lock_init() 创建内核 Wakelock。

17.3 Doze 模式:Android 6.0 引入的省电大招

Doze 模式是Google在Android 6.0(API 23)引入的。它的核心思想是:设备静止且灭屏一段时间后,系统进入深度省电状态。

Doze 模式的工作阶段:

  1. 轻量Doze:灭屏后30分钟进入。禁止网络访问,延迟JobScheduler任务。
  2. 深度Doze:灭屏后更长时间(取决于设备)。进一步限制应用活动。
  3. 维护窗口:系统定期(约9分钟)退出Doze,允许应用执行紧急任务。

应用如何适配 Doze?

  • 使用 setAndAllowWhileIdle() 设置闹钟,可以在Doze模式下触发
  • 使用 Firebase Cloud Messaging (FCM) 高优先级消息唤醒设备
  • 避免在后台频繁使用 Wakelock

避坑指南:我曾经开发一个即时通讯应用,在Doze模式下收不到消息。原因是用了普通的AlarmManager,被Doze延迟了。后来改用 setExactAndAllowWhileIdle() 才解决。但要注意,这种闹钟每小时只能触发几次,频繁使用会被系统忽略。

17.4 功耗调试工具:找到耗电元凶

调试功耗问题,光靠猜是不行的。我常用的工具有这些:

17.4.1 dumpsys power

最直接的命令,查看系统电源状态:

adb shell dumpsys power

输出中重点关注:

  • Wake Locks:当前持有的Wakelock列表
  • Display Power:屏幕状态
  • mWakefulness:Asleep/Awake/Dream
  • mDozeScreenState:Doze模式状态

17.4.2 Battery Historian

Google官方工具,可视化分析耗电数据:

adb bugreport bugreport.zip
# 上传到 https://bathistorian.appspot.com/ 或本地部署

它能展示CPU唤醒次数、Wakelock持有时间、网络活动、传感器使用等。我个人习惯在每次功耗测试后都导出bugreport,对比不同版本的耗电差异。

17.4.3 /proc/wakelocks 和 /sys/power

内核级别的调试接口:

adb shell cat /proc/wakelocks
adb shell cat /sys/power/wakeup_count
adb shell cat /sys/power/state

查看哪些内核驱动持有了Wakelock,以及系统支持的睡眠状态。

17.4.4 电流计与功率分析仪

硬件级别的调试工具。我常用的是 Monsoon 功率分析仪,可以精确测量设备在不同状态下的电流变化。配合 systrace 可以关联代码执行和电流波形。

实战经验:有一次设备待机电流偏高,用 Battery Historian 发现一个名为 "SensorService" 的Wakelock 持续持有。进一步排查发现是某个第三方应用注册了加速度传感器的监听,但没在灭屏时取消注册。解决方案是在 onPause() 中取消传感器监听。

17.5 知识体系总览

下面这张图总结了本章的核心知识点和它们之间的关系:

Android 功耗管理知识体系 系统电源状态 Suspend/Resume Wakelock Doze 模式 Suspend-to-RAM 设备驱动 suspend/resume 回调 唤醒源管理 Partial / Full 应用层 acquire/release 内核层 wake_lock 轻量 / 深度 Doze 维护窗口 setAndAllowWhileIdle 功耗调试工具 dumpsys power | Battery Historian | /proc/wakelocks | 功率分析仪

从图中可以看出,系统电源状态是顶层决策者,它决定了设备是清醒还是睡眠。Suspend/Resume 是内核层面的睡眠机制,Wakelock 是阻止睡眠的锁机制,Doze 是应用层面的省电策略。而调试工具贯穿始终,帮助我们定位问题。

我的建议:刚接触功耗调试的同学,先从 dumpsys power 和 Battery Historian 入手。这两个工具能覆盖80%的功耗问题。等遇到疑难杂症时,再上功率分析仪和内核trace。

功耗管理是个系统工程,从硬件设计到驱动开发,从框架层到应用层,每个环节都可能成为耗电的漏洞。但只要你掌握了 Suspend/Resume、Wakelock、Doze 这三个核心机制,再加上趁手的调试工具,大部分问题都能迎刃而解。


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