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
),但高优先级服务(如媒体播放)可能被豁免。
- 部分屏蔽 :普通 WakeLock 被忽略(如
-
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 适配建议
- 后台任务 :
- 使用
WorkManager
或JobScheduler
,其任务在STATE_DOZE
的维护窗口自动执行,但STATE_DOZE_SUSPEND
中仍会被冻结。
- 使用
- 实时性需求 :
- 关键任务(如闹钟)优先用
setAlarmClock()
(用户可见),或引导用户将应用加入电池优化白名单。
- 关键任务(如闹钟)优先用
- 网络依赖 :
- 在
STATE_DOZE_SUSPEND
下网络完全不可用,需通过高优先级 GCM 消息或短信唤醒设备(系统服务如电话/SMS 始终豁免)。
- 在
- 状态检测 :
- 通过
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
下可靠触发任务,需采用以下策略:
setAlarmClock()
:- 用于用户可见的精确闹钟(如闹钟应用),系统会强制退出休眠并显示通知栏图标。
- 无需特殊权限,但用户感知明显。
- 引导用户加入白名单 :
- 用户手动在 设置→电池优化 中将应用设为"未优化",可部分豁免限制(非完全保障,厂商兼容性差异大)。
- 前台服务(Foreground Service) :
- 通过持续通知栏服务维持进程活跃性,但需合理说明用途以避免被系统限制或用户关闭。
7.4 总结:不同闹钟方法在 Doze 模式下的有效性
闹钟方法 | STATE_DOZE (普通休眠) |
STATE_DOZE_SUSPEND (深度挂起) |
---|---|---|
setExact() / setWindow() |
❌ 延迟至维护窗口 | ❌ 完全冻结 |
setAndAllowWhileIdle() |
✅ 可触发(精度低) | ❌ 无效 |
setExactAndAllowWhileIdle() |
✅ 可触发(秒级误差) | ❌ 无效 |
setAlarmClock() |
✅ 立即触发 | ✅ 强制唤醒(需用户可见通知) |
结论 :
在
STATE_DOZE_SUSPEND
下,setExactAndAllowWhileIdle()
无法触发 。若需极端省电模式下的可靠性,应优先使用setAlarmClock()
或引导用户设置白名单)。
8. 更多内容
有关WorkManager基础的概念和使用,详见 : Android WorkManager的概念和使用