Android Doze低电耗休眠模式 与 WorkManager

1. Doze模式下,WorkManager setInitialDelay设置小于15分钟,被系统强制到15分钟执行,怎么办 ?

Android 拥有两项省电功能,通过管理设备未连接电源时应用的行为来延长用户电池续航时间:低电耗模式 (Doze) 和应用待机模式 (App Standby)。 低电耗模式 通过延迟设备长时间未使用时应用的后台 CPU 和网络活动来降低电池消耗。 应用待机模式 延迟没有近期用户活动的应用的后台网络活动。

设备处于低电耗模式时,应用对某些耗电资源的访问会被延迟,直到维护窗口。 具体限制列在电量管理限制中。

低电耗模式和应用待机模式管理在 Android 6.0 或更高版本上运行的所有应用的行为,无论它们是否专门针对 API 级别 23。为了帮助确保为用户提供最佳体验,请在低电耗模式和应用待机模式下测试您的应用,并对您的代码进行任何必要的调整。以下部分提供详细信息。

2. 了解低电耗模式(Doze)

如果用户将设备拔下电源并长时间静置,且屏幕关闭,设备就会进入低电耗模式。在低电耗模式下,系统会尝试通过限制应用对网络和 CPU 密集型服务的访问来节省电池电量。它还会阻止应用访问网络,并延迟其作业、同步和标准闹钟。

系统会定期短暂退出低电耗模式,让应用完成其延迟的活动。在此 维护窗口 期间,系统会运行所有待处理的同步、作业和闹钟,并允许应用访问网络。

维护窗口结束后,系统会再次进入低电耗模式,暂停网络访问并延迟作业、同步和闹钟。随着时间的推移,系统安排维护窗口的频率会降低,有助于在设备未充电且长时间不活动的情况下减少电池消耗。

当用户通过移动设备、打开屏幕或连接充电器来唤醒设备时,系统会退出低电耗模式,所有应用都会恢复正常活动。

3. 低电耗模式限制

设备处于低电耗模式时,系统会对您的应用施加以下限制:

  • 暂停网络访问。
  • 忽略唤醒锁。
  • 将标准 AlarmManager 闹钟(包括 setExact() 和 setWindow())延迟到下一个维护窗口。
    • 如果您需要在低电耗模式下触发闹钟,请使用 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。
    • 通过 setAlarmClock() 设置的闹钟会正常触发。系统会在这些闹钟触发前不久退出低电耗模式。
  • 不执行 Wi-Fi 扫描。
  • 不允许同步适配器运行。
  • 不允许 JobScheduler 运行。

⚠️ WorkManager 内部使用 JobScheduler,因此 WorkManager 任务不会运行。
具体详见 Android Developer | doze-standby

4. Doze模式下,WorkManager 为何无法精确时间执行 ?

通过上文,我们可以知道,在Doze模式下,WorkManager 任务不会运行,只有在两个Doze间隔期间,系统会定期短暂退出低电耗模式,让应用完成其延迟的活动。在此 维护窗口 期间,系统会运行所有待处理的同步、作业和闹钟,并允许应用访问网络。 这个时候WorkManager的任务才会被执行。但是两个Doze之间休眠时间的间隔是不确定的,所以Doze模式下,WorkManager无法精确时间被执行。

且如果WorkManager.setInitialDelay设置的时间小于15分钟,会被系统强制替换为15分钟。

那如果我就想10分钟后执行,需要怎么办呢 ?

5. Doze模式下,如何精确时间执行 ?

这个时候,就需要使用AlarmManager的setExactAndAllowWhileIdle方法了。 (虽然WorkManager在Android低版本上也是用的AlarmManager,但是并没有使用AlarmManager的setExactAndAllowWhileIdle方法)

那么如何使用呢 ?

5.1 ​​声明权限​

xml 复制代码
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

5.2 调用前需验证是否已授权

kotlin 复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    if (!alarmManager.canScheduleExactAlarms()) {
        // 引导用户前往设置页授权
        Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
        intent.setData(Uri.parse("package:" + getPackageName()));
        startActivity(intent);
        return;
    }
}

5.3 创建 BroadcastReceiver​ : ​​接收闹钟触发事件

kotlin 复制代码
public class AlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 执行任务逻辑(如启动服务、发送通知)
        Log.d("Alarm", "Triggered at exact time!");
    }
}

别忘了注册 Receiver​

xml 复制代码
<receiver android:name=".AlarmReceiver" android:exported="false"/>

5.4 设置精确闹钟​

kotlin 复制代码
// 获取 AlarmManager
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

// 创建 Intent 指向 BroadcastReceiver
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
    this, 0, intent, 
    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);

// 设置触发时间(例如 10 分钟后)
long triggerTime = System.currentTimeMillis() + 10 * 60 * 1000;

// 根据版本选择方法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(
        AlarmManager.RTC_WAKEUP, // 使用 UTC 时间并唤醒设备
        triggerTime, 
        pendingIntent
    );
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
}

5.5 设置了AlarmManager,如果系统时间变更了,是不是闹钟在现实世界响起的时间也会变 ?

  • 若用户将系统时间调快 1 小时(从 9:00 改为 10:00),闹钟会立即触发(因为系统时间已达到目标值)。
  • 若将系统时间调慢 1 小时(从 10:00 改为 9:00),闹钟会延迟 1 小时触发(需等待系统时间再次达到 10:00)

可以监听系统时间变化的广播,然后更改闹钟

kotlin 复制代码
// 在 onResume() 中注册
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
registerReceiver(timeChangeReceiver, filter);

// 在 onPause() 中注销
unregisterReceiver(timeChangeReceiver);

5.6 STATE_DOZE和STATE_DOZE_SUSPEND,有什么区别 ?

在 Android 电源管理机制中,STATE_DOZE 和 STATE_DOZE_SUSPEND 是两种不同的休眠状态,其核心区别在于系统资源限制的严格程度 和CPU活动状态。以下是两者的详细对比:

6. STATE_DOZE 和 STATE_DOZE_SUSPEND

  • STATE_DOZE(Doze 模式)

    • 首次引入于 Android 6.0(Marshmallow),目的是在设备闲置时(未充电、屏幕关闭、静止状态)减少后台活动。
    • 状态描述 :系统进入低功耗状态,但CPU仍保持部分活动,周期性唤醒处理任务。
  • STATE_DOZE_SUSPEND(Doze 挂起模式)

    • 强化版省电机制,于 Android 9.0(Pie) 引入,作为 Doze 的深度扩展。
    • 状态描述 :系统进入更深度的休眠 ,CPU 完全停止运行,仅保留最低限度的硬件唤醒能力(如传感器)。

6.1 CPU 与任务执行机制**

  • STATE_DOZE:

    • CPU 未完全停止,而是周期性唤醒(维护窗口)。
    • 维护窗口间隔:初始为每 30 分钟唤醒一次,随后间隔逐渐延长(如 1 小时、2 小时)。
    • 任务处理:在维护窗口内,系统允许应用执行延迟的任务(如同步、AlarmManager 闹钟)。
  • STATE_DOZE_SUSPEND:

    • CPU 完全挂起,无周期性唤醒。
    • 任务冻结 :所有后台进程被强制暂停(通过 Linux cgroup 的 freezer 子系统),不再分配 CPU 时间片。
    • 唤醒条件 :仅通过外部事件触发(如用户操作、高优先级闹钟 setAlarmClock())。

6.2 网络与后台资源访问

  • STATE_DOZE:

    • 网络限制:禁止后台应用访问网络,仅维护窗口内开放。
    • 部分豁免:高优先级 GCM 消息、短信/电话可临时唤醒网络。
  • STATE_DOZE_SUSPEND:

    • 完全断网:所有网络访问被禁止,包括 GCM 和短信(仅保留基础通信服务如通话)。
    • 硬件限制:Wi-Fi/GPS 扫描、传感器数据采集均暂停。

6.3 闹钟与任务调度行为

  • STATE_DOZE:

    • 标准闹钟延迟 :setExact() 或 setWindow() 的闹钟被推迟至下一个维护窗口。
    • 豁免闹钟 :setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle() 可在 Doze 下触发(但受每分钟 1 次的频率限制)。
  • STATE_DOZE_SUSPEND:

    • 所有闹钟冻结 :包括 AllowWhileIdle 类型的闹钟,仅 setAlarmClock()(用户可见的闹钟)可唤醒设备。
    • 任务调度失效:JobScheduler 和 WorkManager 任务被无限期推迟,直至退出 SUSPEND 状态。

6.4 Wakelock 处理

  • STATE_DOZE:

    • 部分屏蔽 :普通 WakeLock 被忽略(如 PARTIAL_WAKE_LOCK),但高优先级服务(如媒体播放)可能被豁免。
  • STATE_DOZE_SUSPEND:

    • 完全无效:所有 WakeLock 被强制释放,无法阻止 CPU 挂起。

6.5 持续时间与退出机制

  • STATE_DOZE:

    • 动态维护窗口:窗口间隔随闲置时间延长而增加(30 分钟 → 1 小时 → 数小时)。
    • 退出条件:屏幕点亮、设备移动或充电。
  • STATE_DOZE_SUSPEND:

    • 持续挂起:无周期性窗口,直至外部事件唤醒。
    • 退出条件更严格 :仅用户交互(如按键)、setAlarmClock() 闹钟或充电可唤醒。

6.6 对比总结

特性 STATE_DOZE STATE_DOZE_SUSPEND
CPU 状态 周期性唤醒(维护窗口) 完全停止
网络访问 仅维护窗口开放 完全禁止
闹钟执行 允许 AllowWhileIdle 类型 仅 setAlarmClock() 有效
任务调度 维护窗口内执行 完全冻结
Wakelock 有效性 部分豁免 全部释放
退出条件 移动/亮屏/充电 仅用户操作或高优先级闹钟
省电强度 中度 极端

6.7 适配建议

  1. 后台任务 :
    • 使用 WorkManager 或 JobScheduler,其任务在 STATE_DOZE 的维护窗口自动执行,但 STATE_DOZE_SUSPEND 中仍会被冻结。
  2. 实时性需求 :
    • 关键任务(如闹钟)优先用 setAlarmClock()(用户可见),或引导用户将应用加入电池优化白名单。
  3. 网络依赖 :
    • 在 STATE_DOZE_SUSPEND 下网络完全不可用,需通过高优先级 GCM 消息或短信唤醒设备(系统服务如电话/SMS 始终豁免)。
  4. 状态检测 :
    • 通过 PowerManager.isDeviceIdleMode() 检测 Doze 状态,但无法直接区分 SUSPEND(需依赖行为推断)。

📌 核心结论 :
STATE_DOZE 是 周期性唤醒的轻度休眠 ,适合延迟容忍的任务;
STATE_DOZE_SUSPEND 是 CPU 完全停止的深度冻结 ,仅响应极高优先级事件。

开发者需针对两者设计降级策略,确保关键功能在极端省电下仍可工作。

7. STATE_DOZE_SUSPEND状态下设置setExactAndAllowWhileIdle有效吗

在 Android 的 STATE_DOZE_SUSPEND(深度挂起模式)状态下,setExactAndAllowWhileIdle() 是无效的。以下是具体原因和机制分析:


7.1 STATE_DOZE_SUSPEND 的特性

  • CPU 完全冻结 :
    此状态下系统进入深度休眠,CPU 完全停止运行,所有后台进程被强制暂停,仅保留基础硬件唤醒能力(如高优先级闹钟或物理按键)。
  • 网络与任务冻结 :
    所有网络访问被禁止,JobScheduler、WorkManager 等后台任务被无限期推迟,标准闹钟和 AllowWhileIdle 类闹钟均被冻结。

7.2 setExactAndAllowWhileIdle() 的限制

  • 仅适用于普通 Doze 模式 :
    setExactAndAllowWhileIdle() 设计用于 STATE_DOZE(普通休眠),可在维护窗口外触发(但受每分钟 1 次的频率限制)。但在 STATE_DOZE_SUSPEND 下,系统无周期性唤醒机制,导致该方法失效。
  • 深度省电下的唤醒条件 :
    仅以下事件可唤醒 STATE_DOZE_SUSPEND:
    • 用户主动操作(如点亮屏幕)
    • 高优先级闹钟 setAlarmClock()(系统会提前退出休眠并显示通知)
    • 设备充电。

7.3 替代方案

若需在 STATE_DOZE_SUSPEND 下可靠触发任务,需采用以下策略:

  1. setAlarmClock() :
    • 用于用户可见的精确闹钟(如闹钟应用),系统会强制退出休眠并显示通知栏图标。
    • 无需特殊权限,但用户感知明显。
  2. 引导用户加入白名单 :
    • 用户手动在 设置→电池优化 中将应用设为"未优化",可部分豁免限制(非完全保障,厂商兼容性差异大)。
  3. 前台服务(Foreground Service) :
    • 通过持续通知栏服务维持进程活跃性,但需合理说明用途以避免被系统限制或用户关闭。

7.4 总结:不同闹钟方法在 Doze 模式下的有效性

闹钟方法 STATE_DOZE(普通休眠) STATE_DOZE_SUSPEND(深度挂起)
setExact() / setWindow() ❌ 延迟至维护窗口 ❌ 完全冻结
setAndAllowWhileIdle() ✅ 可触发(精度低) ❌ 无效
setExactAndAllowWhileIdle() ✅ 可触发(秒级误差) ❌ 无效
setAlarmClock() ✅ 立即触发 ✅ 强制唤醒(需用户可见通知)

结论 :

在 STATE_DOZE_SUSPEND 下,setExactAndAllowWhileIdle() 无法触发 。若需极端省电模式下的可靠性,应优先使用 setAlarmClock() 或引导用户设置白名单)。

8. 更多内容

有关WorkManager基础的概念和使用,详见 : Android WorkManager的概念和使用

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui