Android中Alarm的机制

五陵年少金市东,银鞍白马渡春风。这篇文章主要讲述Android中Alarm的机制相关的知识,希望能为你提供帮助。
本次给大家分析的是Android中Alarm的机制所用源码为最新的android4.4.4。首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Timer以及Handler在完成定时任务上的差别,最后分析Alarm机制的源码。

什么是AlarmAlarm是android提供的用于完成闹钟式定时任务的类,系统通过AlarmManager来管理所有的Alarm,Alarm支持一次性定时任务和循环定时任务,它的使用方式很简单,这里不多做介绍,只给出一个简单的示例:
[java] view plaincopy

Android中Alarm的机制

文章图片
Android中Alarm的机制

文章图片
  1. AlarmManager  alarmMgr  =  (AlarmManager)  getSystemService(Context.ALARM_SERVICE);    
  2. Intent  intent  =  new  Intent(getApplicationContext(),  TestActivity.class);    
  3. PendingIntent  pendIntent  =  PendingIntent.getActivity(getApplicationContext(),   
  4.                 0,  intent,  PendingIntent.FLAG_UPDATE_CURRENT);    
  5. //5秒后发送广播,只发送一次   
  6. int  triggerAtTime  =  SystemClock.elapsedRealtime()  +  5  *  1000;    
  7. alarmMgr.set(AlarmManager.ELAPSED_REALTIME,  triggerAtTime,  pendIntent);  
Alarm和Timer以及Handler在定时任务上的区别相同点:
三者都可以完成定时任务,都支持一次性定时和循环定时(注:Handler可以间接支持循环定时任务)
不同点:
Handler和Timer在定时上是类似的,二者在系统休眠的情况下无法正常工作,定时任务不会按时触发。Alarm在系统休眠的情况下可以正常工作,并且还可以决定是否唤醒系统,同时Alarm在自身不启动的情况下仍能正常收到定时任务提醒,但是当系统重启或者应用被杀死的情况下,Alarm定时任务会被取消。另外,从Android4.4开始,Alarm事件默认采用非精准方式,即定时任务可能会有小范围的提前或延后,当然我们可以强制采用精准方式,而在此之前,Alarm事件都是精准方式。
Alarm与Binder的交互Alarm由AlarmManager来管理,从使用方式来看,AlarmManager很简单,我们只要得到了AlarmManager的对象,就可以调用set方法来设定定时任务了,而如何得到AlarmManager对象呢?也很简单,AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);下面我们去看看AlarmManager的set方法,当然AlarmManager还有setRepeating方法,但是二者是类似的。为了更好地理解下面的内容,需要你了解AIDL,如果你还不了解,请参看android跨进程通信(IPC):使用AIDL。
code:AlarmManager#set
[java] view plaincopy
Android中Alarm的机制

文章图片
Android中Alarm的机制

文章图片
  1. public  void  set(int  type,  long  triggerAtMillis,  PendingIntent  operation)  {   
  2.         setImpl(type,  triggerAtMillis,  legacyExactLength(),  0,  operation,  null);    
  3. }   
  4.    
  5. public  void  set(int  type,  long  triggerAtMillis,  long  windowMillis,  long  intervalMillis,   
  6.                 PendingIntent  operation,  WorkSource  workSource)  {   
  7.         setImpl(type,  triggerAtMillis,  windowMillis,  intervalMillis,  operation,  workSource);    
  8. }   
  9.    
  10. private  void  setImpl(int  type,  long  triggerAtMillis,  long  windowMillis,  long  intervalMillis,   
  11.                 PendingIntent  operation,  WorkSource  workSource)  {   
  12.         if  (triggerAtMillis  <   0)  {   
  13.                 /*  NOTYET 
  14.                 if  (mAlwaysExact)  { 
  15.                         //  Fatal  error  for  KLP+  apps  to  use  negative  trigger  times 
  16.                         throw  new  IllegalArgumentException("Invalid  alarm  trigger  time  " 
  17.                                         +  triggerAtMillis);  
  18.                 } 
  19.                 */   
  20.                 triggerAtMillis  =  0;    
  21.         }   
  22.    
  23.         try  {   
  24.                 //定时任务实际上都有mService来完成,也就是说AlarmManager只是一个空壳   
  25.                 //从下面的构造方法可以看出,这个mService是IAlarmManager类型的,而IAlarmManager是一个接口   
  26.                 //如果大家了解AIDL就应该知道IAlarmManager应该是一个AIDL接口   
  27.                 mService.set(type,  triggerAtMillis,  windowMillis,  intervalMillis,  operation,   
  28.                                 workSource);    
  29.         }  catch  (RemoteException  ex)  {   
  30.         }   
  31. }   
  32.    
  33. AlarmManager(IAlarmManager  service,  Context  ctx)  {   
  34.         mService  =  service;    
  35.    
  36.         final  int  sdkVersion  =  ctx.getApplicationInfo().targetSdkVersion;    
  37.         mAlwaysExact  =  (sdkVersion  <   Build.VERSION_CODES.KITKAT);    
  38. }   
说明:我对代码进行了注释,从注释可以看出,现在我们需要去找到这个mService,其实我已经帮大家找到了,它就是AlarmManagerService
 
Alarm机制分析通过上面的一系列分析,我们知道AlarmManager的所有功能都是通过AlarmManagerService来完成的,在分析源码之前,我先来描述下Alarm的工作原理:从Android4.4开始,Alarm默认为非精准模式,除非显示指定采用精准模式。在非精准模式下,Alarm是批量提醒的,每个alarm根据其触发时间和最大触发时间的不同会被加入到不同的batch中,同一个batch的不同alarm是同时发生的,这样就无法实现精准闹钟,官方的解释是批量处理可以减少设备被唤醒次数以及节约电量,不过针对精准闹钟,官方预留的方法是setExact和setWindow,二者都是通过将时间窗口定义为0来实现精准闹钟的,因为时间窗口为0,意味着触发时间和最大触发时间是一样的,因为典型的情况下:最大触发时间= 触发时间 + 时间窗口。同时所有的batch是按开始时间升序排列的,在一个batch内部,不同的闹钟也是按触发时间升序排列的,所以闹钟的唤醒顺序是按照batch的排序依次触发的,而同一个batch中的alarm是同时触发的,可以用下面这个示意图来描述:
Android中Alarm的机制

文章图片

 
上图是示意图,系统中可以有多个batch,每个batch中可以有多个alarm。下面我们分析一下AlarmManagerService中的代码。其入口方法为set,set又调用了setImplLocked,所以我们直接看setImplLocked。
code:AlarmManagerService#setImplLocked
[java] view plaincopy
Android中Alarm的机制

文章图片
Android中Alarm的机制

文章图片
  1. private  void  setImplLocked(int  type,  long  when,  long  whenElapsed,  long  maxWhen,  long  interval,   
  2.                 PendingIntent  operation,  boolean  isStandalone,  boolean  doValidate,   
  3.                 WorkSource  workSource)  {   
  4.         /**创建一个alarm,其中各参数的含义如下: 
  5.           *  type  闹钟类型  ELAPSED_REALTIME、RTC、RTC_WAKEUP等 
  6.           *  when  触发时间  UTC类型,绝对时间,通过System.currentTimeMillis()得到 
  7.           *  whenElapsed  相对触发时间,自开机算起,含休眠,通过SystemClock.elapsedRealtime()得到 
  8.           *  maxWhen  最大触发时间 
  9.           *  interval  触发间隔,针对循环闹钟有效 
  10.           *  operation  闹钟触发时的行为,PendingIntent类型 
  11.           */   
  12.         Alarm  a  =  new  Alarm(type,  when,  whenElapsed,  maxWhen,  interval,  operation,  workSource);    
  13.         //根据PendingIntent删除之前已有的同一个闹钟   
  14.         removeLocked(operation);    
  15.    
  16.         boolean  reschedule;    
  17.         //尝试将alarm加入到合适的batch中,如果alarm是独立的或者无法找到合适的batch去容纳此alarm,返回-1   
  18.         int  whichBatch  =  (isStandalone)  ?  -1  :  attemptCoalesceLocked(whenElapsed,  maxWhen);    
  19.         if  (whichBatch  <   0)  {   
  20.                 //没有合适的batch去容纳alarm,则新建一个batch   
  21.                 Batch  batch  =  new  Batch(a);    
  22.                 batch.standalone  =  isStandalone;    
  23.                 //将batch加入mAlarmBatches中,并对mAlarmBatches进行排序:按开始时间升序排列   
  24.                 reschedule  =  addBatchLocked(mAlarmBatches,  batch);    
  25.         }  else  {   
  26.                 //如果找到合适了batch去容纳此alarm,则将其加入到batch中   
  27.                 Batch  batch  =  mAlarmBatches.get(whichBatch);    
  28.                 //如果当前alarm的加入引起了batch开始时间和结束时间的改变,则reschedule为true   
  29.                 reschedule  =  batch.add(a);    
  30.                 if  (reschedule)  {   
  31.                         //由于batch的起始时间发生了改变,所以需要从列表中删除此batch并重新加入、重新对batch列表进行排序   
  32.                         mAlarmBatches.remove(whichBatch);    
  33.                         addBatchLocked(mAlarmBatches,  batch);    
  34.                 }   
  35.         }   
  36.    
  37.         if  (DEBUG_VALIDATE)  {   
  38.                 if  (doValidate  & &   !validateConsistencyLocked())  {   
  39.                         Slog.v(TAG,  "Tipping-point  operation:  type="  +  type  +  "  when="  +  when   
  40.                                         +  "  when(hex)="  +  Long.toHexString(when)   
  41.                                         +  "  whenElapsed="  +  whenElapsed  +  "  maxWhen="  +  maxWhen   
  42.                                         +  "  interval="  +  interval  +  "  op="  +  operation   
  43.                                         +  "  standalone="  +  isStandalone);    
  44.                         rebatchAllAlarmsLocked(false);    
  45.                         reschedule  =  true;    
  46.                 }   
  47.         }   
  48.    
  49.         if  (reschedule)  {   
  50.                 rescheduleKernelAlarmsLocked();    
  51.         }   
  52. }   
说明:通过上述代码可以看出,当我们创建一个alarm的时候,仅仅是将这个alarm加入到某个batch中,系统中有一个batch列表,专门用于存储所有的alarm。可是仅仅把alarm加入到batch中还不行,系统还必须提供一个类似于Looper的东西一直去遍历这个列表,一旦它发现有些alarm的时间已经到达就要把它取出来去执行。事实上,AlarmManagerService中的确有一个类似于Looper的东西去干这个事情,只不过它是个线程,叫做AlarmThread。下面看它的代码:
code:AlarmManagerService#AlarmThread
[java] view plaincopy
Android中Alarm的机制

文章图片
Android中Alarm的机制

文章图片
  1. private  class  AlarmThread  extends  Thread   
  2. {   
  3.         public  AlarmThread()   
  4.         {   
  5.                 super("AlarmManager");    
  6.         }   
  7.            
  8.         public  void  run()   
  9.         {   
  10.                 //当前时间触发的alarm列表   
  11.                 ArrayList< Alarm>   triggerList  =  new  ArrayList< Alarm> ();    
  12.    
  13.                 while  (true)   
  14.                 {   
  15.                         //jni方法,顾名思义,阻塞式方法,当有alarm的时候会被唤醒   
  16.                         int  result  =  waitForAlarm(mDescriptor);    
  17.    
  18.                         triggerList.clear();    
  19.    
  20.                         if  ((result  &   TIME_CHANGED_MASK)  !=  0)  {   
  21.                                 if  (DEBUG_BATCH)  {   
  22.                                         Slog.v(TAG,  "Time  changed  notification  from  kernel;   rebatching");    
  23.                                 }   
  24.                                 remove(mTimeTickSender);    
  25.                                 //将所有的alarm重新排序   
  26.                                 rebatchAllAlarms();    
  27.                                 mClockReceiver.scheduleTimeTickEvent();    
  28.                                 Intent  intent  =  new  Intent(Intent.ACTION_TIME_CHANGED);    
  29.                                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING   
  30.                                                 |  Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);    
  31.                                 mContext.sendBroadcastAsUser(intent,  UserHandle.ALL);    
  32.                         }   
  33.                            
  34.                         synchronized  (mLock)  {   
  35.                                 final  long  nowRTC  =  System.currentTimeMillis();    
  36.                                 final  long  nowELAPSED  =  SystemClock.elapsedRealtime();    
  37.                                 if  (localLOGV)  Slog.v(   
  38.                                         TAG,  "Checking  for  alarms...  rtc="  +  nowRTC   
  39.                                         +  ",  elapsed="  +  nowELAPSED);    
  40.    
  41.                                 if  (WAKEUP_STATS)  {   
  42.                                         if  ((result  &   IS_WAKEUP_MASK)  !=  0)  {   
  43.                                                 long  newEarliest  =  nowRTC  -  RECENT_WAKEUP_PERIOD;    
  44.                                                 int  n  =  0;    
  45.                                                 for  (WakeupEvent  event  :  mRecentWakeups)  {   
  46.                                                         if  (event.when  >   newEarliest)  break;    
  47.                                                         n++;   //  number  of  now-stale  entries  at  the  list  head   
  48.                                                 }   
  49.                                                 for  (int  i  =  0;   i  <   n;   i++)  {   
  50.                                                         mRecentWakeups.remove();    
  51.                                                 }   
  52.    
  53.                                                 recordWakeupAlarms(mAlarmBatches,  nowELAPSED,  nowRTC);    
  54.                                         }   
  55.                                 }   
  56.                                 //这个方法会把batch列表中的第一个batch取出来然后加到触发列表中   
  57.                                 //当然,前提是此batch的开始时间不大于当前时间   
  58.                                 //同时,如果是循环闹钟,则会对下次任务进行再次定时   
  59.                                 triggerAlarmsLocked(triggerList,  nowELAPSED,  nowRTC);    
  60.                                 rescheduleKernelAlarmsLocked();    
  61.    
  62.                                 //  遍历触发列表,发送PendingIntent   
  63.                                 for  (int  i=0;   i< triggerList.size();   i++)  {   
  64.                                         Alarm  alarm  =  triggerList.get(i);    
  65.                                         try  {   
  66.                                                 if  (localLOGV)  Slog.v(TAG,  "sending  alarm  "  +  alarm);    
  67.                                                 //这里PendingIntent会被send,结果就是我们的定时任务被执行了   
  68.                                                 alarm.operation.send(mContext,  0,   
  69.                                                                 mBackgroundIntent.putExtra(   
  70.                                                                                 Intent.EXTRA_ALARM_COUNT,  alarm.count),   
  71.                                                                 mResultReceiver,  mHandler);    
  72.                                                    
  73.                                                 //  we  have  an  active  broadcast  so  stay  awake.   
  74.                                                 if  (mBroadcastRefCount  ==  0)  {   
  75.                                                         setWakelockWorkSource(alarm.operation,  alarm.workSource);    
  76.                                                         mWakeLock.acquire();    
  77.                                                 }   
  78.                                                 final  InFlight  inflight  =  new  InFlight(AlarmManagerService.this,   
  79.                                                                 alarm.operation,  alarm.workSource);    
  80.                                                 mInFlight.add(inflight);    
  81.                                                 mBroadcastRefCount++;    
  82.    
  83.                                                 final  BroadcastStats  bs  =  inflight.mBroadcastStats;    
  84.                                                 bs.count++;    
  85.                                                 if  (bs.nesting  ==  0)  {   
  86.                                                         bs.nesting  =  1;    
  87.                                                         bs.startTime  =  nowELAPSED;    
  88.                                                 }  else  {   
  89.                                                         bs.nesting++;    
  90.                                                 }   
  91.                                                 final  FilterStats  fs  =  inflight.mFilterStats;    
  92.                                                 fs.count++;    
  93.                                                 if  (fs.nesting  ==  0)  {   
  94.                                                         fs.nesting  =  1;    
  95.                                                         fs.startTime  =  nowELAPSED;    
  96.                                                 }  else  {   
  97.                                                         fs.nesting++;    
  98.                                                 }   
  99.                                                 if  (alarm.type  ==  ELAPSED_REALTIME_WAKEUP   
  100.                                                                 ||  alarm.type  ==  RTC_WAKEUP)  {   
  101.                                                         bs.numWakeup++;    
  102.                                                         fs.numWakeup++;    
  103.                                                         //针对能唤醒设备的闹钟,这里会做一些唤醒设备的事情   
  104.                                                         ActivityManagerNative.noteWakeupAlarm(   
  105.                                                                         alarm.operation);    
  106.                                                 }   
  107.                                         }  catch  (PendingIntent.CanceledException  e)  {   
  108.                                                 if  (alarm.repeatInterval  >   0)  {   
  109.                                                         //  This  IntentSender  is  no  longer  valid,  but  this   
  110.                                                         //  is  a  repeating  alarm,  so  toss  the  hoser.   
  111.                                                         remove(alarm.operation);    
  112.                                                 }   
  113.                                         }  catch  (RuntimeException  e)  {   
  114.                                                 Slog.w(TAG,  "Failure  sending  alarm.",  e);    
  115.                                         }   
  116.                                 }   
  117.                         }   
  118.                 }   
  119.         }   
  120. }   
说明:上述代码中,AlarmThread会一直循环的跑着,一旦有新的alarm触发,它就会取出一个batch然后逐个发送PendingIntent,具体alarm的触发是由底层来完成的,我没法再继续分析下去。还有就是Alarm中有一些细节,我没有进行很具体的分析,实际上很简单,大家一看就懂。到此为止,Alarm机制的主要流程也分析完了。
总结【Android中Alarm的机制】本文没有详细介绍如何使用Alarm,因为很简单,看一下官方文档或者网上搜一下,到处都是。关于Alarm,有一点需要强调一下:当手机重启或者应用被杀死的时候,Alarm会被删除,因此,如果想通过Alarm来完成长久定时任务是不可靠的,如果非要完成长久定时任务,可以这样:将应用的所有Alarm信息存到数据库中,每次应用启动的时候都重新注册Alarm并更新Alarm的触发时间,通过这种方式就不存在Alarm丢失的情况了。本文很长,耗时8个小时才完成的,感谢大家阅读本文,希望本文能给大家带来一点帮助。



    推荐阅读