目录
1 概述
AlarmManager类提供对系统警报服务的访问。这些允许您安排应用程序在将来的某个时间运行。当警报响起时,Intent系统会广播为其注册的警报,如果目标应用程序尚未运行,则会自动启动它。设备处于休眠状态时会保留已注册的警报(如果设备在此期间关闭,则可以选择将设备唤醒),但如果设备关闭并重新启动,则会清除AlarmManager的任务。
AlarmManager 系统提供的一个定时任务管理器,通过AlarmManager 提供的定时任务,可以在约定的时间发送广播,启动服务,启动Activity等等(如何实现看后面的例子第6部分)。AlarmManager是Android中常用的一种系统级别的提示服务,在特定的时刻为我们广播某个指定的Intent。简单的说就是我们设定某一个时间,然后在该时间到来时,AlarmManager为 我们广播一个我们设定的Intent广播因此我们需要实现一个针对特定闹钟事件的广播接收器 (PendingIntent)。
2 为什么要有AlarmManager
比较Timer、Hnadler、AlarmManager
Timer,有一个明显的问题,它并不太适合用于需要长期在后台运行的定时任务。我们都知道,为了能让电池更加耐用,每种手机都会有自己的休眠策略,Android手机就会在长时间不操作的情况下自动让CPU进入睡眠状态,这就有可能导致Timer中的定时任务无法正常运行。
Hnadler,Handler的postDelay方法也可以实现定时操作,它同样也是不靠谱的,因为默认Hnadler依赖于线程(main线程或者子线程),所以只要进程被杀死,所有相关的线程都被晒死,所以handler中的定时操作就无效了。Timer也一样,因为Timer实际上是另起一个子线程,进程被杀,子线程当然也被杀了。
AlarmManager,它通过pendingIntent具有唤醒未启动进程的功能,即可以保证每次需要执行定时任务的时候CPU都能正常工作。但是当设备关机和重启后,闹钟将被清除。
3 闹铃类型
AlarmManager中一共提供了四种闹钟类型,
前两种对应的System.currentTimeMillis()(系统当前时间,或者叫绝对时间。例如 2020年几月几日。实际上这个时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数)时间,
后两种对应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;
闹钟类型:一般为 AlarmManager.ELAPSED_REALTIME_WAKEUP
或者 AlarmManager.RTC_WAKEUP
。它们之间的区别就是前者是从手机开机后的时间,包含了手机睡眠时间;而后者使用的就是手机系统设置中的时间。所以如果设置为AlarmManager.RTC_WAKEUP
,那么可以通过修改手机系统的时间来提前触发定时事件。另外,对于相似的 AlarmManager.ELAPSED_REALTIME
和 AlarmManager.RTC
来说,它们不会唤醒 CPU 。所以使用的频率较少;
1)这里补充一句,这些带WakeUp类型的闹钟,同样的在开机的情况下,也是会生效的,也是会触发闹钟的。并不是说必须待机后,才会触发wakeup类型的闹钟。这点要注意。
2)交流断电后,时钟是否还会生效? 应该是不会了。需要重新设置时钟。在哪里设置呢?考虑收到Boot Completed广播后重新去设置时钟。
这里肯定不会生效了,请看 AlarmManager.java 的文件头部的说明。
Registered alarms are retained while the device is asleep [...] but will be cleared if it is turned off and rebooted.
3)挺佩服有些手机没电了关机,充电后,未开机的情况下,还是可以闹钟工作。看样子是设置到了外部时钟里了。单独供电的外部时钟还可以唤醒设备?
4)还有一个方案,静默开机,充电后自动开机,然后再进入待机。走2)就实现了。
4 AlarmManager的常用方法
4.1 设置时间
在AlarmMananger中提供了setTime和setTimeZone方法分别用来设置系统时间和系统默认时区。其中,设置系统时间需要"android.permission.SET_TIME"权限。
返回值 | 公开方法 |
---|---|
void | setTime(long millis) |
void | setTimeZone(String timeZone) |
4.2 设置闹铃
返回值 | 公开方法 |
---|---|
void | set(int type, long triggerAtMillis, PendingIntent operation) |
void | set(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
void | setAlarmClock(AlarmManager.AlarmClockInfo info, PendingIntent operation) |
void | setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) |
void | setExact(int type, long triggerAtMillis, PendingIntent operation) |
void | setExact(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
void | setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) |
void | setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) |
void | setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) |
void | setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) |
void | setWindow(int type, long windowStartMillis, long windowLengthMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
以上是AlarmManager中提供的所有设置闹铃的方法,下面来详细介绍一下
-
4.2.1 set
返回值 | 公开方法 |
---|---|
void | set(int type, long triggerAtMillis, PendingIntent operation) |
void | set(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
用于设置一次性闹铃,执行时间在设置时间附近,为非精确闹铃。方法一和方法二的区别:到达设定时间时方法一会广播PendingIntent中设定的Intent,而方法二会直接回调OnAlarmListener 中的onAlarm()方法。
还是举个栗子吧。以下例子来自《疯狂Android讲义》 10.7.2 设置闹钟 章节
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
Intent.FLAG_RECEIVER_FOREGROUND);
intent.setAction("self.intent.rtctimer.wakeup");
intent.putExtra("name", "xiaoming");
intent.putExtra("age", 18);
PendingIntent pi = PendingIntent.getBroadcast(getApplicationContext(), 11, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
Log.i(TAG, "set timer, config is xiaoming and 18");
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMills() + 120, pi);
-
4.2.2 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) |
用于设置一次性闹铃,执行时间更为精准,为精确闹铃。方法一和二的区别参见上面set的区别。setAlarmClock方法等同于通过setExact方法设置的RTC_WAKEUP类型的闹铃,所以把他归在setExact中介绍。其中AlarmClockInfo实现了Android序列化接口Parcelable,里面包含了mTriggerTime(执行时间)和mShowIntent(执行动作)两个成员变量,可以看做是对闹铃事件的一个封装类。
- 4.2.3 setInexactRepeating和setRepeating
返回值 | 公开方法 |
---|---|
void | setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) |
void | setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) |
setInexactRepeating和setRepeating两种方法都是用来设置重复闹铃的,setRepeating执行时间更为精准。在Android 4.4之后,Android系统为了省电把时间相近的闹铃打包到一起进行批量处理,这就使得setRepeating方法设置的闹铃不能被精确的执行,必须要使用setExact来代替。
- 4.2.4 setAndAllowWhileIdle和setExactAndAllowWhileIdle
返回值 | 公开方法 |
---|---|
void | setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) |
void | setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) |
使用setAndAllowWhileIdle和setExactAndAllowWhileIdle方法设置一次闹铃,可以在低功耗模式下被执行,setExactAndAllowWhileIdle执行时间更为精准。手机灭屏以后会进入低功耗模式(low-power idle modes),这个时候你会发现通过setExact设置的闹铃也不是100%准确了,需要用setExactAndAllowWhileIdle方法来设置,闹铃才能在低功耗模式下被执行。
- 4.2.5 setWindow
返回值 | 公开方法 |
---|---|
void | setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) |
void | setWindow(int type, long windowStartMillis, long windowLengthMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
用于设置某个时间段内的一次闹铃。比如,我想在下午的2点到4点之间设置一次提醒。两个方法的区别同set。
4.3 取消闹铃
返回值 | 公开方法 |
---|---|
void | cancel(PendingIntent operation) |
void | cancel(AlarmManager.OnAlarmListener listener) |
用于取消设置过的闹铃,分别对应于PendingIntent和AlarmManager.OnAlarmListener方式注册的闹铃。
AlarmManager alarmMgr = (AlarmManager)mContext.getSystemService(Contetxt.ALARM_SERVICE);
alarmMgr.cancel(mPendItForXXX);
//所以说你的PendingIntent ,最好还是存成成员变量。否则,要取消的时候,找不到了。
if ( mPendItForXXX != null)
{
alarmMgr.cancel(mPendItForXXX);
mPendItForXXX = null;
}
//这个时候再去设置。这样就是实现了只设置一次。否则,会设置多次时钟,与预期不符。
是不是定义为 private static PendingIntent mPendItForXXX = null; 比较合适。
呵呵,否则,新建一个实例,创建一个PendingIntent, 那累了。
4.4 获得下一次闹铃事件
返回值 | 公开方法 |
---|---|
AlarmManager.AlarmClockInfo | getNextAlarmClock() |
用于获得下一次闹铃事件。
5 常用时间定义
AlarmManager类已经帮我们定义好了常用的时间常量。
AlarmManager.INTERVAL_FIFTEEN_MINUTES 间隔15分钟
AlarmManager.INTERVAL_HALF_HOUR 间隔半个小时
AlarmManager.INTERVAL_HOUR 间隔一个小时
AlarmManager.INTERVAL_HALF_DAY 间隔半天
AlarmManager.INTERVAL_DAY 间隔一天
public static final long INTERVAL_FIFTEEN_MINUTES = 15 * 60 * 1000;
public static final long INTERVAL_HALF_HOUR = 2*INTERVAL_FIFTEEN_MINUTES;
public static final long INTERVAL_HOUR = 2*INTERVAL_HALF_HOUR;
public static final long INTERVAL_HALF_DAY = 12*INTERVAL_HOUR;
public static final long INTERVAL_DAY = 2*INTERVAL_HALF_DAY;
6 举个例子操作一下
6.1 初始化PendingIntent
public static final int ALARM_ACTION_CODE= "intent_alarm_code";
Intent intent = new Intent(ALARM_ACTION_CODE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
REQUEST_CODE, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
-
PendingInent,Intent 更加倾向于去立即执行某个动作,而 PendingIntent 更加倾向于在某个合适的时机去执行某个动作。所以,也可以把 PendingIntent 简单地理解为延迟执行的 Intent。
getActivity()方法、getBroadcast()方法、getService()方法。分别对应着启动Activity,发送广播,启动service。 -
ALARM_ACTION_CODE,Intent传递给广播接收者的action,自己设定。自己设定用于标记自己的命令的。
-
PendingIntent.FLAG_CANCEL_CURRENT,pendingIntent的第四个参数如果直接传0,表示你不打算通过任何一个flag来控制pendingIntent的创建。下面说说pendingIntent提供的四种flag:
- FLAG_CANCEL_CURRENT,如果要创建的PendingIntent已经存在了,那么在创建新的PendingIntent之前,原先已经存在的PendingIntent中的intent将不能使用。
- FLAG_NO_CREATE,如果要创建的PendingIntent尚未存在,则不创建新的PendingIntent,直接返回null。
- FLAG_ONE_SHOT,相同的PendingIntent只能使用一次,且遇到相同的PendingIntent时不会去更新PendingIntent中封装的Intent的extra部分的内容。
- FLAG_UPDATE_CURRENT,如果要创建的PendingIntent已经存在了,那么在保留原先PendingIntent的同时,将原先PendingIntent封装的Intent中的extra部分替换为现在新创建的PendingIntent的intent中extra的内容。
6.2 初始化AlarmManger
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
设置重复执行的定时任务, 这个就要区分Android版本了
4.4之前,SDK API < 19
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
或
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(), TIME_INTERVAL, pendingIntent);
- 第二个参数表示任务首次执行时间:与第一个参数密切相关。第一个参数若为
AlarmManager.ELAPSED_REALTIME_WAKEUP
,那么当前时间就为SystemClock.elapsedRealtime()
;若为AlarmManager.RTC_WAKEUP
,那么当前时间就为System.currentTimeMillis()
; - 第三个参数表示两次执行的间隔时间:这个参数没什么好讲的,一般为常量;
- 第四个参数表示对应的响应动作:一般都是去发送广播,然后在广播接收
onReceive(Context context, Intent intent)
中做相关操作。
6.0之后的版本,SDK API >= 23
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(), pendingIntent);