24. Doze模式与Android 12的睡眠模式(Sleep Mode):新变化

各位好,今天我们聊一个有意思的话题——Android 12引入的睡眠模式。

说实话,我第一次看到这个新特性时,心里想的是:「这不就是Doze模式的马甲吗?」但深入研究后,我发现事情没那么简单。Android 12的睡眠模式,其实是对Doze的一次重要补充和重构。

为什么要有睡眠模式?

先说说背景。Doze模式从Android 6.0开始引入,到Android 11已经相当成熟。但我在项目中遇到过一个典型问题:用户晚上睡觉时,手机明明放在那里不动,Doze却没能完全阻止后台唤醒

为什么会这样?因为Doze的触发条件是「设备静止且屏幕关闭一段时间」。但有些App会利用小技巧——比如每隔几分钟发一个低优先级通知,或者用JobScheduler卡着Doze的维护窗口做点小动作。这些行为虽然不致命,但确实影响了待机功耗。

Android 12的睡眠模式,说白了就是针对「用户真正在睡觉」这个场景做的深度优化

睡眠模式 vs Doze模式:核心区别

我整理了一张对比表,方便大家理解:

特性 Doze模式(Android 6~11) 睡眠模式(Android 12+)
触发条件 设备静止 + 屏幕关闭 用户睡眠时间(基于行为预测)
网络访问 维护窗口期可访问 完全禁止(除非白名单)
闹钟唤醒 允许setExactAndAllowWhileIdle 仅允许setAlarmClock
JobScheduler 延迟执行 完全挂起
系统行为 分阶段限制 一次性进入深度限制

嗯,这里要注意:睡眠模式不是取代Doze,而是叠加在Doze之上的更严格策略

睡眠模式的触发机制

Android 12通过DeviceStateManagerSleepModeController来管理睡眠模式。触发条件不再是简单的「屏幕关闭+静止」,而是引入了用户行为预测

我记得在调试某款原型机时,发现睡眠模式总是无法触发。后来查了源码才发现,系统需要收集至少7天的用户行为数据,才能建立睡眠模型。这包括:

  • 每天最后一次解锁时间
  • 每天第一次解锁时间
  • 夜间屏幕关闭后的活动模式

核心代码路径大致如下:

// frameworks/base/services/core/java/com/android/server/deviceidle/DeviceIdleController.java

private boolean shouldEnterSleepMode() {
    // 1. 检查是否处于预测的睡眠时间段
    if (!mSleepModePredictor.isSleepTime()) {
        return false;
    }
    // 2. 检查设备是否静止
    if (!isDeviceIdle()) {
        return false;
    }
    // 3. 检查屏幕是否关闭超过阈值
    if (mScreenOffTime < SLEEP_MODE_SCREEN_OFF_THRESHOLD_MS) {
        return false;
    }
    // 4. 检查是否有用户活动
    if (hasRecentUserActivity()) {
        return false;
    }
    return true;
}

说白了,系统会判断「你现在是不是真的在睡觉」,而不是「手机是不是没动」。

睡眠模式下的应用限制

进入睡眠模式后,应用的待遇比Doze更「惨」。我给大家列几个关键点:

核心变化:网络访问被完全切断

在Doze模式下,App还能在维护窗口期访问网络。但在睡眠模式下,除非App在白名单中,否则网络访问被完全禁止。这意味着后台同步、心跳包、甚至某些系统服务都会被限制。

我曾经帮一个团队排查过问题:他们的即时通讯App在Android 12上收不到消息。查了半天,发现是睡眠模式把网络切断了,而他们的FCM通道没走白名单。解决方案很简单——把App加到DEVICE_IDLE_USER_WHITELIST中。

白名单机制的变化

Android 12的白名单机制也做了调整。以前Doze的白名单是全局的,现在睡眠模式有自己独立的睡眠白名单

// 获取睡眠模式白名单
List<String> sleepWhitelist = deviceIdleController.getSleepModeWhitelist();

// 检查App是否在睡眠白名单中
boolean isWhitelisted = sleepWhitelist.contains(packageName);

我建议各位开发者注意:不要滥用白名单。Google在CTS测试中会检查睡眠白名单的App数量,超过阈值会直接fail。我在项目中就遇到过这种情况——某个预装App为了做数据上报,申请了睡眠白名单,结果CTS测试没过。

对开发者的影响

说实话,Android 12的睡眠模式对大多数App影响不大。但如果你做的是以下类型的应用,就需要特别注意:

  • 即时通讯类:必须走FCM高优先级通道,或者申请白名单
  • 健康监测类:使用setAlarmClock而不是setExact
  • 后台同步类:考虑使用WorkManagerEXPEDITED任务

避坑指南

我曾经在适配Android 12时,发现某个App的定时任务在睡眠模式下完全不执行。后来查了文档才知道,setExactAndAllowWhileIdle在睡眠模式下会被降级为setWindow。正确的做法是使用setAlarmClock,这个API在睡眠模式下仍然保证准时触发。

睡眠模式的状态机

为了让大家更直观地理解,我画了一张状态转换图:

Android 12 睡眠模式状态转换图 正常模式 屏幕亮 / 用户活跃 Doze模式 静止 + 屏幕关闭 深度Doze 长时间静止 睡眠模式 预测睡眠时间 唤醒 用户交互 / 闹钟 屏幕关闭 + 静止 长时间静止 预测睡眠时间 预测睡眠时间 用户交互 屏幕亮起 / 解锁 正常 Doze 深度Doze 睡眠模式 唤醒

从图中可以看到,睡眠模式是一个独立的状态,它可以从Doze或深度Doze进入。一旦进入睡眠模式,限制会更严格,直到用户主动唤醒设备。

调试与测试建议

最后,我分享几个调试睡眠模式的实用命令:

# 强制进入睡眠模式
adb shell dumpsys deviceidle force-sleep

# 退出睡眠模式
adb shell dumpsys deviceidle exit-sleep

# 查看睡眠模式状态
adb shell dumpsys deviceidle | grep -A 10 "SleepMode"

# 查看睡眠白名单
adb shell dumpsys deviceidle whitelist | grep "sleep"

注意事项

在测试睡眠模式时,记得先让系统收集足够的用户行为数据。否则force-sleep命令可能不会生效。我一般会在测试机上先跑一周的模拟数据,或者直接修改SleepModePredictor的阈值参数。

好了,关于Android 12睡眠模式的新变化,今天就聊到这里。这个特性虽然看起来只是Doze的补充,但实际上对后台行为的限制力度大了不少。各位在适配Android 12时,一定要重点测试睡眠模式下的应用行为,尤其是那些依赖后台定时任务和网络通信的App。


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