AlarmManager(系统服务之定时服务)
概述
AlarmManager提供了对系统定时服务的访问接口,使得开发者可以安排在未来的某个时间运行应用。当到达闹铃设定时间,系统就会广播闹铃之前注册的Intent。如果此时目标应用没有被启动,系统还会帮你自动启动目标应用。即使设备已经进入睡眠已注册的闹铃也会被保持,只有当设备关闭或是重启的时候会被清除。 下面基于Android 8.0源码来一起学习一下。
AlarmManager 对象获取
//AlarmManager 系统服务的获取方式
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
类型
AlarmManager中一共提供了四种闹钟类型,前两种对应的System.currentTimeMillis()(系统当前时间)时间,后两种对应SystemClock.elapsedRealtime()(系统运行时间)时间,以WAKEUP结尾的类型能够唤醒设备,其他的类型不能唤醒设备,直到设备被唤醒才能出发警报提醒。
public static final int RTC_WAKEUP = 0;
public static final int RTC = 1;
public static final int ELAPSED_REALTIME_WAKEUP = 2;
public static final int ELAPSED_REALTIME = 3;
AlarmType | 时间参照物 | 是否在休眠时候唤起设备 |
---|---|---|
public static final int RTC_WAKEUP = 0 | System.currentTimeMillis() ,当前系统的时间,单位ms | 是 |
public static final int RTC = 1 | System.currentTimeMillis(),当前系统的时间,单位ms | 否 |
public static final int ELAPSED_REALTIME_WAKEUP = 2 | SystemClock.elapsedRealtime() ,从系统启动到目前的时间(包括系统休眠),单位ms | 是 |
public static final int ELAPSED_REALTIME = 3 | SystemClock.elapsedRealtime(),从系统启动到目前的时间(包括系统休眠),单位ms | 否 |
功能方法
设置定时任务相关方法
set(设置单次的定时任务)
返回值 | 公开方法 | 参数说明 |
---|---|---|
void | set(int type, long triggerAtMillis, PendingIntent operation) | triggerAtMillis:触发执行的时间;operation:挂起待执行的意图 |
void | set(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) | listener:待执行的回调;targetHandler:回调执行 |
用于设置一次性闹铃,执行时间在设置时间附近,为非精确闹铃。 方法一和方法二的区别:到达设定时间时方法一会广播PendingIntent中设定的Intent,而方法二会直接回调OnAlarmListener 中的onAlarm()方法。
setExact(设置单次准确的定时任务)
返回值 | 公开方法 | 参数说明 |
---|---|---|
void | setExact(int type, long triggerAtMillis, PendingIntent operation) | - |
void | setExact(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) | - |
void | setAlarmClock(AlarmManager.AlarmClockInfo info, PendingIntent operation) | type=RTC_WAKEUP |
用于设置一次性闹铃,执行时间更为精准,为精确闹铃。 方法一和二的区别参见上面set的区别。setAlarmClock方法等同于通过setExact方法设置的RTC_WAKEUP类型的闹铃,所以把他归在setExact中介绍。其中AlarmClockInfo实现了Android序列化接口Parcelable,里面包含了mTriggerTime(执行时间)和mShowIntent(执行动作)两个成员变量,可以看做是对闹铃事件的一个封装类。
setInexactRepeating和setRepeating(设置循环的定时任务)
返回值 | 公开方法 | 参数说明 |
---|---|---|
void | setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) | 这个循环定时,时间是精确地 |
void | setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) | 与上面相反,是不精确的 |
setInexactRepeating和setRepeating两种方法都是用来设置重复闹铃的,setRepeating执行时间更为精准。在Android 4.4之后,Android系统为了省电把时间相近的闹铃打包到一起进行批量处理,这就使得setRepeating方法设置的闹铃不能被精确的执行,必须要使用setExact来代替。
setAndAllowWhileIdle和setExactAndAllowWhileIdle。(设置在低功耗条件下也执行的定时任务)
返回值 | 公开方法 | 参数说明 |
---|---|---|
void | setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,PendingIntent operation) | 这个单次定时,时间是精确地 |
void | setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,PendingIntent operation) | 与上面相反,是不精确的 |
使用setAndAllowWhileIdle和setExactAndAllowWhileIdle方法设置一次闹铃,可以在低功耗模式下被执行,setExactAndAllowWhileIdle执行时间更为精准。 手机灭屏以后会进入低功耗模式(low-power idle modes),这个时候你会发现通过setExact设置的闹铃也不是100%准确了,需要用setExactAndAllowWhileIdle方法来设置,闹铃才能在低功耗模式下被执行。
setWindow(设置在时间段执行的定时任务)
返回值 | 公开方法 | 参数说明 |
---|---|---|
void | setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,PendingIntent operation) | 时间段左边界,时间段右边界 |
setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,String tag, OnAlarmListener listener, Handler targetHandler) | 同上 |
用于设置某个时间段内的一次闹铃。 比如,我想在下午的2点到4点之间设置一次提醒。两个方法的区别同set。
取消定时任务相关方法
返回值 | 公开方法 | 参数说明 |
---|---|---|
void | cancel(PendingIntent operation) | 根据PendingIntent 取消定时任务 |
void | cancel(AlarmManager.OnAlarmListener listener) | 根据OnAlarmListener 取消定时任务 |
用于取消设置过的闹铃,分别对应于PendingIntent和AlarmManager.OnAlarmListener方式注册的闹铃。
获得下一次闹铃事件
返回值 | 公开方法 | 参数说明 |
---|---|---|
AlarmManager.AlarmClockInfo | getNextAlarmClock() | 获取下一次定时任务信息,没有就为null |
用于获得下一次闹铃事件。
设置时间
在AlarmMananger中提供了setTime和setTimeZone方法分别用来设置系统时间和系统默认时区。其中,设置系统时间需要"android.permission.SET_TIME"权限。
返回值 | 公开方法 | 参数说明 |
---|---|---|
void | setTime(long millis) | 设置系统时间,需要权限android.permission.SET_TIME |
void | setTimeZone(String timeZone) | 设置系统时区,对所有apps生效;TimeZone.setDefault设置时区,只在当前app生效 |
tips:
setTimeZone(String timeZone)的参数timeZone,在sdk>=Build.VERSION_CODES.M(23),TimeZone#getAvailableIDs()中字符串会抛出IllegalArgumentException
总结
本文基于Android 8.0源码,首先列举了闹铃种类和设置闹铃、取消闹铃、获得下一次闹铃事件、设置系统时间、设置系统时区等相关方法,然后结合系统源码详细分析了各种方法的实现机制。使用过程中有以下几点需要注意:
- 设置系统时间需要"android.permission.SET_TIME"权限。
- 每当有新的Alarm设置或删除定时服务都会重新计算所属批次,把时间相近的Alarm打包到一个批次里(Batch)一起执行,起到优化电池节省耗电的目的。这就是导致非精确Alarm执行时间存在不确定误差的根本原因。
- 如果想要在低耗电模式下触发闹铃需要通过setAndAllowWhileIdle和setExactAndAllowWhileIdle方法来设置闹铃。
- 如果设置的闹铃时间已经过了,闹铃会被立即触发。这个问题可以通过比较闹铃设置时间和当前时间来解决。
- 根据实际需求选择是否设置精确闹铃以达到优化电池节省耗电的目的。
- 通过设置时区的源码可知,如果想要获取系统时区的相关信息可以通过监听Intent.ACTION_TIMEZONE_CHANGED广播或是直接读取系统属性TIMEZONE_PROPERTY。
android不同版本对AlarmManager的影响
版本节点 | 影响 | 原因 | 解决办法 |
---|---|---|---|
SDK < 19 | 使用 set() 或 setRepeating() 创建的闹铃是精确的 | 如果您已将 targetSdkVersion 设置为“18”或更低版本,那么在 Android 4.4 上运行时,您的闹铃的行为方式和在以前版本上一样 | |
19=<SDK < 23 | 使用 set() 或 setRepeating() 创建的闹铃将变得不准确 | 为提高电源效率,Android 现在批处理在合理的相似时间发生的所有应用的闹铃,以便系统仅唤醒设备一次,而不是多次唤醒设备来处理每个闹 | 无需精确建议使用新的 setWindow() 方法;任然需要精确的时钟时间(例如,日历事件提醒),那么您可以使用新的 setExact() 方法 |
23=<SDK | 在低电耗模式下,您的应用会受到以下限制:系统忽略唤醒锁定;标准 AlarmManager 闹钟(包括 setExact() 和 setWindow())推迟到下一个维护期 | 针对低电耗模式和应用待机模式进行优化 | 如果您需要设置在设备处于低电耗模式时触发的闹钟,请使用 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。使用 setAlarmClock() 设置的闹钟将继续正常触发,系统会在这些闹钟触发之前不久退出低电耗模式 |
tips:
版本4.4(19)变更说明:https://developer.android.google.cn/about/versions/android-4.4
版本6.0(23)变更说明:https://developer.android.google.cn/about/versions/marshmallow/android-6.0-changes
6.0变更具体位置,低电耗模式和应用待机模式:https://developer.android.google.cn/training/monitoring-device-state/doze-standby
程序杀死后定时任务不执行
定时任务不执行的原因是程序进程停止运行了
- 通过在启动的广播manifest.xml中添加android:process=":myReceiver"
- android8.0不能有静态广播,那就添加一个前台服务,在启动定时器任务前打开前台服务,前台服务最近历史列表中杀不死,除非在设置里面杀死
AlarmManager与Timer的区别
上下文依赖 | |
---|---|
AlarmManager | android系统提供的系统服务功能,只有当系统关机或者重启任务才会失败不执行;能唤醒休眠锁,可在休眠时候执行 |
Timer | 依赖于线程,线程又依赖于启动线程的app进程,也就是说程序杀死就终止;不能唤醒休眠锁,系统休眠会停止运行 |
AlarmManagerCompat版本兼容处理
implementation "com.android.support:support-compat:28.0.0"
//或者
implementation "androidx.core:core:1.3.0"
AlarmManagerCompat 代码:
public final class AlarmManagerCompat {
public static void setAlarmClock(@NonNull AlarmManager alarmManager, long triggerTime, @NonNull PendingIntent showIntent, @NonNull PendingIntent operation) {
if (VERSION.SDK_INT >= 21) {
alarmManager.setAlarmClock(new AlarmClockInfo(triggerTime, showIntent), operation);
} else {
setExact(alarmManager, 0, triggerTime, operation);
}
}
public static void setAndAllowWhileIdle(@NonNull AlarmManager alarmManager, int type, long triggerAtMillis, @NonNull PendingIntent operation) {
if (VERSION.SDK_INT >= 23) {
alarmManager.setAndAllowWhileIdle(type, triggerAtMillis, operation);
} else {
alarmManager.set(type, triggerAtMillis, operation);
}
}
public static void setExact(@NonNull AlarmManager alarmManager, int type, long triggerAtMillis, @NonNull PendingIntent operation) {
if (VERSION.SDK_INT >= 19) {
alarmManager.setExact(type, triggerAtMillis, operation);
} else {
alarmManager.set(type, triggerAtMillis, operation);
}
}
public static void setExactAndAllowWhileIdle(@NonNull AlarmManager alarmManager, int type, long triggerAtMillis, @NonNull PendingIntent operation) {
if (VERSION.SDK_INT >= 23) {
alarmManager.setExactAndAllowWhileIdle(type, triggerAtMillis, operation);
} else {
setExact(alarmManager, type, triggerAtMillis, operation);
}
}
private AlarmManagerCompat() {
}
}
AlarmManager,JobScheduler,WorkManager的功能
服务 | 功能介绍 | 版本问题 |
---|---|---|
AlarmManager | 提供在将来的某个时刻运行你的程序 | API 19开始运行时间传递不精准 |
JobScheduler | 提供特定时间并满足某些条件后运行你的程序 | API 21开始使用, |
WorkManager | 是以上两个的进一步封装,Jetpack中的组件 | WorkManager requires compileSdk version 28 or higher |
https://www.jianshu.com/p/8a2ce9d02640
https://blog.youkuaiyun.com/kingyc123456789/article/details/107179126/