Android中AlarmManager的使用

本篇博客的部分内容参考了:https://www.cnblogs.com/ProtectedDream/p/6351447.html

 

最近在写一个 “抢订羽毛球场地” 的 app,有个开抢时间的设定,抢订时需要唤醒手机,并开启目标 app 进行预订操作。

因此需要用到可以摆脱 Android Dozen 模式的 AlarmManager。

在介绍 AlarmManager 之前,先简单了解下 Calendar。

Calendar 简介

Calendar 是 JDK 提供的一个时间信息的类。

YEAR = 2018,
MONTH = 9, //0月起步,9月
WEEK_OF_YEAR = 41,
WEEK_OF_MONTH = 2,
DAY_OF_MONTH = 10, //1日起步,10日
DAY_OF_YEAR = 283,
DAY_OF_WEEK = 4, //周三,对应周日起步
DAY_OF_WEEK_IN_MONTH = 2,
AM_PM = 1,
HOUR = 5,//12小时制
HOUR_OF_DAY = 17,//24小时制
MINUTE = 35,
SECOND = 0,
MILLISECOND = 0,
ZONE_OFFSET = 28800000,
DST_OFFSET = 0

以上贴出Calendar的部分字段信息

Calendar的使用方法:

Calendar c = Calendar.getInstance();  //获取系统当前日历信息,其中包含了以上当前实时时间的字段信息

      c.set(Calendar.YEAR,2016);
      c.set(Calendar.MONTH,04); //也可以填数字,0-11,一月为0
      c.set(Calendar.DAY_OF_MONTH, 26);
      c.set(Calendar.HOUR_OF_DAY, 21);
      c.set(Calendar.MINUTE, 40);
      c.set(Calendar.SECOND, 0);
//设定时间为 2011年6月28日19点50分0秒
//c.set(2011, 05,28, 19,50, 0);

      c.get(Calendar.YEAR);
      c.get(Calendar.MONTH);
      c.set(Calendar.DAY_OF_MONTH);

以上,Calendar中分别提供了set()、get(),用于设置和获取各个字段信息。

我的 app 中使用的 AlarmManager 代码:


        AlarmManager manager = (AlarmManager) context.getSystemService(ALARM_SERVICE);//获取AlarmManager实例

        Intent intent2 = new Intent(context, TimerReceiver.class); //TimerReceiver是自己定义的一个BroadcastReceiver
        intent2.setAction("BroadcastReceiver_to_Start_Reservation");
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent2, 0);

        Log.w(TAG, "startAppAt: start TimerReceiver to handle");
        manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calender.getTimeInMillis(), pi);//开启提醒

AlarmManager介绍:

AlarmManager 是 Android 中常用的一种系统级别的提示服务,在特定的时刻为我们广播一个指定的 Intent。

简单的说就是我们设定一个时间,然后在该时间到来时,AlarmManager 为我们广播一个我们设定的 Intent, 通常我们使用 PendingIntent,PendingIntent 可以理解为 Intent 的封装包,简单的说就是在Intent上在加个指定的动作。

在使用Intent的时候,我们还需要在执行 startActivity、startService 或 sendBroadcast 才能使 Intent 有用。而 PendingIntent 的话就是将这个动作包含在内了。

定义一个PendingIntent对象。


PendingIntent pi = PendingIntent.getBroadcast(this,0,intent,0);

 

AlarmManager的常用方法有三个:

(1)set(int type,long startTime,PendingIntent pi);

该方法用于设置一次性闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟执行时间,第三个参数表示闹钟响应动作。

(2)setRepeating(int type,long startTime,long intervalTime,PendingIntent pi);

该方法用于设置重复闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟首次执行时间,第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。

(3)setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi);

该方法也用于设置重复闹钟,与第二个方法相似,不过其两个闹钟执行的间隔时间不是固定的而已。

 

注意: 高版本的 Android 中使用了 Dozen 模式,该模式会使以上函数的定时在生效时出现一定误差:

为此,最新版本的AlarmManager提供了setExactAndAllowWhileIdle() 函数,最大限度的保证定时操作被准时执行,即相关service,activity 或 broadcast 被准时唤起。

 

三个方法各个参数详悉:

(1)int type: 闹钟类型

常用的有5个值:

AlarmManager.ELAPSED_REALTIME

表示闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;

AlarmManager.ELAPSED_REALTIME_WAKEUP

表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;

AlarmManager.RTC

表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间(这个绝对时间其实是相对于1970年一月一日),状态值为1;

AlarmManager.RTC_WAKEUP

表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;

AlarmManager.POWER_OFF_WAKEUP

表示闹钟在手机关机状态下也能正常进行提示功能,所以是 5 个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为 4;不过本状态好像受 SDK 版本影响,某些版本并不支持;

(2)long startTime: 

闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。需要注意的是,本属性与第一个属性(type)密切相关

(3)long intervalTime:

对于后两个方法来说,存在本属性,表示两次闹钟执行的间隔时间,也是以毫秒为单位。

以下 AlarmManager 中的字段会用于这个参数的设置:

INTERVAL_DAY 设置闹钟,间隔一天
INTERVAL_HALF_DAY 设置闹钟,间隔半天
INTERVAL_FIFTEEN_MINUTES 设置闹钟,间隔15分钟
INTERVAL_HALF_HOUR 设置闹钟,间隔半个小时
INTERVAL_HOUR 设置闹钟,间隔一个小时

(4)PendingIntent pi:

绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。

PendingIntent是Intent的封装类。

需要注意的是:

如果是通过启动服务来实现闹钟提示的话,PendingIntent 对象的获取就应该采用 Pending.getService(Context c,int i,Intent intent,int j) 方法;

如果是通过广播来实现闹钟提示的话,PendingIntent 对象的获取就应该采用 PendingIntent.getBroadcast(Context c,int i,Intent intent,int j) 方法;

如果是采用 Activity 的方式来实现闹钟提示的话,PendingIntent 对象的获取就应该采用 PendingIntent.getActivity(Context c,int i,Intent intent,int j) 方法。

如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。

注意:

1. 设置闹钟使用 AlarmManager.set() 函数。

它的 triggerAtTime 参数,如果要用 Calendar.getTimesInMillis() 获得,就必须先设置 Calendar 对象,例如要让闹钟在当天的16:30分启动,就要设置HOUR_OF_DAY(16)、MINUTE(30)、MILLISECOND(0),特别是HOUR_OF_DAY,我一开始误用了 HOUR,这是12进制计时方法,HOUR_OF_DAY 是 24 进制计时方法。

2. 针对同一个 PendingIntent,AlarmManager.set() 函数不能设置多个 alarm。

调用该函数时,假如已经有 old alarm 使用相同的PendingIntent,会先取消(cancel)old alarm,然后再设置新的 alarm。如何判断是否已经有相同的 PendingIntent,请看下条。

3. 取消 alarm 使用 AlarmManager.cancel() 函数,传入参数是个 PendingIntent 实例。

该函数会将所有跟这个 PendingIntent 相同的 Alarm 全部取消,怎么判断两者是否相同,android 使用的是 intent.filterEquals(),具体就是判断两个 PendingIntent 的 action、data、type、class 和 category 是否完全相同。

4. 在AndroidManifest.xml 中静态注册 BroadcastReceiver 时,一定使用android:process=":xxx"属性,因为SDK已注明:If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the broadcast receiver runs in that process.

 

在此讨论一下 process 属性,它规定了组件 (activity, service, receiver等)所在的进程。

  通常情况下,没有指定这个属性,一个应用所有的组件都运行在应用的默认进程中,进程的名字和应用的包名一致。

  比如 manifest 的package="com.example.helloalarm",则默认进程名就是com.example.helloalarm。

  元素的 process 属性可以为全部的组件设置一个不同的默认进程。

  组件可以 override 这个默认的进程设置,这样你的应用就可以是多进程的。

 

  如果你的 process 属性以一个冒号开头,进程名会在原来的进程名之后附加冒号之后的字符串作为新的进程名。当组件需要时,会自动创建这个进程。这个进程是应用私有的进程。

  如果 process 属性以小写字母开头,将会直接以属性中的这个名字作为进程名,这是一个全局进程,这样的进程可以被多个不同应用中的组件共享。

 

针对不同版本的Android上AlarmManager机制的不同,要在不同版本上保证定时被触发的实时性,可以参考以下博客中给出的解决方案:https://www.jianshu.com/p/d69a90bc44c0

// pendingIntent 为发送广播
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else {
    alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
}

private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 重复定时任务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
        }
        // to do something
        doSomething();
    }
};

作者:俞其荣
链接:https://www.jianshu.com/p/d69a90bc44c0
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Android基础)