AlarmManager.setRepeating()不精确, 重复Alarm都不好用了,想用的话每次重复设置吧!!!!
================================================================================
238 * Note: as of API 19, all repeating alarms are inexact. If your
239 * application needs precise delivery times then it must use one-time
240 * exact alarms, rescheduling each time as described above. Legacy applications
241 * whose {@code targetSdkVersion} is earlier than API 19 will continue to have all
242 * of their alarms, including repeating alarms, treated as exact.
=============================================================================
版权声明:本文为博主原创文章,未经博主允许不得转载。
公司项目已经渡过了研发阶段,现在进入了调试阶段(疯狂改Bug有木有!现在一看到有Bug的邮件过来,就想一拳打爆屏幕,当然也只是想想)。
言归正传! 在最近的Bug中发现一个问题,项目有用到预约的功能,所以就使用了AlarmManager来进行定时提醒的功能。当时开发这个功能的时候并没有发现会有这种Bug,原因是开发时只会预约一个来测试功能是否有实现,实现了就算完成,而这个问题恰好是有多个预约时才会发生的问题(这里的多个预约是指系统中有多个AlarmManager的定时,具体为什么这样说,下面我会提到),比如当我进行了10个预约,也就是设定了10个AlarmManager的时候,我让设备休眠(我用的Type是RTC_WAKEUP),依次观察10个通知的提醒。发现其中会有一两个通知并没有按照我设定的时间唤醒设备,而是会和与他时间相近的下一个预约同时出现(也就是发生了时间不精确的问题)。当时发现这个问题时以为是我在传递预约时间时发生了问题,就打Log查看了一下,发现并没有什么问题,既然时间没有问题,那很有可能是AlarmManager的问题,上网查了一下,在Android的文档中发现了端倪。
官方文档说从API19(android4.4)开始, 为了节能省电(减少系统唤醒和电池使用)。使用Alarm.set()和Alarm.setRepeating()已经不保证精确性(是不是突然一下就蒙了,不精确还怎么用?),不过Google怎么可能会做出这样不合理的设计呢!接着看就会发现其实Google还提供两个精确的Alarm方法,setWindow()和setExact(),看来问题是解决了。那顺便也分析一下AlarmManager吧,分析的过程中发现使用setAlarmClock这个方法也可以实现精准通知,但是有局限性,下面会说到。
浏览了一下官方文档和AlarmManager这个类,发现其实这个类很简单,就是定义了几个Type变量和set、cancle方法(其实AlarmManager的核心在AlarmManagerService中,这个一会儿再说)。这里只介绍一些经常使用的。
经常使用的变量:
其实主要就是分为两类,系统相对时间和绝对时间和是否唤醒CPU。
ELAPSED_REALTIME:使用相对时间,可以通过SystemClock.elapsedRealtime() 获取(从开机到现在的毫秒数,包括手机的睡眠时间),设备休眠时并不会唤醒设备。
ELAPSED_REALTIME_WAKEUP:与ELAPSED_REALTIME基本功能一样,只是会在设备休眠时唤醒设备。
RTC:使用绝对时间,可以通过 System.currentTimeMillis()获取,设备休眠时并不会唤醒设备。
RTC_WAKEUP: 与RTC基本功能一样,只是会在设备休眠时唤醒设备。
还有其他几个变量是针对API19和特殊方法使用的,这里就不具体介绍,可以通过文档查看。
经常使用的方法:
对于方法我不会把每一个都讲一遍,应为所有的set方法其实都是调用内部的一个private的方法setImpl(),只是不同的set方法传入的值不同而已,我就说一下setImpl这个方法。
可以看到setImpl这个方法内部也很简单,就是判断一下triggerAtMillis这个参数,然后调用mService.set方法。那这个mService又是什么呢?
在上面找发现了mService是一个叫IAlarmManager的service,这里变量是红色的,我用AndroidStudio无法再跟踪下去,但是看到这儿我们应该就想到安卓的AIDL通信方式,其实AlarmManager就是用的AIDl通信,从Alarm对象的获取我们也可以看出来
AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
是获取的系统服务,AlarmManager真正的实现都在FrameWork层的AlarmManagerService这个服务中。(从AlarmManager的构造方法中我们也可以看到他进行了API19版本的判断,这里我们还要注意mAlwaysExact这个参数,下面会用到)我先将setImpl的几个参数的含义说一下:
Type:就是我们上面说的那些变量。
triggerAtMillis:alarm的触发时间。
windowMillis:这个参数很繁琐,只有setWindow会主动设置,它的意思是设置一个时间区间,他会在triggerAtMillis的时间开始的这个区间内触发Alarm通知。普通的set()和setRepeating()方法会通过调用legacyExactLength()这个方法返回这个值:
这个方法就用到了我们上面说的mAlwaysExact这个变量,如果是小于API19的版本会使用
WINDOW_EXACT参数,这个参数是0(意思就是区间设置为0,那么就会按照triggerAtMillis这个时间准时触发,也就是精准触发)另一个参数WINDOW_HEURISTIC的值是-1,这个值具体的用法就要看AlarmManagerService具体的实现了,反正只要知道这个值是不精准就可以。剩下的其他set方法直接就将这个值设置为WINDOW_EXACT,那么那几个方法就都是精准通知了,除了setInexactRepeating方法将参数设置为WINDOW_HEURISTIC。
intervalMillis:间隔时间,用到Repeating类型的通知时使用,效果就类似我们设置的闹钟,过10分钟后提醒一次一样。
operation: 就是PendingIntent,没什么说的。
worksource:这个参数我也不知道什么意思,应为所有的方法都设置它为null,有知道的朋友可以告诉我一下。
alarmClock:设置AlarmClockInfo对象,看代码里这个对象实现了Parcelable接口,其实就是对triggerTime和PendingIntent的一个封装类。
那么参数就基本说完了,大家只需要不同方法传递不同参数就可以了。再说一下setAlarmClock这个方法,应为它用的是WINDOW_EXACT这个参数,所以这个方法也是精准的,不过他有一些局限性:
1:他只能在API21版本和之后的版本调用。
2:他的Type是固定的RTC_WAKEUP。
由于好奇心我又在网上查找了一下AlarmManagerService节能省电的原理是什么?通过看很多大神的分析,了解了原理,这里我简单总结一下。如果我们没有设置和使用精准通知的话,系统会把触发时间相近的Alarm放在同一个batch(看名字是一批的意思)中,然后每个bach根据时间排序放在mAlarmBatchs中,前面的就是先要触发的alarm。这就是原理,
也就是我们的Alarm会分为一批一批的一起触发,而不是每个Alarm都要触发。这就是我上面说的系统中有多个Alarm时会发生时间不准的现象,但如果系统中只有几个Alarm,并且他们的触发时间隔得很远,那么我们的Alarm就自己分为一批,触发还是会准的。
好了,对于AlarmManager的分析就到这里。有不对的地方希望大牛指导。