Android 开发 AlarmManager 定时器

介绍

AlarmManager是Android中常用的一种系统级别的提示服务,在特定的时刻为我们广播一个指定的Intent。简单的说就是我们设定一个时间,然后在该时间到来时,AlarmManager为我们广播一个我们设定的Intent,通常我们使用 PendingIntentPendingIntent可以理解为Intent的封装包,简单的说就是在Intent上在加个指定的动作。在使用Intent的时候,我们还需要在执行startActivity、startService或sendBroadcast才能使Intent有用。而PendingIntent的话就是将这个动作包含在内了。

注意!因为在较高的android版本(大概应该是5.0,其他厂商的机器都会有所不同)里,为了节省电源使用了一种在一段时间的所有定时器都会自动合并到一个时间点上一起发出的机制。所以,现在的定时器启动的时间并不是十分精确,它只是一个大概时间。就算你在后续设置了精确版本重复模式也是如此,它只是稍微好一点点。

创建流程

  • 创建Intent用来告诉定时器触发后它要做什么,Intent可以是启动activity、启动Service、发送广播。
  • 创建时间值用来告诉定时器什么时候触发。
  • 创建PendingIntent(等待Intent)用来包裹创建好的Intent。
  • 取得AlarmManager系统服务,强制转型并且实例化
  • 用AlarmManager实例以set方法,添加类型、时间值、PendingIntent参数

简单Demo了解流程

关于更详解的了解在后续说明,我们先简单大概的了解一下使用流程。帮助理解后续更详细的参数说明。

我们创建一个定时器启动Activity的简单demo:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_time);

        mBtnStart = (Button)findViewById(R.id.btn_start);
        mBtnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //正常的创建intent,TimeDemoActivity是我要启动的活动
                Intent intent = new Intent(TimeActivity.this,TimeDemoActivity.class);
                long time = System.currentTimeMillis()+5*1000;//得到当前时间并且增加5秒,表示我们在5秒后触发定时器
                //创建PendingIntent封装了intent
                PendingIntent pi = PendingIntent.getActivity(TimeActivity.this,0,intent,0);
                AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);//得到系统AlarmManager服务
                //设置了AlarmManager类型参数,时间值,PendingIntent
                manager.set(AlarmManager.RTC_WAKEUP,time,pi);

            }
        });
    }
 

 这样就完成了,下面我们来看看效果:

PendingIntent对象

//这是启动activity的PendingIntent
PendingIntent pi = PendingIntent.getActivity(TimeActivity.this,0,intent,0);
  • 启动Activity:  PendingIntent对象的获取就应该采用 PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。
  • 启动服务:  PendingIntent对象的获取就应该采用Pending.getService(Context c,int i,Intent intent,int j)方法;
  • 广播:  PendingIntent对象的获取就应该采用 PendingIntent.getBroadcast(Context c,int i,Intent intent,int j)方法;

  介绍一下参数:

  1. 第一个参数是当前activity或者服务的上下文。
  2. 第二个参数是这个PendingIntent的key代码,是识别这个PendingIntent的关键,在重复添加的定时器的时候,如果有Intent的内容一致(识别不同的PendingIntent也靠Intent是不是有一样的内容(setClass、setAction、setData内容决定Intent是否相同)),但是你还是需要有多个定时器,就需要更新这个参数。
  3. 第三个参数是添加一个需要延迟执行的Intent
  4. 第四个参数是,这个参数很重要这里我需要举一个坑来帮你理解一下为什么很重要:

           如果你有一个PendingIntent创建后并且添加到AlarmManager里,在触发时间之前,如果你需要更新这个AlarmManager里的PendingIntent包裹的Intent的putExtra内容。如果这个时候你把第四个参数设置为0.你就会发现你就算重新把AlarmManager重写set了PendingIntent,它依然不会将Intent里的putExtra传值更新到最新。因为它会觉得你Intent是一样的。所以我们就需要使用2个常设定的参数来更新Intent或者更新PendingIntent。分别如下:

  •  PendingIntent.FLAG_CANCEL_CURRENT   如果AlarmManager管理的PendingIntent已经存在,那么将会取消当前的PendingIntent,从而创建一个新的PendingIntent
  •  PendingIntent.FLAG_UPDATE_CURRENT   如果PendingIntent第二个参数一样不变,则所有对应的Intent里面的数据被更新为最新的, 就是全部为最后一次更新的Intent。 如果PendingIntent第二个参数不一样,就不会更新Intent

AlarmManager详解

常用set方法

  • set(int type,long startTime,PendingIntent pi);该方法用于设置一次性闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟执行时间,第三个参数表示闹钟响应动作。
  • setRepeating(int type,long startTime,long intervalTime,PendingIntent pi);该方法用于设置重复闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟首次执行时间,第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。
  • setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi);该方法也用于设置重复闹钟,重复闹钟的不精确版本,它相对而言更节能(power-efficient)一些,因为系统可能会将几个差不多的闹钟合并为一个来执行,减少设备的唤醒次数。
  • setTimeZone(String timeZone) ;设置系统的默认时区。需要android.permission.SET_TIME_ZONE权限

  注意!
    API 19(Android4.4)开始,AlarmManager的机制是非准确激发的,操作系统会偏移(shift)闹钟来最小化唤醒和电池消耗。不过AlarManager新增了如下两个方法来支持精确激发。

        setExact(int type long triggerAtMillis,PendingIntent operation)
        设置闹钟闹钟将在精确的时间被激发。

        setWindow(int type,long windowStartMillis,long windowLengthMillis,PendingIntent operation)
        设置闹钟将在精确的时间段内被激发。

    很显示API19以后无法使用setInexactRepeating()和setRepeating(),也就是无法设置重复闹钟,唯一解决的方式,也只有启动闹钟的时候再设置一次闹钟,也就变相地实现了重复闹钟了。
    API19以下使用setExact()和setWindow()将会报没有匹配的方法

java.lang.NoSuchMethodError: android.app.AlarmManager.setExact  

在Android6.0后google又更新了最新的设置方式,如果想继续保持Alarm在手机处于所谓Doze模式(6.0后新的电量控制模式)时仍然能够被即时响应,则需要使用AlarmManager新提供的两个方法(但是需要将app添加到电量白名单中)

  • setAndAllowWhileIdle()  这个是日历级别的定时器,在添加了电量白名单后,依然会被Doze模式影响,在Doze模式下的5分钟窗口期会被触发,并且窗口的触发期会越来越久。(可能会被其他定时器影响触发时间)

  • setExactAndAllowWhileIdle()   这个是通知信息级别的定时器,在添加了电量白名单后,依然会被Doze模式影响,在Doze模式下的5分钟窗口期会被触发,并且窗口的触发期会越来越久。

  • setAlarmClock()   这个是闹钟级别的定时器,添加电量白名单后,会有最高的触发精度。(另外怀疑这个定时器最高级别独立的,不会因为其他定时器而影响触发时间)

所以最后你可能需要写出这样的三次判断来兼容全部版本的定时器:

if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M) {
  alarmMgr.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendIntent); }
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
  alarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendIntent); }
else{
   alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendIntent); }

set方法各参数详解

不介绍setTimeZone方法

int type 参数

闹钟的类型,常用的有5个值:AlarmManager.ELAPSED_REALTIME、 AlarmManager.ELAPSED_REALTIME_WAKEUP、AlarmManager.RTC、 AlarmManager.RTC_WAKEUP、AlarmManager.POWER_OFF_WAKEUP。

  • AlarmManager.ELAPSED_REALTIME表示闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;
  • AlarmManager.ELAPSED_REALTIME_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;
  • AlarmManager.RTC表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;
  • AlarmManager.RTC_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;
  • AlarmManager.POWER_OFF_WAKEUP表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;

long startTime 参数

闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对 应的闹钟使用的是相对时间(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间(相对于 系统启动时间来说),比如当前时间就表示为:SystemClock.elapsedRealtime();如果第一个参数对应的闹钟使用的是绝对时间 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那么本属性就得使用绝对时间,比如当前时间就表示 为:System.currentTimeMillis()。

long intervalTime 参数

对于后两个方法来说,存在本属性,表示两次闹钟执行的间隔时间,也是以毫秒为单位。这里系统代码上已经提供了一些快捷的间隔时间,让你可以不需要在另外计算一天的毫秒级时间:

  • INTERVAL_DAY = 86400000L;//设置闹钟,间隔一天
  • INTERVAL_FIFTEEN_MINUTES = 900000L; //设置闹钟,间隔15分钟
  • INTERVAL_HALF_DAY = 43200000L; //设置闹钟,间隔半天
  • INTERVAL_HALF_HOUR = 1800000L; //设置闹钟,间隔半个小时
  • INTERVAL_HOUR = 3600000L; //设置闹钟,间隔一个小时

使用方法:

manager.setRepeating(AlarmManager.RTC_WAKEUP,time+10*1000,AlarmManager.INTERVAL_DAY,pi);

PendingIntent pi 参数

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

取消已经注册的AlarmManager

AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
Intent intent = new Intent(AlarmTest.this, AlarmActivity.class); 
intent.setAction("111111");
PendingIntent pendingIntent = PendingIntent.getActivity( AlarmTest.this, 0, intent, 0); 
manager.cancel(pendingIntent);

说明一下为什么需要setAction,因为有一种情况就是多个 AlarmManager 都在不同时间段要启动相同activity的intent,所以为了区分不同时段的intent,我们需要添加setAction。这样取消的时候只会取消指定的intent。注意!如果你添加了2个一样的Intent到定时器里,那么第一个被添加到定时器的Intent会自动被取消。

了解时间值的创建

请移步到本人另一篇博客了解Java的时间值操作与获取:https://blog.csdn.net/qq_37217804/article/details/81741684取消
 

Demo演示

定时器广播Demo_1

注意!必需静态广播!

在AndroidManifest.xml静态广播注册与action接收器注册:

<receiver
            android:name=".ui.MyReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.xxx.xxx.MyReceiver"/>
            </intent-filter>
        </receiver>

创建接收器class:

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("com.yt.owl.MyReceiver")) {
            Toast.makeText(context, "定时器广播成功接收", Toast.LENGTH_SHORT).show();
        }
    }
}

创建定时器:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_time);

        mBtnStart = (Button)findViewById(R.id.btn_start);
        mBtnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mFormat = new SimpleDateFormat("yyyy年MM月dd日 : HH时mm分ss秒");
                long time = System.currentTimeMillis();
                Log.e(TAG, "TIME1: "+time);
                Intent intent = new Intent(TimeActivity.this,MyReceiver.class);
                intent.setAction("com.xxx.xxx.MyReceiver");
                PendingIntent pt = PendingIntent.getBroadcast(TimeActivity.this,0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
                AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
                manager.set(AlarmManager.RTC_WAKEUP,time+5*1000,pt);
                Toast.makeText(TimeActivity.this,"定时器启动 触发时间"+mFormat.format(time+5*1000),Toast.LENGTH_SHORT).show();
                
            }
        });
    }

效果图:

定时器广播Demo_2

当然我们可以不添加action

AndroidManifest.xml注册静态广播

        <receiver
            android:name=".ui.MyReceiver"
            android:enabled="true"
            android:exported="true">
        </receiver>

创建广播class

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "定时器广播成功接收", Toast.LENGTH_SHORT).show();
    }
}

创建定时器:

mBtnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                mFormat = new SimpleDateFormat("yyyy年MM月dd日 : HH时mm分ss秒");
                long time = System.currentTimeMillis();
                Log.e(TAG, "TIME1: "+time);
                Intent intent = new Intent(TimeActivity.this,MyReceiver.class);
                PendingIntent pi = PendingIntent.getBroadcast(TimeActivity.this,0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
                AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
                manager.set(AlarmManager.RTC_WAKEUP,time+5*1000,pi);
                Toast.makeText(TimeActivity.this,"定时器启动 触发时间"+mFormat.format(time+5*1000),Toast.LENGTH_SHORT).show();

            }
     });
原文地址:https://www.cnblogs.com/guanxinjing/p/9708572.html