android4.4 AlarmManagerService深刻分析

1.概述

        在Android系統中,鬧鐘和喚醒功能都是由Alarm Manager Service控制並管理的。咱們所熟悉的RTC鬧鐘以及定時器都和它有莫大的關係。爲了便於稱呼,我經常也把這個service簡稱爲ALMS。java

        另外,ALMS還提供了一個AlarmManager輔助類。在實際的代碼中,應用程序通常都是經過這個輔助類來和ALMS打交道的。就代碼而言,輔助類只不過是把一些邏輯語義傳遞給ALMS服務端而已,具體怎麼作則徹底要看ALMS的實現代碼了。android

        ALMS的實現代碼並不算太複雜,主要只是在管理「邏輯鬧鐘」。它把邏輯鬧鐘分紅幾個大類,分別記錄在不一樣的列表中。而後ALMS會在一個專門的線程中循環等待鬧鐘的激發,一旦時機到了,就「回調」邏輯鬧鐘對應的動做。數組

        以上只是一些概要性的介紹,下面咱們來看具體的技術細節。安全

 

先看下具體ALMS在應用中的使用app

  1. 1. Intent intent = new Intent(this, OneShotAlarm.class);    
  2. 2. PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);    
  3. 3.     
  4. 4. // 設置警報時間           
  5. 5. Calendar calendar = Calendar.getInstance();    
  6. 6. calendar.setTimeInMillis(System.currentTimeMillis());    
  7. 7. calendar.add(Calendar.SECOND, 30);    
  8. 8.     
  9. 9. // 設置警報時間,除了用Calendar以外,還能夠用    
  10. 10. long firstTime = SystemClock.elapsedRealtime();    
  11. 11.                 
  12. 12. AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);    
  13. 13. // 只會警報一次    
  14. 14. am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);    
  15. 15. // 會重複警報屢次    
  16. 16. am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, 15*1000, sender);    
  17. 17.     
  18. 18. // 要取消這個警報,只要經過PendingIntent就能夠作到    
  19. 19. am.cancel(sender);    

2.AlarmManager

        前文咱們已經說過,ALMS只是服務端的東西。它必須向外提供具體的接口,才能被外界使用。在android平臺中,ALMS的外部接口爲IAlarmManager。其定義位於frameworks\base\core\Java\android\app\IAlarmManager.aidl腳本中,定義截選以下: 框架

interface IAlarmManager {
    void set(int type, long triggerAtTime, in PendingIntent operation);
    void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
    void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
    void setTime(long millis);
    void setTimeZone(String zone);
    void remove(in PendingIntent operation);
}

        在通常狀況下,service的使用者會經過Service Manager Service接口,先拿到它感興趣的service對應的代理I接口,而後再調用I接口的成員函數向service發出請求。因此按理說,咱們也應該先拿到一個IAlarmManager接口,而後再使用它。但是,對Alarm Manager Service來講,狀況略有不一樣,其最多見的調用方式以下:less

manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

其中,getSystemService()返回的再也不是IAlarmManager接口,而是AlarmManager對象。 ide

 

2.1  AlarmManager的成員函數

        AlarmManager的成員函數有:函數

AlarmManager(IAlarmManager service)
publicvoid set(int type,long triggerAtTime, PendingIntent operation)
publicvoid setRepeating(int type,long triggerAtTime,long interval,
PendingIntent operation)
publicvoid setInexactRepeating(int type,long triggerAtTime,long interval,
                                      PendingIntent operation)
publicvoid cancel(PendingIntent operation)
publicvoid setTime(long millis)
publicvoid setTimeZone(String timeZone)即1個構造函數,6個功能函數。基本上徹底和IAlarmManager的成員函數一一對應。  ui

      另外,AlarmManager類中會以不一樣的公共常量來表示多種不一樣的邏輯鬧鐘,在Android 4.0的原生代碼中有4種邏輯鬧鐘: 
1)  RTC_WAKEUP 
2)  RTC 
3)  ELAPSED_REALTIME_WAKEUP 
4)  ELAPSED_REALTIME

      應用側經過調用AlarmManager對象的成員函數,能夠把語義傳遞到AlarmManagerService,並由它進行實際的處理。

 

3.AlarmManagerService

        ALMS的重頭戲在AlarmManagerService中,這個類繼承於IAlarmManager.Stub,因此是個binder實體。它包含的重要成員以下:

其中,mRtcWakeupAlarms等4個ArrayList<Alarm>數組分別對應着前文所說的4種「邏輯鬧鐘」。爲了便於理解,咱們能夠想象在底層有4個「實體鬧鐘」,注意,是4個,不是4類。上面每一類「邏輯鬧鐘」都會對應一個「實體鬧鐘」,而邏輯鬧鐘則能夠有若干個,它們被存儲在ArrayList中,示意圖以下:

 ALMS_002

固然,這裏所說的「實體鬧鐘」只是個概念而已,其具體實現和底層驅動有關,在frameworks層沒必要過多關心。

         Frameworks層應該關心的是那幾個ArrayList<Alarm>。這裏的Alarm對應着邏輯鬧鐘。

 

3.1  邏輯鬧鐘

        Alarm是AlarmManagerService的一個內嵌類Alarm,定義截選以下:

  1. private static class Alarm {  
  2.     public int type;  
  3.     public int count;  
  4.     public long when;  
  5.     public long repeatInterval;  
  6.     public PendingIntent operation;  
  7.     public int uid;  
  8.     public int pid;  
  9.     . . . . . .  
其中記錄了邏輯鬧鐘的一些關鍵信息。
  • type域:記錄着邏輯鬧鐘的鬧鐘類型,好比RTC_WAKEUP、ELAPSED_REALTIME_WAKEUP等;
  • count域:是個輔助域,它和repeatInterval域一塊兒工做。當repeatInterval大於0時,這個域可被用於計算下一次重複激發alarm的時間,詳細狀況見後文;
  • when域:記錄鬧鐘的激發時間。這個域和type域相關,詳細狀況見後文;
  • repeatInterval域:表示重複激發鬧鐘的時間間隔,若是鬧鐘只需激發一次,則此域爲0,若是鬧鐘須要重複激發,此域爲以毫秒爲單位的時間間隔;
  • operation域:記錄鬧鐘激發時應該執行的動做,詳細狀況見後文;
  • uid域:記錄設置鬧鐘的進程的uid;
  • pid域:記錄設置鬧鐘的進程的pid。

        整體來講仍是比較簡單的,咱們先補充說明一下其中的count域。這個域是針對重複性鬧鐘的一個輔助域。重複性鬧鐘的實現機理是,若是當前時刻已經超過鬧鐘的激發時刻,那麼ALMS會先從邏輯鬧鐘數組中摘取下Alarm節點,並執行鬧鐘對應的邏輯動做,而後進一步比較「當前時刻」和「Alarm理應激發的理想時刻」之間的時間跨度,從而計算出Alarm的「下一次理應激發的理想時刻」,並將這個激發時間記入Alarm節點,接着將該節點從新排入邏輯鬧鐘列表。這一點和普通Alarm不太同樣,普通Alarm節點摘下後就再也不還回邏輯鬧鐘列表了。

 

        「當前時刻」和「理應激發時刻」之間的時間跨度會隨實際的運做狀況而變更。咱們分兩步來講明「下一次理應激發時刻」的計算公式: 
1)  count  =  (時間跨度 /  repeatInterval ) + 1 ; 
2)  「下一次理應激發時刻」 = 「上一次理應激發時刻」+ count * repeatInterval ; 

 

        咱們畫一張示意圖,其中綠色的可激發時刻表示「上一次理應激發時刻」,咱們假定「當前時刻」分別爲now_1處或now_2處,能夠看到會計算出不一樣的「下一次理應激發時刻」,這裏用桔紅色表示。

 ALMS_003

 

        能夠看到,若是當前時刻爲now_1,那麼它和「上一次理應激發時刻」之間的「時間跨度」是小於一個repeatInterval的,因此count數爲1。而若是當前時刻爲now_2,那麼「時間跨度」與repeatInterval的商取整後爲2,因此count數爲3。另外,圖中那兩個虛線箭頭對應的可激發時刻,只是用來作刻度的東西。

 

3.2  主要行爲

        接下來咱們來看ALMS中的主要行爲,這些行爲和AlarmManager輔助類提供的成員函數相對應。

 

3.2.1   設置alarm

        外界能接觸的設置alarm的函數是set():publicvoid set(int type,long triggerAtTime, PendingIntent operation)

type:表示要設置的alarm類型。如前文所述,有4個alarm類型。 
triggerAtTime:表示alarm「理應激發」的時間。 
operation:指明瞭alarm鬧鈴激發時須要執行的動做,好比執行某種廣播通告。

 

        設置alarm的動做會牽扯到一個發起者。簡單地說,發起者會向Alarm Manager Service發出一個設置alarm的請求,並且在請求裏註明了到時間後須要執行的動做。因爲「待執行的動做」通常都不會立刻執行,因此要表達成PendingIntent的形式。(PendingIntent的詳情可參考其餘文章)

 

         另外,triggerAtTime參數的意義也會隨type參數的不一樣而不一樣。簡單地說,若是type是和RTC相關的話,那麼triggerAtTime的值應該是標準時間,即從1970 年 1 月 1 日午夜開始所通過的毫秒數。而若是type是其餘類型的話,那麼triggerAtTime的值應該是從本次開機開始算起的毫秒數。

 

3.2.2   重複性alarm

        另外一個設置alarm的函數是setRepeating():

  1. public void setRepeating(int type, long triggerAtTime, long interval,PendingIntent operation)  

其參數基本上和set()函數差很少,只是多了一個「時間間隔」參數。事實上,在Alarm Manager Service一側,set()函數內部也是在調用setRepeating()的,只不過會把interval設成了0。 

 

         setRepeating()的實現函數以下:

  1. public void setRepeating(int type, long triggerAtTime, long interval,  
  2.                          PendingIntent operation)   
  3. {  
  4.     if (operation == null) {  
  5.         Slog.w(TAG, "set/setRepeating ignored because there is no intent");  
  6.         return;  
  7.     }  
  8.     synchronized (mLock) {  
  9.         Alarm alarm = new Alarm();  
  10.         alarm.type = type;  
  11.         alarm.when = triggerAtTime;  
  12.         alarm.repeatInterval = interval;  
  13.         alarm.operation = operation;  
  14.   
  15.         // Remove this alarm if already scheduled.  
  16.         removeLocked(operation);  
  17.   
  18.         if (localLOGV) Slog.v(TAG, "set: " + alarm);  
  19.   
  20.         int index = addAlarmLocked(alarm);  
  21.         if (index == 0) {  
  22.             setLocked(alarm);  
  23.         }  
  24.     }  
  25. }  

        代碼很簡單,會建立一個邏輯鬧鐘Alarm,然後調用addAlarmLocked()將邏輯鬧鐘添加到內部邏輯鬧鐘數組的某個合適位置。

  1. private int addAlarmLocked(Alarm alarm) {  
  2.     ArrayList<Alarm> alarmList = getAlarmList(alarm.type);  
  3.   
  4.     int index = Collections.binarySearch(alarmList, alarm, mIncreasingTimeOrder);  
  5.     if (index < 0) {  
  6.         index = 0 - index - 1;  
  7.     }  
  8.     if (localLOGV) Slog.v(TAG, "Adding alarm " + alarm + " at " + index);  
  9.     alarmList.add(index, alarm);  
  10.     . . . . . .  
  11.     return index;  
  12. }  

        邏輯鬧鐘列表是依據alarm的激發時間進行排序的,越早激發的alarm,越靠近第0位。因此,addAlarmLocked()在添加新邏輯鬧鐘時,須要先用二分查找法快速找到列表中合適的位置,而後再把Alarm對象插入此處。

 ALMS_004

若是所插入的位置正好是第0位,就說明此時新插入的這個邏輯鬧鐘將會是本類alarm中最早被激發的alarm,而正如咱們前文所述,每一類邏輯鬧鐘會對應同一個「實體鬧鐘」,此處咱們在第0位設置了新的激發時間,明確表示咱們之前對「實體鬧鐘」設置的激發時間已經不許確了,因此setRepeating()中必須從新調整一下「實體鬧鐘」的激發時間,因而有了下面的句子:

  1. if (index == 0) {  
  2.     setLocked(alarm);  
  3. }  

        setLocked()內部會調用native函數set():

  1. private native void set(int fd, int type, long seconds, long nanoseconds);  

從新設置「實體鬧鐘」的激發時間。這個函數內部會調用ioctl()和底層打交道。具體代碼可參考frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp文件:

  1. static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd,   
  2. jint type, jlong seconds, jlong nanoseconds)  
  3. {  
  4.     struct timespec ts;  
  5.     ts.tv_sec = seconds;  
  6.     ts.tv_nsec = nanoseconds;  
  7.   
  8.     int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);  
  9.     if (result < 0)  
  10.     {  
  11.         ALOGE("Unable to set alarm to %lld.%09lld: %s\n", seconds, nanoseconds, strerror(errno));  
  12.     }  
  13. }  

         咱們知道,PendingIntent只是frameworks一層的概念,和底層驅動是沒有關係的。因此向底層設置alarm時只須要type信息以及激發時間信息就能夠了。

 在AlarmManagerService中真正設置alarm的函數是setImplLocked函數,在這個函數中把alarm添加到mAlarmBatchs中,mAlarmBatchs會把觸發時間相近的Alarm放在同一個bach中,而後每一個bach根據時間排序放在mAlarmBatchs中,前面的就是先要觸發的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. }  

 

  1. rescheduleKernelAlarmsLocked函數主要用來選取alarm的觸發時間設置到RTC中去。  
  2. private void rescheduleKernelAlarmsLocked() {  
  3.     // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch  
  4.     // prior to that which contains no wakeups, we schedule that as well.  
  5.     if (mAlarmBatches.size() > 0) {  
  6.         //查找第一個有wakeup類型alarm的batch  
  7.         final Batch firstWakeup = findFirstWakeupBatchLocked();  
  8.         //查找第一個batch  
  9.         final Batch firstBatch = mAlarmBatches.get(0);  
  10.         判斷條件是爲了防止重複設置  
  11.         if (firstWakeup != null && mNextWakeup != firstWakeup.start) {  
  12.             //將第一個有wakeup類型alarm的batch的時間設置到rtc中  
  13.             mNextWakeup = firstWakeup.start;  
  14.             setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);  
  15.         }  
  16.         if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {  
  17.             mNextNonWakeup = firstBatch.start;  
  18.             setLocked(ELAPSED_REALTIME, firstBatch.start);  
  19.         }  
  20.     }  
  21. }  

 

3.2.3   取消alarm

        用戶端是調用AlarmManager對象的cancel()函數來取消alarm的。這個函數內部實際上是調用IAlarmManager的remove()函數。因此咱們只來看AlarmManagerService的remove()就能夠了。

  1. public void remove(PendingIntent operation)   
  2. {  
  3.     if (operation == null) {  
  4.         return;  
  5.     }  
  6.     synchronized (mLock) {  
  7.         removeLocked(operation);  
  8.     }  
  9. }  

       注意,在取消alarm時,是以一個PendingIntent對象做爲參數的。這個PendingIntent對象正是當初設置alarm時,所傳入的那個operation參數。咱們不能隨便建立一個新的PendingIntent對象來調用remove()函數,不然remove()是不會起做用的。PendingIntent的運做細節不在本文論述範圍以內,此處咱們只需粗淺地知道,PendingIntent對象在AMS(Activity Manager Service)端會對應一個PendingIntentRecord實體,而ALMS在遍歷邏輯鬧鐘列表時,是根據是否指代相同PendingIntentRecord實體來判斷PendingIntent的相符狀況的。若是咱們隨便建立一個PendingIntent對象並傳入remove()函數的話,那麼在ALMS端勢必找不到相符的PendingIntent對象,因此remove()必然無效。

         remove()中調用的removeLocked()以下:

  1. public void removeLocked(PendingIntent operation)   
  2. {  
  3.     removeLocked(mRtcWakeupAlarms, operation);  
  4.     removeLocked(mRtcAlarms, operation);  
  5.     removeLocked(mElapsedRealtimeWakeupAlarms, operation);  
  6.     removeLocked(mElapsedRealtimeAlarms, operation);  
  7. }  

簡單地說就是,把4個邏輯鬧鐘數組都遍歷一遍,刪除其中全部和operation相符的Alarm節點。removeLocked()的實現代碼以下:

  1. private void removeLocked(ArrayList<Alarm> alarmList,  
  2.                                PendingIntent operation)   
  3. {  
  4.     if (alarmList.size() <= 0) {  
  5.         return;  
  6.     }  
  7.   
  8.     // iterator over the list removing any it where the intent match  
  9.     Iterator<Alarm> it = alarmList.iterator();  
  10.   
  11.     while (it.hasNext()) {  
  12.         Alarm alarm = it.next();  
  13.         if (alarm.operation.equals(operation)) {  
  14.             it.remove();  
  15.         }  
  16.     }  
  17. }  

         請注意,所謂的取消alarm,只是刪除了對應的邏輯Alarm節點而已,並不會和底層驅動再打什麼交道。也就是說,是不存在針對底層「實體鬧鐘」的刪除動做的。因此,底層「實體鬧鐘」在到時之時,仍是會被「激發」出來的,只不過此時在frameworks層,會由於找不到符合要求的「邏輯鬧鐘」而不作進一步的激發動做。

3.2.4   設置系統時間和時區

        AlarmManager還提供設置系統時間的功能,設置者須要具備android.permission.SET_TIME權限。

  1. public void setTime(long millis)   
  2. {  
  3.     mContext.enforceCallingOrSelfPermission("android.permission.SET_TIME", "setTime");  
  4.     SystemClock.setCurrentTimeMillis(millis);  
  5. }  

         另外,還具備設置時區的功能:

  1. public void setTimeZone(String tz)  
相應地,設置者須要具備android.permission.SET_TIME_ZONE權限。

 

3.3  運做細節

3.3.1   AlarmThread和Alarm的激發

        AlarmManagerService內部是如何感知底層激發alarm的呢?首先,AlarmManagerService有一個表示線程的mWaitThread成員:

  1. private final AlarmThread mWaitThread = new AlarmThread();  
在AlarmManagerService構造之初,就會啓動這個專門的「等待線程」。
  1. public AlarmManagerService(Context context)   
  2. {  
  3.     mContext = context;  
  4.     mDescriptor = init();  
  5.     . . . . . .  
  6.     . . . . . .  
  7.     if (mDescriptor != -1)   
  8.     {  
  9.         mWaitThread.start();   // 啓動線程!  
  10.     }   
  11.     else   
  12.     {  
  13.         Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");  
  14.     }  
  15. }  

 

AlarmManagerService的構造函數一開始就會調用一個init()函數,該函數是個native函數,它的內部會打開alarm驅動,並返回驅動文件句柄。只要可以順利打開alarm驅動,ALMS就能夠走到mWaitThread.start()一句,因而「等待線程」就啓動了。

 

3.3.1.1  AlarmThread中的run()

         AlarmThread自己是AlarmManagerService中一個繼承於Thread的內嵌類:

  1. private class AlarmThread extends Thread  

 

其最核心的run()函數的主要動做流程圖以下:

ALMS_005

咱們分別來闡述上圖中的關鍵步驟。

 

3.3.1.2  waitForAlarm()

        首先,從上文的流程圖中能夠看到,AlarmThread線程是在一個while(true)循環裏不斷調用waitForAlarm()函數來等待底層alarm激發動做的。waitForAlarm()是一個native函數:

  1. private native int waitForAlarm(int fd);  

其對應的C++層函數是android_server_AlarmManagerService_waitForAlarm():

【com_android_server_AlarmManagerService.cpp】

 

  1. static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd)  
  2. {  
  3.     int result = 0;  
  4.   
  5.     do  
  6.     {  
  7.         result = ioctl(fd, ANDROID_ALARM_WAIT);  
  8.     } while (result < 0 && errno == EINTR);  
  9.   
  10.     if (result < 0)  
  11.     {  
  12.         ALOGE("Unable to wait on alarm: %s\n", strerror(errno));  
  13.         return 0;  
  14.     }  
  15.   
  16.     return result;  
  17. }  

 

       當AlarmThread調用到ioctl()一句時,線程會阻塞住,直到底層激發alarm。並且所激發的alarm的類型會記錄到ioctl()的返回值中。這個返回值對外界來講很是重要,外界用它來判斷該遍歷哪一個邏輯鬧鐘列表。

 

3.3.1.3  triggerAlarmsLocked()

        一旦等到底層驅動的激發動做,AlarmThread會開始遍歷相應的邏輯鬧鐘列表:

  1. ArrayList<Alarm> triggerList = new ArrayList<Alarm>();  
  2. . . . . . .  
  3. final long nowRTC = System.currentTimeMillis();  
  4. final long nowELAPSED = SystemClock.elapsedRealtime();  
  5. . . . . . .  
  6. if ((result & RTC_WAKEUP_MASK) != 0)  
  7.     triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);  
  8. if ((result & RTC_MASK) != 0)  
  9.     triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);  
  10. if ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0)  
  11.     triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowELAPSED);  
  12. if ((result & ELAPSED_REALTIME_MASK) != 0)  
  13.     triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowELAPSED);  

 

         能夠看到,AlarmThread先建立了一個臨時的數組列表triggerList,而後根據result的值對相應的alarm數組列表調用triggerAlarmsLocked(),一旦發現alarm數組列表中有某個alarm符合激發條件,就把它移到triggerList中。這樣,4條alarm數組列表中須要激發的alarm就彙總到triggerList數組列表中了。

  triggerAlarmsLocked函數主要將要發送的alarm降入triggerlist中 

  1. private void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) {  
  2.     // batches are temporally sorted, so we need only pull from the  
  3.     // start of the list until we either empty it or hit a batch  
  4.     // that is not yet deliverable  
  5.     while (mAlarmBatches.size() > 0) {  
  6.         //獲取第一個batch  
  7.         Batch batch = mAlarmBatches.get(0);  
  8.         if (batch.start > nowELAPSED) {  
  9.             // Everything else is scheduled for the future  
  10.             break;  
  11.         }  
  12.   
  13.         // We will (re)schedule some alarms now; don't let that interfere  
  14.         // with delivery of this current batch  
  15.         //將第一個batch去除  
  16.         mAlarmBatches.remove(0);  
  17.   
  18.         final int N = batch.size();  
  19.         for (int i = 0; i < N; i++) {  
  20.             Alarm alarm = batch.get(i);  
  21.             alarm.count = 1;  
  22.             //遍歷加入triggerList  
  23.             triggerList.add(alarm);  
  24.   
  25.             // Recurring alarms may have passed several alarm intervals while the  
  26.             // phone was asleep or off, so pass a trigger count when sending them.  
  27.             //若是有重複類型的,計算時間從新設置  
  28.             if (alarm.repeatInterval > 0) {  
  29.                 // this adjustment will be zero if we're late by  
  30.                 // less than one full repeat interval  
  31.                 alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval;  
  32.   
  33.                 // Also schedule its next recurrence  
  34.                 final long delta = alarm.count * alarm.repeatInterval;  
  35.                 final long nextElapsed = alarm.whenElapsed + delta;  
  36.                 setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,  
  37.                         maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),  
  38.                         alarm.repeatInterval, alarm.operation, batch.standalone, true,  
  39.                         alarm.workSource);  
  40.             }  
  41.   
  42.         }  
  43.     }  
  44. }  

 

         接下來,只需遍歷一遍triggerList就能夠了:

  1. Iterator<Alarm> it = triggerList.iterator();  
  2. while (it.hasNext())   
  3. {  
  4.     Alarm alarm = it.next();  
  5.     . . . . . .  
  6.     alarm.operation.send(mContext, 0,  
  7.                          mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),  
  8.                          mResultReceiver, mHandler);  
  9.   
  10.     // we have an active broadcast so stay awake.  
  11.     if (mBroadcastRefCount == 0) {  
  12.         setWakelockWorkSource(alarm.operation);  
  13.         mWakeLock.acquire();  
  14.     }  
  15.     mInFlight.add(alarm.operation);  
  16.     mBroadcastRefCount++;  
  17.     mTriggeredUids.add(new Integer(alarm.uid));  
  18.     BroadcastStats bs = getStatsLocked(alarm.operation);  
  19.     if (bs.nesting == 0) {  
  20.         bs.startTime = nowELAPSED;  
  21.     } else {  
  22.         bs.nesting++;  
  23.     }  
  24.     if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP  
  25.         || alarm.type == AlarmManager.RTC_WAKEUP) {  
  26.         bs.numWakeup++;  
  27.         ActivityManagerNative.noteWakeupAlarm(alarm.operation);  
  28.     }  
  29. }  

 

在上面的while循環中,每遍歷到一個Alarm對象,就執行它的alarm.operation.send()函數。咱們知道,alarm中記錄的operation就是當初設置它時傳來的那個PendingIntent對象,如今開始執行PendingIntent的send()操做啦。

 

         PendingIntent的send()函數代碼是:

  1. public void send(Context context, int code, Intent intent,  
  2.                     OnFinished onFinished, Handler handler) throws CanceledException  
  3. {  
  4.     send(context, code, intent, onFinished, handler, null);  
  5. }  
  6.  

調用了下面的send()函數:

  1. public void send(Context context, int code, Intent intent,  
  2.                  OnFinished onFinished, Handler handler, String requiredPermission)  
  3.                  throws CanceledException   
  4. {  
  5.     try   
  6.     {  
  7.         String resolvedType = intent != null   
  8.                               ? intent.resolveTypeIfNeeded(context.getContentResolver())  
  9.                               : null;  
  10.         int res = mTarget.send(code, intent, resolvedType,  
  11.                                onFinished != null  
  12.                                ? new FinishedDispatcher(this, onFinished, handler)  
  13.                                : null,  
  14.                                requiredPermission);  
  15.         if (res < 0)   
  16.         {  
  17.             throw new CanceledException();  
  18.         }  
  19.     }   
  20.     catch (RemoteException e)   
  21.     {  
  22.         throw new CanceledException(e);  
  23.     }  
  24. }  

 

mTarget是個IPendingIntent代理接口,它對應AMS(Activity Manager Service)中的某個PendingIntentRecord實體。須要說明的是,PendingIntent的重要信息都是在AMS的PendingIntentRecord以及PendingIntentRecord.Key對象中管理的。AMS中有一張哈希表專門用於記錄全部可用的PendingIntentRecord對象。

        相較起來,在建立PendingIntent對象時傳入的intent數組,其重要性並不太明顯。這種intent數組主要用於一次性啓動多個activity,若是你只是但願啓動一個activity或一個service,那麼這個intent的內容有可能在最終執行PendingIntent的send()動做時,被新傳入的intent內容替換掉。

 

         AMS中關於PendingIntentRecord哈希表的示意圖以下:

ALMS_006

AMS是整個Android平臺中最複雜的一個核心service了,因此咱們不在這裏作過多的闡述,有興趣的讀者能夠參考其餘相關文檔。

 

3.3.1.4  進一步處理「喚醒鬧鐘」

        在AlarmThread.run()函數中while循環的最後,會進一步判斷,當前激發的alarm是否是「喚醒鬧鐘」。若是鬧鐘類型爲RTC_WAKEUP或ELAPSED_REALTIME_WAKEUP,那它就屬於「喚醒鬧鐘」,此時須要通知一下AMS:

  1. if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP  
  2.     || alarm.type == AlarmManager.RTC_WAKEUP)   
  3. {  
  4.     bs.numWakeup++;  
  5.     ActivityManagerNative.noteWakeupAlarm(alarm.operation);  
  6. }  

 

這兩種alarm就是咱們常說的0型和2型鬧鐘,它們和咱們手機的續航時間息息相關。 

 

         AMS裏的noteWakeupAlarm()比較簡單,只是在調用BatteryStatsService服務的相關動做,可是卻會致使機器的喚醒:

  1. public void noteWakeupAlarm(IIntentSender sender)   
  2. {  
  3.     if (!(sender instanceof PendingIntentRecord))   
  4.     {  
  5.         return;  
  6.     }  
  7.       
  8.     BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();  
  9.     synchronized (stats)   
  10.     {  
  11.         if (mBatteryStatsService.isOnBattery())   
  12.         {  
  13.             mBatteryStatsService.enforceCallingPermission();  
  14.             PendingIntentRecord rec = (PendingIntentRecord)sender;  
  15.             int MY_UID = Binder.getCallingUid();  
  16.             int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;  
  17.             BatteryStatsImpl.Uid.Pkg pkg = stats.getPackageStatsLocked(uid, rec.key.packageName);  
  18.             pkg.incWakeupsLocked();  
  19.         }  
  20.     }  
  21. }  

 

         好了,說了這麼多,咱們仍是畫一張AlarmThread示意圖做爲總結:

 ALMS_007

 

3.3.2   說說AlarmManagerService中的mBroadcastRefCount

        下面咱們說說AlarmManagerService中的mBroadcastRefCount,之因此要說它,僅僅是由於我在修改AlarmManagerService代碼的時候,吃過它的虧。

         咱們先回顧一下處理triggerList列表的代碼,以下:

  1. Iterator<Alarm> it = triggerList.iterator();  
  2. while (it.hasNext())   
  3. {  
  4.     Alarm alarm = it.next();  
  5.     . . . . . .  
  6.     alarm.operation.send(mContext, 0,  
  7.                          mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),  
  8.                          mResultReceiver, mHandler);  
  9.   
  10.     // we have an active broadcast so stay awake.  
  11.     if (mBroadcastRefCount == 0) {  
  12.         setWakelockWorkSource(alarm.operation);  
  13.         mWakeLock.acquire();  
  14.     }  
  15.     mInFlight.add(alarm.operation);  
  16.     mBroadcastRefCount++;  
  17.     . . . . . .  
  18.     . . . . . .  
  19. }  

 

能夠看到,在AlarmThread.run()中,只要triggerList中含有可激發的alarm,mBroadcastRefCount就會執行加一操做。一開始mBroadcastRefCount的值爲0,因此會進入上面那句if語句,進而調用mWakeLock.acquire()。

         後來我才知道,這個mBroadcastRefCount變量,是決定什麼時候釋放mWakeLock的計數器。AlarmThread的意思很明確,只要還有處於激發狀態的邏輯鬧鐘,機器就不能徹底睡眠。那麼釋放這個mWakeLock的地方又在哪裏呢?答案就在alarm.operation.send()一句的mResultReceiver參數中。

 

         mResultReceiver是AlarmManagerService的私有成員變量:

  1. private final ResultReceiver mResultReceiver = newResultReceiver();  

類型爲ResultReceiver,這個類實現了PendingIntent.OnFinished接口:

  1. class ResultReceiver implements PendingIntent.OnFinished  

當send()動做完成後,框架會間接回調這個對象的onSendFinished()成員函數。

  1. public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,  
  2.                                 String resultData, Bundle resultExtras)   
  3. {  
  4.     . . . . . .  
  5.     . . . . . .  
  6.     if (mBlockedUids.contains(new Integer(uid)))  
  7.     {  
  8.         mBlockedUids.remove(new Integer(uid));  
  9.     }   
  10.     else   
  11.     {  
  12.         if (mBroadcastRefCount > 0)  
  13.         {  
  14.             mInFlight.removeFirst();  
  15.             mBroadcastRefCount--;  
  16.               
  17.             if (mBroadcastRefCount == 0)   
  18.             {  
  19.                 mWakeLock.release();  
  20.             }   
  21.             . . . . . .  
  22.         }   
  23.         . . . . . .  
  24.     }  
  25.     . . . . . .  
  26. }  

         我一開始沒有足夠重視這個mBroadcastRefCount,因此把alarm.operation.send()語句包在了一條if語句中,也就是說在某種狀況下,程序會跳過alarm.operation.send()一句,直接執行下面的語句。然而此時的mBroadcastRefCount還在堅決不移地加一,這直接致使mBroadcastRefCount再也減不到0了,因而mWakeLock也永遠不會釋放了。使人頭痛的是,這個mWakeLock雖然不讓手機深睡眠下去,卻也不會點亮屏幕,因此這個bug潛藏了很久才被找到。還真是應了我說的那句話:「魔鬼總藏在細節中。」

也許一些使用alarmmanager作定時任務的同窗遇到過這樣的問題:設定alarm後,進入設置-->應用程序管理-->強行中止app後,定時任務就失效了。
簡單的講就是:force stop會致使alarm失效。

最典型的例子就是我碰到過的一個bug,使用android手機的時鐘app設置一個鬧鐘,而後進入設置-->應用程序管理裏面,將時鐘這個app force stop掉,結果鬧鐘就不響了。


其實這不是bug,這是android系統的新加入的機制。下面我來詳細分析一下前因後果。

1. 在設置的應用程序管理裏面強行中止app:

    這裏會最終會調用到 ActivityManagerService的forceStopPackageLocked()

  1. private void forceStopPackageLocked(final String packageName, int uid) {  
  2.         forceStopPackageLocked(packageName, uid, false, false, true, false);  
  3.         Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,  
  4.                 Uri.fromParts("package", packageName, null));  
  5.         if (!mProcessesReady) {  
  6.             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);  
  7.         }  
  8.         intent.putExtra(Intent.EXTRA_UID, uid);  
  9.         broadcastIntentLocked(null, null, intent,  
  10.                 null, null, 0, null, null, null,  
  11.                 false, false, MY_PID, Process.SYSTEM_UID);  
  12.     }  


代碼裏面發送了一個廣播:ACTION_PACKAGE_RESTARTED,這個廣播大有文章。

 

最後來看UninstallReceiver,當AlarmManagerService接受到這個廣播後,會把其那些alarm的包名傳過來的給刪除了。

  1. class UninstallReceiver extends BroadcastReceiver {  
  2.         public UninstallReceiver() {  
  3.             IntentFilter filter = new IntentFilter();  
  4.             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);  
  5.             filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);  
  6.             filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);  
  7.             filter.addDataScheme("package");  
  8.             mContext.registerReceiver(this, filter);  
  9.              // Register for events related to sdcard installation.  
  10.             IntentFilter sdFilter = new IntentFilter();  
  11.             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);  
  12.             sdFilter.addAction(Intent.ACTION_USER_STOPPED);  
  13.             mContext.registerReceiver(this, sdFilter);  
  14.         }  
  15.           
  16.         @Override  
  17.         public void onReceive(Context context, Intent intent) {  
  18.             synchronized (mLock) {  
  19.                 String action = intent.getAction();  
  20.                 String pkgList[] = null;  
  21.                 if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {  
  22.                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);  
  23.                     for (String packageName : pkgList) {  
  24.                         if (lookForPackageLocked(packageName)) {  
  25.                             setResultCode(Activity.RESULT_OK);  
  26.                             return;  
  27.                         }  
  28.                     }  
  29.                     return;  
  30.                 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {  
  31.                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);  
  32.                 } else if (Intent.ACTION_USER_STOPPED.equals(action)) {  
  33.                     int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);  
  34.                     if (userHandle >= 0) {  
  35.                         removeUserLocked(userHandle);  
  36.                     }  
  37.                 } else {  
  38.                     if (Intent.ACTION_PACKAGE_REMOVED.equals(action)  
  39.                             && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {  
  40.                         // This package is being updated; don't kill its alarms.  
  41.                         return;  
  42.                     }  
  43.                     Uri data = intent.getData();  
  44.                     if (data != null) {  
  45.                         String pkg = data.getSchemeSpecificPart();  
  46.                         if (pkg != null) {  
  47.                             pkgList = new String[]{pkg};  
  48.                         }  
  49.                     }  
  50.                 }  
  51.                 if (pkgList != null && (pkgList.length > 0)) {  
  52.                     for (String pkg : pkgList) {  
  53.                         //將這個pkg的alarm從AlarmManagerService中去除  
  54.                         removeLocked(pkg);  
  55.                         mBroadcastStats.remove(pkg);  
  56.                     }  
  57.                 }  
  58.             }  
  59.         }  
  60.     }  

 

爲何google要加入這樣的機制呢?

應該是出於系統安全的考慮,google在4.0系統中在安全方面作了不少努力。

不少病毒程序都不但願本身的進程被用戶強行中止,但願本身的病毒程序能夠一直運行,而常見的方式就是經過設置alarm,在病毒進程被殺死後,經過定時發送廣播來拉起病毒進程,來實現病毒進程的從新啓動。

google也正是看到了這個一點,因此加入了forceStopPackage的這一機制,讓用戶可以有機會幹掉病毒進程。

android系統的安全性一直是android系統的短板,google在提高系統安全性方面也在不斷努力,在以後的文章中,我會再進行介紹。

相關文章
相關標籤/搜索