4. Wakeup Sources与Wake Lock机制详解
好,咱们今天聊点硬核的。Wakeup Source和Wake Lock,这两个东西说白了就是Android Automotive休眠唤醒的「守门员」。一个在内核层面盯着硬件,一个在用户空间管着应用。我做了这么多年嵌入式,见过太多因为这两者配合不好导致的休眠失败案例。今天咱们就把它们彻底讲透。
4.1 Wakeup Source:内核里的硬件唤醒管家
先说说Wakeup Source。这东西在内核里,本质就是一个数据结构。它负责跟踪哪些硬件设备有能力把系统从休眠中唤醒。你想想看,如果没有它,内核怎么知道按一下电源键就该醒过来?
我个人习惯把Wakeup Source理解成一个「注册表」。每个能唤醒系统的硬件,都得在这里登个记。比如GPIO按键、USB插拔检测、RTC闹钟,甚至车载的CAN总线唤醒信号。
4.1.1 在内核中注册一个Wakeup Source
注册过程其实不复杂。看个例子:
// 定义一个wakeup source结构体
static struct wakeup_source *my_wakeup_src;
// 在驱动初始化时注册
static int my_driver_probe(struct platform_device *pdev)
{
// 创建一个名为"my_wakeup"的唤醒源
my_wakeup_src = wakeup_source_register(NULL, "my_wakeup");
if (!my_wakeup_src) {
dev_err(&pdev->dev, "Failed to register wakeup source\n");
return -ENOMEM;
}
// 使能设备的唤醒能力
device_init_wakeup(&pdev->dev, true);
return 0;
}
// 在驱动卸载时注销
static int my_driver_remove(struct platform_device *pdev)
{
wakeup_source_unregister(my_wakeup_src);
device_init_wakeup(&pdev->dev, false);
return 0;
}
嗯,这里要注意。注册只是第一步,真正让Wakeup Source生效的是后面的事件上报。
4.1.2 上报唤醒事件
当硬件检测到唤醒信号时,驱动需要调用特定的API来通知内核。我见过不少新手在这里翻车——注册了但不报事件,等于白干。
// 在中断处理函数中上报唤醒事件
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
// 上报一个唤醒事件
__pm_wakeup_event(my_wakeup_src, 0); // 第二个参数是超时时间,0表示不超时
// 处理中断...
return IRQ_HANDLED;
}
这里有个关键点:__pm_wakeup_event会递增wakeup source的计数,同时通知电源管理子系统「有人要醒过来了」。内核会根据这个计数决定是否真的退出休眠。
核心概念:Wakeup Source的计数机制。当计数大于0时,系统认为有活跃的唤醒源,不会进入深度休眠。这跟后面要讲的Wake Lock有异曲同工之妙。
4.2 Android Wake Lock:用户空间的电源锁
好,从内核跳出来,咱们看看用户空间。Wake Lock是Android提供给应用层的API,说白了就是一个「别睡」的锁。应用拿到这个锁,系统就不敢轻易休眠。
我记得在做一个车载导航项目时,导航应用在播报语音时没拿Wake Lock,结果屏幕一灭,语音也断了。用户投诉说「导航到一半没声音了」——这就是典型的Wake Lock使用不当。
4.2.1 PowerManager.WakeLock的使用
Android的Wake Lock通过PowerManager来管理。用法如下:
// 获取PowerManager实例
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
// 创建一个Wake Lock,类型为PARTIAL_WAKE_LOCK
PowerManager.WakeLock wakeLock = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"MyApp:NavigationVoice"
);
// 获取锁,阻止系统休眠
wakeLock.acquire();
// 播放语音导航...
// 播放完毕,释放锁
wakeLock.release();
这里有个坑。我见过很多开发者在acquire()之后忘记release(),结果手机一晚上都睡不了。在Android Automotive上更严重——车机不休眠,电瓶很快就亏电了。
避坑指南:我曾经在一个项目中排查过「车机熄火后不休眠」的问题。最后发现是一个第三方地图应用在后台持有了Wake Lock没释放。从那以后,我要求所有Wake Lock必须配合try-finally或者acquire(timeout)来使用。
4.2.2 Wake Lock的类型
Android定义了多种Wake Lock类型,每种对应不同的行为:
| 类型 | CPU | 屏幕 | 键盘灯 | 适用场景 |
|---|---|---|---|---|
| PARTIAL_WAKE_LOCK | 保持运行 | 可关闭 | 可关闭 | 后台下载、音乐播放 |
| SCREEN_DIM_WAKE_LOCK | 保持运行 | 保持亮但变暗 | 可关闭 | 视频播放(非全屏) |
| SCREEN_BRIGHT_WAKE_LOCK | 保持运行 | 保持全亮 | 可关闭 | 导航、视频通话 |
| FULL_WAKE_LOCK | 保持运行 | 保持全亮 | 保持亮 | 需要用户交互的场景 |
| PROXIMITY_SCREEN_OFF_WAKE_LOCK | 保持运行 | 可关闭(接近传感器控制) | 可关闭 | 通话中贴近耳朵时 |
在Android Automotive上,我最常用的是PARTIAL_WAKE_LOCK。为什么?因为车机不需要频繁亮屏,但后台服务(比如CAN总线监听、OTA下载)必须持续运行。
4.3 两者的协同关系:从应用到内核的完整链路
好,现在咱们把Wakeup Source和Wake Lock串起来。它们是怎么配合的?
说白了,这是一个「上下呼应」的关系:
- 应用层:通过PowerManager.WakeLock告诉系统「我需要CPU保持运行」
- Framework层:PowerManagerService维护一个Wake Lock的引用计数
- 内核层:当所有Wake Lock释放后,内核检查是否有活跃的Wakeup Source
- 硬件层:如果没有活跃的唤醒源,系统进入休眠;如果有,继续等待
我画个简化的流程图(用文字描述):
应用acquire Wake Lock
↓
PowerManagerService增加引用计数
↓
内核通过wakeup_source_activate()通知电源管理
↓
系统保持唤醒状态
↓
应用release Wake Lock
↓
PowerManagerService减少引用计数
↓
计数为0? → 是 → 检查所有Wakeup Source
↓
所有Wakeup Source都deactivate?
↓
是 → 系统进入休眠
↓
否 → 等待活跃的Wakeup Source处理完毕
经验之谈:在Android Automotive上,我建议你在调试休眠问题时同时查看两个地方:/sys/power/wake_lock(用户空间锁)和/sys/kernel/debug/wakeup_sources(内核唤醒源)。这两个文件能告诉你「谁不让系统睡」。
4.4 实战中的常见问题
最后,分享几个我在项目中踩过的坑:
- Wake Lock泄漏:应用异常退出时没释放锁。解决方案:使用
acquire(timeout)设置超时,或者用WakefulBroadcastReceiver自动管理。 - Wakeup Source冲突:两个驱动注册了同一个唤醒源名称。内核会报错,但不会阻止注册。排查时记得检查
dmesg。 - 车载特殊场景:ACC信号断开后,车机应该休眠。但如果某个Wakeup Source(比如CAN控制器)还在活跃,系统就睡不了。我当时的做法是在ACC掉电时强制deactivate所有非必要的Wakeup Source。
嗯,关于Wakeup Source和Wake Lock,今天就讲这么多。记住一句话:内核管硬件能不能醒,应用管系统要不要睡。两者配合好了,你的车机才能该睡就睡、该醒就醒。