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串起来。它们是怎么配合的?

说白了,这是一个「上下呼应」的关系:

  1. 应用层:通过PowerManager.WakeLock告诉系统「我需要CPU保持运行」
  2. Framework层:PowerManagerService维护一个Wake Lock的引用计数
  3. 内核层:当所有Wake Lock释放后,内核检查是否有活跃的Wakeup Source
  4. 硬件层:如果没有活跃的唤醒源,系统进入休眠;如果有,继续等待

我画个简化的流程图(用文字描述):

应用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,今天就讲这么多。记住一句话:内核管硬件能不能醒,应用管系统要不要睡。两者配合好了,你的车机才能该睡就睡、该醒就醒。