很简单,没有什么特殊需要说明。API 19 之前 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)// 该方法用于设置可重复执行的定时器。与 setRepeating 相比,这个方法更加考虑系统电量,比如系统在低电量情况下可能不会严格按照设定的间隔时间执行闹钟,因为系统可以调整报警的交付时间,使其同时触发,避免超过必要的唤醒设备。参数说明:int type:闹钟类型,常用有五个类型,说明如下:
- 1 am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
AlarmManager.ELAPSED_REALTIME | 表示闹钟在手机睡眠状态下不可用,就是睡眠状态下不具备唤醒 CPU 的能力(跟普通 Timer 差不多了),该状态下闹钟使用相对时间, 相对于系统启动开始。 |
AlarmManager.ELAPSED_REALTIME_WAKEUP | 表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间 |
AlarmManager.RTC | 表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间 |
AlarmManager.RTC_WAKEUP | 表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间 |
AlarmManager.POWER_OFF_WAKEUP | 表示闹钟在手机关机状态下也能正常进行提示功能,5 个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间 |
long startTime: 闹钟的第一次执行时间,以毫秒为单位。需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对应的闹钟使用的是相对时间(ELAPSED_REALTIME 和 ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间,比如当前时间就表示为:SystemClock.elapsedRealtime();如果第一个参数对应的闹钟使用的是绝对时间 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那么本属性就得使用绝对时间,当前时间就表示 为:System.currentTimeMillis()。
long intervalTime: 表示两次闹钟执行的间隔时间,也是以毫秒为单位。
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) 方法。关于 PendingInten 不是本文重点,请自行查阅使用方法。
使用举例:需求,定义一个在 CPU 休眠情况下也能执行的闹钟,每隔 5 秒发送一次广播,代码如下:三、AlarmManager 的版本适配
- Intent intent = new Intent("WANG_LEI");
- intent.putExtra("msg", "起床了啊");
- PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
- AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
- // 每隔5秒后通过PendingIntent pi对象发送广播
- am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 5 * 1000, pi);
以上讲解在 API<19 的情况下能正常运行,但是在 API>=19 和 API<=23 手机上运行会发现尼玛怎么不好使了,比如我们设置 1 分钟执行一次,真正运行起来却变成 3 分钟执行一次,这不是坑爹吗。这是为什么呢?查阅谷歌文档会发现,关于 4.4 版本有如下描述:
看到了吧,4.4 及以上版本谷歌进行了优化,怎么优化的呢?这样说吧之前版本比如手机上装了两个应用 A,B 均使用了 AlarmManager,A 应用设定 5 秒唤醒一次 CPU 执行任务,B 应用设定 7 秒唤醒一次 CPU 执行任务,在 API<19 手机上这样运行没问题的,5 秒一次,7 秒一次轮着唤醒 CPU 干活,但是到了 4.4 及以上版本这样就不行了,谷歌一想老子出的这功能都被你们玩坏了,照这样下去小刘 5 秒一次,小徐 6 秒一次,小江 7 秒一次 CPU 不停地被唤醒这用户电量都被消耗没了 (唤醒 CPU 是很耗电的),好,老子直接优化一下,针对这种情况老子统一进行批处理了,你们都给我 7 秒唤醒一次 CPU,这一次你们三个活都干了。大体优化逻辑就是这样子。
BUT,凡是都有但是啊,你要想在 API>=19 和 API<=23 手机上照样能正常运行咋办,谷歌还是很贴心的提供额外 API,使用 setExact(int type, long triggerAtMillis, PendingIntent operation) 就可以了。(具体使用代码文章下面会有,别急) 满心欢舞的我们修改完后继续运行了。BUT,在 6.0 及以上手机又出问题了,手机在进入休眠状态一段时间后 AlarmManager 不工作了,真是服了,继续找问题吧,发现 6.0 中谷歌对低电耗模式和应用待机模式 (6.0 开始引入) 进行了优化,描述如下(原文链接:https://developer.android.google.cn/training/monitoring-device-state/doze-standby.html):
看到了吧,描述很清楚了,仔细读一下就明白了,但是同样为我们提供了对应 API 解决:setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)。到此 AlarmManager 的版本适配就完了,但是还有一个问题 setExact(int type, long triggerAtMillis, PendingIntent operation) 以及 setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) 方法都没有重复提醒的设置,没有 setRepeating 类似 API,都是一次性的闹钟,我们怎么实现每隔一段时间执行一次任务的需求呢?很简单,重复注册就可以了,这么说不明白的话请继续看下文,Demo 会讲到。
四、AlarmManager 实例 Demo 讲解(包含版本适配以及高版本设置重复闹钟)好了经过上面讲解,我相信你是似懂非懂的,因为没看到具体代码啊,简单,一个小 Demo 就全都明白了。
实现功能:在 CPU 休眠情况下依然可以每隔五秒发送一次广播,在广播接收者中执行相应逻辑 (Demo 中只是打印 Log), 适配各个版本。
先看一下最核心的 AlarmManagerUtils 类:
AlarmManagerUtils 就是将与 AlarmManager 有关的操作都封装起来了,方便解耦。很简单,主要就是版本适配了,上面已经讲解够仔细了,这里就是判断不同版本调用不同 API 了。MainActivity 代码:
- public class AlarmManagerUtils {
- private static final long TIME_INTERVAL = 5 * 1000;//闹钟执行任务的时间间隔
- private Context context;
- public static AlarmManager am;
- public static PendingIntent pendingIntent;
- //
- private AlarmManagerUtils(Context aContext) {
- this.context = aContext;
- }
- //饿汉式单例设计模式
- private static AlarmManagerUtils instance = null;
- public static AlarmManagerUtils getInstance(Context aContext) {
- if (instance == null) {
- synchronized (AlarmManagerUtils.class) {
- if (instance == null) {
- instance = new AlarmManagerUtils(aContext);
- }
- }
- }
- return instance;
- }
- public void createGetUpAlarmManager() {
- am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent("WANG_LEI");
- intent.putExtra("msg", "赶紧起床");
- pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);//每隔5秒发送一次广播
- }
- @SuppressLint("NewApi")
- public void getUpAlarmManagerStartWork() {
- //版本适配
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
- am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
- System.currentTimeMillis(), pendingIntent);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4及以上
- am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
- pendingIntent);
- } else {
- am.setRepeating(AlarmManager.RTC_WAKEUP,
- System.currentTimeMillis(), TIME_INTERVAL, pendingIntent);
- }
- }
- @SuppressLint("NewApi")
- public void getUpAlarmManagerWorkOnReceiver() {
- //高版本重复设置闹钟达到低版本中setRepeating相同效果
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
- am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
- System.currentTimeMillis() + TIME_INTERVAL, pendingIntent);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4及以上
- am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
- + TIME_INTERVAL, pendingIntent);
- }
- }
- }
- public class MainActivity extends Activity {
- private AlarmManagerUtils alarmManagerUtils;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- //
- alarmManagerUtils = AlarmManagerUtils.getInstance(this);
- alarmManagerUtils.createGetUpAlarmManager();
- //
- findViewById(R.id.am).setOnClickListener(new OnClickListener() {
- @SuppressLint("NewApi")
- @Override
- public void onClick(View v) {
- //
- alarmManagerUtils.getUpAlarmManagerStartWork();
- }
- });
- }
- }
MainActivity 中就是调用 AlarmManagerUtils 中已经封装好的代码进行初始化以及点击 Button 的时候调用 getUpAlarmManagerStartWork 方法完成第一次触发 AlarmManager。
最后看下广播接收者中具体做了什么。
MyBroadcastReceiver 类:
- public class MyBroadcastReceiver extends BroadcastReceiver {
- private static final String TAG = "MyBroadcastReceiver";
- @SuppressLint("NewApi")
- @Override
- public void onReceive(Context context, Intent intent) {
- //高版本重复设置闹钟达到低版本中setRepeating相同效果
- AlarmManagerUtils.getInstance(context).getUpAlarmManagerWorkOnReceiver();
- //
- String extra = intent.getStringExtra("msg");
- Log.i(TAG, "extra = " + extra);
- }
- }
在 onReceive 方法中再次注册一下 AlarmManager 达到低版本中 setRepeating 相同效果。
好了,Demo 中核心就是 AlarmManagerUtils 类,看懂了就全懂了,还需要自己去慢慢研究明白。四、AlarmManager 疑难问题总结 1:进程被杀死,AlarmManager 停止工作在 Demo 运行的过程中发现我们主动杀死进程 AlarmManager 也就停止运行了,Log 停止打印。我们只能在应用打开或者应用中存在服务在服务重启的时候重新注册一下 AlarmManager,我还没有发现什么其余好的办法,如果你有好的解决办法,请留言给与解答,向您请教。2:手机重启,AlarmManager 停止工作其实这个问题和上面进程被杀死情况差不多,这种情况我们可以注册一个监听手机重启的广播,在收到广播的时候重新注册一下 AlarmManager 就可以了。3:各厂商的 "心跳对齐" 小米,华为等手机厂商,都有 "心跳对齐" 机制,比如我们开发一个 APP,在后台 2s 就要唤醒一次 CPU 执行任务,上面说过唤醒 CPU 是耗电的 (2s 就唤醒一次,我只能说产品经理有问题,实现者需求的程序更有问题)。各大厂商检测到你 APP 这么频繁唤醒 CPU 对用户来说是很耗电的,所以系统会将你应用强制 "心跳对齐",使你的 APP 不那么频繁唤醒 CPU。比如你 APP 设定的是 2s 执行一次,但是实际在各大厂商运行起来是 10s 一次。五、总结好了,本文到此就该结束了,相信经过以上讲述你对 AlarmManager 有了更进一步全面了解,在我们使用的时候请不要滥用,考虑一下用户电量,尽量优化自己 APP。吐槽一下,博客园编辑器怎么那么难用,全屏切换总出问题,代码字体大小我这怎么设置大小不起作用,发表出来总是那么小,小,小!!!
来源: https://www.cnblogs.com/leipDao/p/8203684.html