品茗論道說廣播(Broadcast內部機制講解)

1 概述

        我們在編寫Android程序時,常常會用到廣播(Broadcast)機制。從易用性的角度來說,使用廣播是非常簡單的。不過,這個不是本文關心的重點,我們希望探索得再深入一點兒。我想,許多人也不想僅僅停留在使用廣播的階段,而是希望瞭解一些廣播機制的內部機理。如果是這樣的話,請容我斟一杯紅茶,慢慢道來。

        簡單地說,Android廣播機制的主要工作是爲了實現一處發生事情,多處得到通知的效果。這種通知工作常常要牽涉跨進程通訊,所以需要由AMS(Activity Manager Service)集中管理。

 

        在Android系統中,接收廣播的組件叫作receiver,而且receiver還分爲動態和靜態的。動態receiver是在運行期通過調用registerReceiver()註冊的,而靜態receiver則是在AndroidManifest.xml中聲明的。動態receiver比較簡單,靜態的就麻煩一些了,因爲在廣播遞送之時,靜態receiver所從屬的進程可能還沒有啓動呢,這就需要先啓動新的進程,費時費力。另一方面,有些時候用戶希望廣播能夠按照一定順序遞送,爲此,Android又搞出了ordered broadcast的概念。

        細節如此繁雜,非一言可以說清。我們先從receiver這一側入手吧。

 

2 兩種receiver

        Android中的receiver,分爲“動態receiver”和“靜態receiver”。

2.1 動態receiver

        動態receiver必須在運行期動態註冊,其實際的註冊動作由ContextImpl對象完成:

  1. <code class="hljs less" style=""><span class="hljs-variable" style="">@Override</span>  
  2. public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter)   
  3. {      
  4.     return registerReceiver(receiver, filter, nullnull);  
  5. }  
  6. <span class="hljs-variable" style="">@Override</span>  
  7. public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,  
  8.                                String broadcastPermission, Handler scheduler)   
  9. {     
  10.     return registerReceiverInternal(receiver, filter, broadcastPermission,  
  11.                                     scheduler, getOuterContext());  
  12. }</code>  

註冊之時,用戶會把一個自定義的receiver對象作爲第一個參數傳入。當然,用戶的receiver都是繼承於BroadcastReceiver的。使用過廣播機制的程序員,對這個BroadcastReceiver應該都不陌生,這裏就不多說了。我們需要關心的是,這個registerReceiverInternal()內部還包含了什麼重要的細節。

        registerReceiverInternal()代碼的截選如下:

  1. <code class="hljs kotlin" style=""><span class="hljs-keyword" style="">private</span> Intent registerReceiverInternal(BroadcastReceiver receiver,  
  2.                                         IntentFilter filter, String broadcastPermission,  
  3.                                         Handler scheduler, Context context)   
  4. {  
  5.     IIntentReceiver rd = <span class="hljs-literal" style="">null</span>;      
  6.     <span class="hljs-keyword" style="">if</span> (receiver != <span class="hljs-literal" style="">null</span>)   
  7.     {          
  8.         <span class="hljs-keyword" style="">if</span> (mPackageInfo != <span class="hljs-literal" style="">null</span> && context != <span class="hljs-literal" style="">null</span>)   
  9.         {              
  10.             <span class="hljs-keyword" style="">if</span> (scheduler == <span class="hljs-literal" style="">null</span>)   
  11.             {  
  12.                 scheduler = mMainThread.getHandler();  
  13.             }              
  14.             <span class="hljs-comment" style="">// 查找和context對應的“子哈希表”裏的ReceiverDispatcher,如果找不到,就重新new一個</span>  
  15.             rd = mPackageInfo.getReceiverDispatcher(receiver, context, scheduler,  
  16.                                                     mMainThread.getInstrumentation(), <span class="hljs-literal" style="">true</span>);  
  17.         }   
  18.         . . . . . .  
  19.     }      
  20.     <span class="hljs-keyword" style="">try</span>   
  21.     {          
  22.         <span class="hljs-keyword" style="">return</span> ActivityManagerNative.getDefault().registerReceiver(  
  23.                 mMainThread.getApplicationThread(), mBasePackageName,  
  24.                 rd, filter, broadcastPermission);  
  25.     }   
  26.     <span class="hljs-keyword" style="">catch</span> (RemoteException e)   
  27.     {          
  28.         <span class="hljs-keyword" style="">return</span> <span class="hljs-literal" style="">null</span>;  
  29.     }  
  30. }</code>  

請大家注意那個rd對象(IIntentReceiver rd)。我們知道,在Android架構中,廣播動作最終其實都是由AMS遞送出來的。AMS利用binder機制,將語義傳遞給各個應用進程,應用進程再輾轉調用到receiver的onReceive(),完成這次廣播。而此處的rd對象正是承擔“語義傳遞工作“的binder實體。

        爲了管理這個重要的binder實體,Android搞出了一個叫做ReceiveDispatcher的類。該類的定義截選如下:

【frameworks/base/core/Java/android/app/LoadedApk.java】

  1. <code class="hljs java" style=""><span class="hljs-keyword" style="">static</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">ReceiverDispatcher</span></span></span><span class="hljs-class" style="">   
  2. </span></span>{  
  3.     <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">static</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">InnerReceiver</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">extends</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">IIntentReceiver</span></span></span><span class="hljs-class" style="">.</span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">Stub</span></span></span><span class="hljs-class" style=""> </span></span>{  
  4.         . . . . . .  
  5.         . . . . . .  
  6.     }  
  7.     <span class="hljs-keyword" style="">final</span> IIntentReceiver.Stub mIIntentReceiver;   <span class="hljs-comment" style="">// 請注意這個域!它就是傳到外面的rd。</span>  
  8.     <span class="hljs-keyword" style="">final</span> BroadcastReceiver mReceiver;  
  9.     <span class="hljs-keyword" style="">final</span> Context mContext;  
  10.     <span class="hljs-keyword" style="">final</span> Handler mActivityThread;  
  11.     <span class="hljs-keyword" style="">final</span> Instrumentation mInstrumentation;  
  12.     <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">boolean</span> mRegistered;  
  13.     <span class="hljs-keyword" style="">final</span> IntentReceiverLeaked mLocation;  
  14.     RuntimeException mUnregisterLocation;  
  15.     <span class="hljs-keyword" style="">boolean</span> mForgotten;  
  16.     . . . . . .</code>  

        這樣看來,“動態註冊的BroadcastReceiver”和“ReceiverDispatcher節點”具有一一對應的關係。示意圖如下:

一個應用裏可能會註冊多個動態receiver,所以這種一一對應關係最好整理成表,這個表就位於LoadedApk中。前文mPackageInfo.getReceiverDispatcher()一句中的mPackageInfo就是LoadedApk對象。

        在Android的架構裏,應用進程裏是用LoadedApk來對應一個apk的,進程里加載了多少個apk,就會有多少LoadedApk。每個LoadedApk裏會有一張“關於本apk動態註冊的所有receiver”的哈希表(mReceivers)。當然,在LoadedApk初創之時,這張表只是個空表。

        mReceivers表的定義如下:

  1. <code class="hljs ruby" style="">private final   
  2. HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher<span class="hljs-meta" style="">>> </span>mReceivers  
  3.     = new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher<span class="hljs-meta" style="">>></span>();</code>  

該表的key項是我們比較熟悉的Context,也就是說可以是Activity、Service或Application。而value項則是另一張“子哈希表”。這是個“表中表”的形式。言下之意就是,每個Context(比如一個activity),是可以註冊多個receiver的,這個很好理解。mReceivers裏的“子哈希表”的key值爲BroadcastReceiver,value項爲ReceiverDispatcher,示意圖如下:

圖:客戶進程中的mReceivers表

        接下來我們繼續看registerReceiverInternal(),它最終調用到

  1. <code class="hljs css" style=""><span class="hljs-selector-tag" style="">ActivityManagerNative</span><span class="hljs-selector-class" style="">.getDefault</span>()<span class="hljs-selector-class" style="">.registerReceiver</span>(  
  2.                     <span class="hljs-selector-tag" style="">mMainThread</span><span class="hljs-selector-class" style="">.getApplicationThread</span>(), <span class="hljs-selector-tag" style="">mBasePackageName</span>,  
  3.                     <span class="hljs-selector-tag" style="">rd</span>, <span class="hljs-selector-tag" style="">filter</span>, <span class="hljs-selector-tag" style="">broadcastPermission</span>);</code>  

registerReceiver()函數的filter參數指明瞭用戶對哪些intent感興趣。對同一個BroadcastReceiver對象來說,可以註冊多個感興趣的filter,就好像聲明靜態receiver時,也可以爲一個receiver編寫多個<intent-filter>一樣。這些IntentFilter信息會彙總到AMS的mRegisteredReceivers表中。在AMS端,我們可以這樣訪問相應的彙總表:

  1. <code class="hljs nginx" style=""><span class="hljs-attribute" style="">ReceiverList</span> rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());</code>  

其中的receiver參數爲IIntentReceiver型,正對應着ReceiverDispatcher中那個binder實體。也就是說,每個客戶端的ReceiverDispatcher,會對應AMS端的一個ReceiverList。

        ReceiverList的定義截選如下:

  1. <code class="hljs java" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">ReceiverList</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">extends</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">ArrayList</span></span></span><span class="hljs-class" style=""><</span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">BroadcastFilter</span></span></span><span class="hljs-class" style="">>  
  2.         </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">implements</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">IBinder</span></span></span><span class="hljs-class" style="">.</span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">DeathRecipient</span></span></span><span class="hljs-class" style="">   
  3. </span></span>{  
  4.     <span class="hljs-keyword" style="">final</span> ActivityManagerService owner;   
  5.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> IIntentReceiver receiver;      
  6.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> ProcessRecord app;      
  7.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> pid;      
  8.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> uid;  
  9.     BroadcastRecord curBroadcast = <span class="hljs-keyword" style="">null</span>;  
  10.     <span class="hljs-keyword" style="">boolean</span> linkedToDeath = <span class="hljs-keyword" style="">false</span>;  
  11.     String stringName;  
  12.     . . . . . .</code>  

ReceiverList繼承於ArrayList<BroadcastFilter>,而BroadcastFilter又繼承於IntentFilter,所以ReceiverList可以被理解爲一個IntentFilter數組列表。

  1. <code class="hljs actionscript" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">BroadcastFilter</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">extends</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">IntentFilter</span></span></span><span class="hljs-class" style=""> </span></span>{  
  2.     <span class="hljs-keyword" style="">final</span> ReceiverList receiverList;  
  3.     <span class="hljs-keyword" style="">final</span> String packageName;  
  4.     <span class="hljs-keyword" style="">final</span> String requiredPermission;  
  5.     . . . . . .</code>  

        現在,我們可以繪製一張完整一點兒的圖:

這張圖只畫了一個用戶進程,在實際的系統裏當然會有很多用戶進程了,不過其關係是大致統一的,所以我們不再重複繪製。關於動態receiver的註冊,我們就先說這麼多。至於激發廣播時,又會做什麼動作,我們會在後文闡述,現在我們先接着說明和動態receiver相對的靜態receiver。

 

2.2 靜態receiver

        靜態receiver是指那些在AndroidManifest.xml文件中聲明的receiver,它們的信息會在系統啓動時,由Package Manager Service(PKMS)解析並記錄下來。以後,當AMS調用PKMS的接口來查詢“和intent匹配的組件”時,PKMS內部就會去查詢當初記錄下來的數據,並把結果返回AMS。有的同學認爲靜態receiver是常駐內存的,這種說法並不準確。因爲常駐內存的只是靜態receiver的描述性信息,並不是receiver實體本身。

        在PKMS內部,會有一個針對receiver而設置的Resolver(決策器),其示意圖如下:

        關於PKMS的查詢動作的細節,可參考PKMS的相關文檔。目前我們只需知道,PKMS向外界提供了queryIntentReceivers()函數,該函數可以返回一個List<ResolveInfo>列表。

        我們舉個實際的例子:

  1. <code class="hljs php" style="">Intent intent = <span class="hljs-keyword" style="">new</span> Intent(Intent.ACTION_PRE_BOOT_COMPLETED);  
  2. <span class="hljs-keyword" style="">List</span><ResolveInfo> ris = <span class="hljs-keyword" style="">null</span>;<span class="hljs-keyword" style="">try</span> {  
  3.     ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, <span class="hljs-keyword" style="">null</span>, <span class="hljs-number" style="">0</span>, <span class="hljs-number" style="">0</span>);  
  4. } <span class="hljs-keyword" style="">catch</span> (RemoteException e) {}</code>  

這是AMS的systemReady()函數裏的一段代碼,意思是查找有多少receiver對ACTION_PRE_BOOT_COMPLETED感興趣。

         ResolveInfo的定義截選如下:

  1. <code class="hljs java" style=""><span class="hljs-keyword" style="">public</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">ResolveInfo</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">implements</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">Parcelable</span></span></span><span class="hljs-class" style="">   
  2. </span></span>{      
  3.     <span class="hljs-keyword" style="">public</span> ActivityInfo activityInfo;      
  4.     <span class="hljs-keyword" style="">public</span> ServiceInfo serviceInfo;      
  5.     <span class="hljs-keyword" style="">public</span> IntentFilter filter;      
  6.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">int</span> priority;      
  7.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">int</span> preferredOrder;      
  8.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">int</span> match;  
  9.     . . . . . .  
  10.     . . . . . .</code>  

        總之,當系統希望發出一個廣播時,PKMS必須能夠決策出,有多少靜態receiver對這個廣播感興趣,而且這些receiver的信息分別又是什麼。

        關於receiver的註冊動作,我們就先說這麼多。下面我們來看看激發廣播時的動作。

 

3 激發廣播

        大家常見的激發廣播的函數有哪些呢?從ContextImpl.java文件中,我們可以看到一系列發送廣播的接口,列舉如下:

  • public void sendBroadcast(Intent intent)

  • public void sendBroadcast(Intent intent, int userId)

  • public void sendBroadcast(Intent intent, String receiverPermission)

  • public void sendOrderedBroadcast(Intent intent, String receiverPermission)

  • public void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)

  • public void sendStickyBroadcast(Intent intent)

  • public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)

        其中sendBroadcast()是最簡單的發送廣播的動作。而sendOrderedBroadcast(),則是用來向系統發出有序廣播(Ordered broadcast)的。這種有序廣播對應的所有接收器只能按照一定的優先級順序,依次接收intent。這些優先級一般記錄在AndroidManifest.xml文件中,具體位置在<intent-filter>元素的android:priority屬性中,其數值越大表示優先級越高,取值範圍爲-1000到1000。另外,有時候我們也可以調用IntentFilter對象的setPriority()方法來設置優先級。

        對於有序廣播而言,前面的接收者可以對接收到的廣播intent進行處理,並將處理結果放置到廣播intent中,然後傳遞給下一個接收者。需要注意的是,前面的接收者有權終止廣播的進一步傳播。也就是說,如果廣播被前面的接收者終止了,那麼後面的接收器就再也無法接收到廣播了。

        還有一個怪東西,叫做sticky廣播,它又是什麼呢?簡單地說,sticky廣播可以保證“在廣播遞送時尚未註冊的receiver”,一旦日後註冊進系統,就能夠馬上接到“錯過”的sticky廣播。有關它的細節,我們在後文再說。

        在發送方,我們熟悉的調用sendBroadcast()的代碼片段如下:

  1. <code class="hljs actionscript" style="">mContext = getApplicationContext();   
  2. Intent intent = <span class="hljs-keyword" style="">new</span> Intent();    
  3. intent.setAction(<span class="hljs-string" style="">"com.android.xxxxx"</span>);    
  4. intent.setFlags(<span class="hljs-number" style="">1</span>);    
  5. mContext.sendBroadcast(intent);</code>  

上面的mContext的內部其實是在調用一個ContextImpl對象的同名函數,所以我們繼續查看ContextImpl.java文件。

【frameworks/base/core/java/android/app/ContextImpl.java】

  1. <code class="hljs java" style=""><span class="hljs-meta" style="">@Override</span>  
  2. <span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">sendBroadcast</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(Intent intent)</span></span></span><span class="hljs-function" style="">   
  3. </span></span>{  
  4.     String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());      
  5.     <span class="hljs-keyword" style="">try</span>   
  6.     {  
  7.         intent.setAllowFds(<span class="hljs-keyword" style="">false</span>);  
  8.         ActivityManagerNative.getDefault().broadcastIntent(  
  9.             mMainThread.getApplicationThread(), intent, resolvedType, <span class="hljs-keyword" style="">null</span>,  
  10.             Activity.RESULT_OK, <span class="hljs-keyword" style="">null</span>, <span class="hljs-keyword" style="">null</span>, <span class="hljs-keyword" style="">null</span>, <span class="hljs-keyword" style="">false</span>, <span class="hljs-keyword" style="">false</span>,  
  11.             Binder.getOrigCallingUser());  
  12.     } <span class="hljs-keyword" style="">catch</span> (RemoteException e) {  
  13.     }  
  14. }</code>  

簡單地調用broadcastIntent()向AMS發出請求了。

 

3.1 AMS一側的broadcastIntentLocked()

        用戶進程把發送廣播的語義傳遞到AMS之後,最終會由AMS的broadcastIntentLocked()處理。其原型如下:

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">private</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">final</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">int</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">broadcastIntentLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(ProcessRecord callerApp,  
  2.                                         String callerPackage,   
  3.                                         Intent intent, String resolvedType,  
  4.                                         IIntentReceiver resultTo, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode,   
  5.                                         String resultData,  
  6.                                         Bundle map, String requiredPermission,  
  7.                                         </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> ordered, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> sticky,   
  8.                                         </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> callingPid, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> callingUid,                     
  9.                                         </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> userId)</span></span></span></span></code>  

broadcastIntentLocked()需要考慮以下方面的技術細節。

        首先,有些廣播intent只能由具有特定權限的進程發送,而有些廣播intent在發送之前需要做一些其他動作。當然,如果發送方進程是系統進程、phone進程、shell進程,或者具有root權限的進程,那麼必然有權發出廣播。

        另外,有時候用戶希望發送sticky廣播,以便日後註冊的receiver可以收到“錯過”的sticky廣播。要達到這個目的,系統必須在內部維護一張sticky廣播表,在具體的實現中,AMS會把廣播intent加入mStickyBroadcasts映射表中。mStickyBroadcasts是一張哈希映射表,其key值爲intent的action字符串,value值爲“與這個action對應的intent數組列表”的引用。當我們發送sticky廣播時,新的廣播intent要麼替換掉intent數組列表中的某項,要麼作爲一個新項被添加進數組列表,以備日後使用。

        發送廣播時,還需要考慮所發送的廣播是否需要有序(ordered)遞送。而且,receiver本身又分爲動態註冊和靜態聲明的,這讓我們面對的情況更加複雜。從目前的代碼來看,靜態receiver一直是按照有序方式遞送的,而動態receiver則需要根據ordered參數的值,做不同的處理。當我們需要有序遞送時,AMS會把動態receivers和靜態receivers合併到一張表中,這樣才能依照receiver的優先級,做出正確的處理,此時動態receivers和靜態receivers可能呈現一種交錯順序。

        另一方面,有些廣播是需要發給特定目標組件的,這個也要加以考慮。

        現在我們來分析broadcastIntentLocked()函數。說得難聽點兒,這個函數的實現代碼頗有些裹腳布的味道,我們必須耐下性子解讀這部分代碼。經過一番努力,我們可以將其邏輯大致整理成以下幾步:

1) 爲intent添加FLAG_EXCLUDE_STOPPED_PACKAGES標記; 
2) 處理和package相關的廣播; 
3) 處理其他一些系統廣播; 
4) 判斷當前是否有權力發出廣播; 
5) 如果要發出sticky廣播,那麼要更新一下系統中的sticky廣播列表; 
6) 查詢和intent匹配的靜態receivers; 
7) 查詢和intent匹配的動態receivers; 
8) 嘗試向並行receivers遞送廣播; 
9) 整合(剩下的)並行receivers,以及靜態receivers,形成一個串行receivers表; 
10) 嘗試逐個向串行receivers遞送廣播。

        下面我們來詳細說這幾個部分。

3.1.1 爲intent添加FLAG_EXCLUDE_STOPPED_PACKAGES標記

        對應的代碼爲:

  1. <code class="hljs haskell" style=""><span class="hljs-title" style="">intent</span> = new <span class="hljs-type" style="">Intent</span>(intent);// <span class="hljs-type" style="">By</span> <span class="hljs-keyword" style="">default</span> broadcasts do not go to stopped apps.intent.addFlags(<span class="hljs-type" style="">Intent</span>.<span class="hljs-type" style="">FLAG_EXCLUDE_STOPPED_PACKAGES</span>);</code>  

        爲什麼intent要添加FLAG_EXCLUDE_STOPPED_PACKAGES標記呢?原因是這樣的,在Android 3.1之後,PKMS加強了對“處於停止狀態的”應用的管理。如果一個應用在安裝後從來沒有啓動過,或者已經被用戶強制停止了,那麼這個應用就處於停止狀態(stopped state)。爲了達到精細調整的目的,Android增加了2個flag:FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES,以此來表示intent是否要激活“處於停止狀態的”應用。

  1. <code class="hljs gradle" style=""><span class="hljs-comment" style="">/** 
  2.  * If set, this intent will not match any components in packages that 
  3.  * are currently stopped.  If this is not set, then the default behavior 
  4.  * is to include such applications in the result. 
  5.  */</span>  
  6. <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">static</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> FLAG_EXCLUDE_STOPPED_PACKAGES = <span class="hljs-number" style="">0</span>x00000010;  
  7. <span class="hljs-comment" style="">/** 
  8.  * If set, this intent will always match any components in packages that 
  9.  * are currently stopped.  This is the default behavior when 
  10.  * {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set.  If both of these 
  11.  * flags are set, this one wins (it allows overriding of exclude for 
  12.  * places where the framework may automatically set the exclude flag). 
  13.  */</span>  
  14. <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">static</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> FLAG_INCLUDE_STOPPED_PACKAGES = <span class="hljs-number" style="">0</span>x00000020;</code>  

        從上面的broadcastIntentLocked()函數可以看到,在默認情況下,AMS是不會把intent廣播發給“處於停止狀態的”應用的。據說Google這樣做是爲了防止一些流氓軟件或病毒幹壞事。當然,如果廣播的發起者認爲自己的確需要廣播到“處於停止狀態的”應用的話,它可以讓intent攜帶FLAG_INCLUDE_STOPPED_PACKAGES標記,從這個標記的註釋可以瞭解到,如果這兩個標記同時設置的話,那麼FLAG_INCLUDE_STOPPED_PACKAGES標記會“取勝”,它會覆蓋掉framework自動添加的FLAG_EXCLUDE_STOPPED_PACKAGES標記。

3.1.2 處理和package相關的廣播

        接下來需要處理一些系統級的“Package廣播”,這些主要從PKMS(Package Manager Service)處發來。比如,當PKMS處理APK的添加、刪除或改動時,一般會發出類似下面的廣播:ACTION_PACKAGE_ADDED、ACTION_PACKAGE_REMOVED、ACTION_PACKAGE_CHANGED、ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE、ACTION_UID_REMOVED。

        AMS必須確保發送“包廣播”的發起方具有BROADCAST_PACKAGE_REMOVED權限,如果沒有,那麼AMS會拋出異常(SecurityException)。接着,AMS判斷如果是某個用戶id被刪除了的話(Intent.ACTION_UID_REMOVED),那麼必須把這件事通知給“電池狀態服務”(Battery Stats Service)。另外,如果是SD卡等外部設備上的應用不可用了,這常常是因爲卡被unmount了,此時PKMS會發出Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE,而AMS則需要把SD卡上的所有包都強制停止(forceStopPackageLocked()),並立即發出另一個“Package廣播”——EXTERNAL_STORAGE_UNAVAILABLE。

        如果只是某個外部包被刪除或改動了,則要進一步判斷intent裏是否攜帶了EXTRA_DONT_KILL_APP額外數據,如果沒有攜帶,說明需要立即強制結束package,否則,不強制結束package。看來有些應用即使在刪除或改動了包後,還會在系統(內存)中保留下來並繼續運行。另外,如果是刪除包的話,此時要發出PACKAGE_REMOVED廣播。

3.1.3 處理其他一些系統廣播

        broadcastIntentLocked()不但要對“Package廣播”進行處理,還要關心其他一些系統廣播。比如ACTION_TIMEZONE_CHANGED、ACTION_CLEAR_DNS_CACHE、PROXY_CHANGE_ACTION等等,感興趣的同學可以自行研究這些廣播的意義。

3.1.4 判斷當前是否有權力發出廣播

        接着,broadcastIntentLocked()會判斷當前是否有權力發出廣播,代碼截選如下:

  1. <code class="hljs lisp" style="">/* 
  2.  * Prevent non-system code (<span class="hljs-name" style="">defined</span> here to be non-persistent 
  3.  * processes) from sending protected broadcasts. 
  4.  */  
  5. if (<span class="hljs-name" style="">callingUid</span> == Process.SYSTEM_UID || callingUid == Process.PHONE_UID  
  6.         || callingUid == Process.SHELL_UID || callingUid == <span class="hljs-number" style="">0</span>)   
  7. {  
  8.     // Always okay.  
  9. }   
  10. else if (<span class="hljs-name" style="">callerApp</span> == null || !callerApp.persistent)   
  11. {  
  12.     try   
  13.     {  
  14.         if (<span class="hljs-name" style="">AppGlobals</span>.getPackageManager().isProtectedBroadcast(<span class="hljs-name" style="">intent</span>.getAction()))   
  15.         {  
  16.             String msg = <span class="hljs-string" style="">"Permission Denial: not allowed to send broadcast "</span>  
  17.                     + intent.getAction() + <span class="hljs-string" style="">" from pid="</span>  
  18.                     + callingPid + <span class="hljs-string" style="">", uid="</span> + callingUid<span class="hljs-comment" style="">;</span>  
  19.             Slog.w(<span class="hljs-name" style="">TAG</span>, msg)<span class="hljs-comment" style="">;</span>  
  20.             throw new SecurityException(<span class="hljs-name" style="">msg</span>)<span class="hljs-comment" style="">;</span>  
  21.         }  
  22.     }   
  23.     catch (<span class="hljs-name" style="">RemoteException</span> e)   
  24.     {  
  25.         Slog.w(<span class="hljs-name" style="">TAG</span>, <span class="hljs-string" style="">"Remote exception"</span>, e)<span class="hljs-comment" style="">;</span>  
  26.         return ActivityManager.BROADCAST_SUCCESS<span class="hljs-comment" style="">;</span>  
  27.     }  
  28. }</code>  

        如果發起方的Uid爲SYSTEM_UID、PHONE_UID或SHELL_UID,或者發起方具有root權限,那麼它一定有權力發送廣播。

        另外,還有一個“保護性廣播”的概念,也要考慮進來。網上有一些人詢問AndroidManifest.xml中的一級標記<protected-broadcast>是什麼意思。簡單地說,Google認爲有一些廣播是隻能由系統發送的,如果某個系統級AndroidManifest.xml中寫了這個標記,那麼在PKMS解析該文件時,就會把“保護性廣播”標記中的名字(一般是Action字符串)記錄下來。在系統運作起來之後,如果某個不具有系統權限的應用試圖發送系統中的“保護性廣播”,那麼到AMS的broadcastIntentLocked()處就會被攔住,AMS會拋出異常,提示"Permission Denial: not allowed to send broadcast"。

        我們在frameworks/base/core/res/AndroidManifest.xml文件中,可以看到<protected-broadcast>標記的具體寫法,截選如下:

 

3.1.5 必要時更新一下系統中的sticky廣播列表

        接着,broadcastIntentLocked()中會判斷當前是否在發出sticky廣播,如果是的話,必須把廣播intent記錄下來。

        一開始會判斷一下發起方是否具有發出sticky廣播的能力,比如說要擁有android.Manifest.permission.BROADCAST_STICKY權限等等。判斷合格後,broadcastIntentLocked()會更新AMS裏的一張表——mStickyBroadcasts,其大致代碼如下:

  1. <code class="hljs cpp" style="">    ArrayList<Intent> <span class="hljs-built_in" style="">list</span> = mStickyBroadcasts.get(intent.getAction());  
  2.     <span class="hljs-keyword" style="">if</span> (<span class="hljs-built_in" style="">list</span> == null)   
  3.     {  
  4.         <span class="hljs-built_in" style="">list</span> = <span class="hljs-keyword" style="">new</span> ArrayList<Intent>();  
  5.         mStickyBroadcasts.put(intent.getAction(), <span class="hljs-built_in" style="">list</span>);  
  6.     }  
  7.     <span class="hljs-keyword" style="">int</span> N = <span class="hljs-built_in" style="">list</span>.size();  
  8.     <span class="hljs-keyword" style="">int</span> i;  
  9.     <span class="hljs-keyword" style="">for</span> (i=<span class="hljs-number" style="">0</span>; i<N; i++)   
  10.     {  
  11.         <span class="hljs-keyword" style="">if</span> (intent.filterEquals(<span class="hljs-built_in" style="">list</span>.get(i)))   
  12.         {  
  13.             <span class="hljs-comment" style="">// This sticky already exists, replace it.</span>  
  14.             <span class="hljs-built_in" style="">list</span>.<span class="hljs-built_in" style="">set</span>(i, <span class="hljs-keyword" style="">new</span> Intent(intent));  
  15.             <span class="hljs-keyword" style="">break</span>;  
  16.         }  
  17.     }  
  18.     <span class="hljs-keyword" style="">if</span> (i >= N)   
  19.     {  
  20.         <span class="hljs-built_in" style="">list</span>.add(<span class="hljs-keyword" style="">new</span> Intent(intent));  
  21.     }</code>  

mStickyBroadcasts的定義是這樣的:

  1. <code class="hljs javascript" style="">    final HashMap<<span class="hljs-built_in" style="">String</span>, ArrayList<Intent>> mStickyBroadcasts =  
  2.             <span class="hljs-keyword" style="">new</span> HashMap<<span class="hljs-built_in" style="">String</span>, ArrayList<Intent>>();</code>  

上面代碼的filterEquals()函數會比較兩個intent的action、data、type、class以及categories等信息,但不會比較extra數據。如果兩個intent的action是一樣的,但其他信息不同,那麼它們在ArrayList<Intent>中會被記成兩個不同的intent。而如果發現新發送的intent在ArrayList中已經有個“相等的”舊intent時,則會用新的替掉舊的。

        以後,每當註冊新的動態receiver時,註冊動作中都會遍歷一下mStickyBroadcast表,看哪些intent可以和新receiver的filter匹配,只有匹配的intent纔會遞送給新receiver,示意圖如下:

圖中新receiver的filter只對a1和a3這兩個action感興趣,所以遍歷時就不會考慮mStickyBroadcast表中的a2表項對應的子表,而a1、a3子表所對應的若干intent中又只有一部分可以和filter匹配,比如a1的intent1以及a3的intent2,所以圖中只選擇了這兩個intent遞送給新receiver。

        除了記入mStickyBoradcast表的動作以外,sticky廣播和普通廣播在broadcastIntentLocked()中的代碼是一致的,並沒有其他什麼不同了。

 

3.1.6 嘗試向並行receivers遞送廣播

        然後broadcastIntentLocked()會嘗試向並行receivers遞送廣播。此時會調用到queue.scheduleBroadcastsLocked()。相關代碼截選如下:

  1. <code class="hljs cpp" style=""><span class="hljs-keyword" style="">int</span> NR = registeredReceivers != null ? registeredReceivers.size() : <span class="hljs-number" style="">0</span>;  
  2. <span class="hljs-keyword" style="">if</span> (!ordered && NR > <span class="hljs-number" style="">0</span>)   
  3. {  
  4.     <span class="hljs-comment" style="">// If we are not serializing this broadcast, then send the</span>  
  5.     <span class="hljs-comment" style="">// registered receivers separately so they don't wait for the</span>  
  6.     <span class="hljs-comment" style="">// components to be launched.</span>  
  7.     final BroadcastQueue <span class="hljs-built_in" style="">queue</span> = broadcastQueueForIntent(intent);  
  8.     BroadcastRecord r = <span class="hljs-keyword" style="">new</span> BroadcastRecord(<span class="hljs-built_in" style="">queue</span>, intent, callerApp,  
  9.             callerPackage, callingPid, callingUid, requiredPermission,  
  10.             registeredReceivers, resultTo, resultCode, resultData, <span class="hljs-built_in" style="">map</span>,  
  11.             ordered, sticky, <span class="hljs-literal" style="">false</span>);  
  12.     <span class="hljs-keyword" style="">if</span> (DEBUG_BROADCAST) Slog.v(  
  13.             TAG, <span class="hljs-string" style="">"Enqueueing parallel broadcast "</span> + r);  
  14.     final boolean replaced = replacePending && <span class="hljs-built_in" style="">queue</span>.replaceParallelBroadcastLocked(r);  
  15.     <span class="hljs-keyword" style="">if</span> (!replaced) {  
  16.         <span class="hljs-built_in" style="">queue</span>.enqueueParallelBroadcastLocked(r);  
  17.         <span class="hljs-built_in" style="">queue</span>.scheduleBroadcastsLocked();    <span class="hljs-comment" style="">// 注意這句。。。</span>  
  18.     }  
  19.     registeredReceivers = null;  
  20.     NR = <span class="hljs-number" style="">0</span>;  
  21. }</code>  

簡單地說就是,new一個BroadcastRecord節點,並插入BroadcastQueue內的並行處理隊列,最後發起實際的廣播調度(scheduleBroadcastsLocked())。關於上面代碼中的registeredReceivers列表,我們會在後文說明,這裏先跳過。

        其實不光並行處理部分需要一個BroadcastRecord節點,串行處理部分也需要BroadcastRecord節點。也就是說,要激發一次廣播,AMS必須構造一個或兩個BroadcastRecord節點,並將之插入合適的廣播隊列(mFgBroadcastQueue或mBgBroadcastQueue)。插入成功後,再執行隊列的scheduleBroadcastsLocked()動作,進行實際的派發調度。示意圖如下:

請注意圖中BroadcastRecord節點所攜帶的節點鏈。在mParallelBroadcasts表中,每個BroadcastRecord只可能攜帶BroadcastFilter,因爲平行處理的節點只會對應動態receiver,而所有靜態receiver只能是串行處理的。另一方面,在mOrderedBroadcasts表中,BroadcastRecord中則既可能攜帶BroadcastFilter,也可能攜帶ResolveInfo。這個其實很容易理解,首先,ResolveInfo對應靜態receiver,放到這裏自不待言,其次,如果用戶在發送廣播時明確指定要按ordered方式發送的話,那麼即使目標方的receiver是動態註冊的,它對應的BroadcastFilter也會被強制放到這裏。

        好,現在讓我們再整合一下思路。BroadcastRecord節點內部的receivers列表,記錄着和這個廣播動作相關的目標receiver信息,該列表內部的子節點可能是ResolveInfo類型的,也可能是BroadcastFilter類型的。ResolveInfo是從PKMS處查到的靜態receiver的描述信息,它的源頭是PKMS分析的那些AndroidManifest.xml文件。而BroadcastFilter事實上來自於本文一開始闡述動態receiver時,提到的AMS端的mRegisteredReceivers哈希映射表。現在,我們再畫一張示意圖:

因爲BroadcastRecord裏的BroadcastFilter,和AMS的mRegisteredReceivers表中(間接)所指的對應BroadcastFilter是同一個對象,所以我是用虛線將它們連起來的。

         Ok,我們接着看scheduleBroadcastsLocked()動作。scheduleBroadcastsLocked()的代碼如下:

【frameworks/base/services/java/com/android/server/am/BroadcastQueue.java】

  1. <code class="hljs cpp" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">scheduleBroadcastsLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">()</span></span></span><span class="hljs-function" style="">   
  2. </span></span>{  
  3.     . . . . . .  
  4.     <span class="hljs-keyword" style="">if</span> (mBroadcastsScheduled)   
  5.     {  
  6.         <span class="hljs-keyword" style="">return</span>;  
  7.     }  
  8.     mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, <span class="hljs-keyword" style="">this</span>));  
  9.     mBroadcastsScheduled = <span class="hljs-literal" style="">true</span>;  
  10. }</code>  

發出BROADCAST_INTENT_MSG消息。

        上面用到的mHandler是這樣創建的:

  1. <code class="hljs groovy" style=""><span class="hljs-keyword" style="">final</span> Handler mHandler = <span class="hljs-keyword" style="">new</span> Handler()   
  2. {  
  3.     <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">void</span> handleMessage(Message msg)   
  4.     {  
  5.         <span class="hljs-keyword" style="">switch</span> (msg.what)   
  6.         {  
  7.             <span class="hljs-keyword" style="">case</span> <span class="hljs-string" style="">BROADCAST_INTENT_MSG:</span>   
  8.             {  
  9.                 <span class="hljs-keyword" style="">if</span> (DEBUG_BROADCAST)   
  10.                     Slog.v(TAG, <span class="hljs-string" style="">"Received BROADCAST_INTENT_MSG"</span>);  
  11.                 processNextBroadcast(<span class="hljs-literal" style="">true</span>);  
  12.             }   
  13.             <span class="hljs-keyword" style="">break</span>;  
  14.               
  15.             <span class="hljs-keyword" style="">case</span> <span class="hljs-string" style="">BROADCAST_TIMEOUT_MSG:</span>   
  16.             {  
  17.                 <span class="hljs-keyword" style="">synchronized</span> (mService)   
  18.                 {  
  19.                     broadcastTimeoutLocked(<span class="hljs-literal" style="">true</span>);  
  20.                 }  
  21.             }   
  22.             <span class="hljs-keyword" style="">break</span>;  
  23.         }  
  24.     }  
  25. };</code>  

       也就是說,AMS端會在BroadcastQueue.java中的processNextBroadcast()具體處理廣播。

 

3.1.7 整理兩個receiver列表

        我們前文已經說過,有些廣播是需要有序遞送的。爲了合理處理“有序遞送”和“平行遞送”,broadcastIntentLocked()函數內部搞出了兩個list:

  1. <code class="hljs php" style=""><span class="hljs-keyword" style="">List</span> receivers = <span class="hljs-keyword" style="">null</span>;  
  2. <span class="hljs-keyword" style="">List</span><BroadcastFilter> registeredReceivers = <span class="hljs-keyword" style="">null</span>;</code>  

其中,receivers主要用於記錄“有序遞送”的receiver,而registeredReceivers則用於記錄與intent相匹配的動態註冊的receiver。

        關於這兩個list的大致運作是這樣的,我們先利用包管理器的queryIntentReceivers()接口,查詢出和intent匹配的所有靜態receivers,此時所返回的查詢結果本身已經排好序了,因此,該返回值被直接賦值給了receivers變量,代碼如下:

  1. <code class="hljs ini" style=""><span class="hljs-attr" style="">receivers</span> = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, userId);</code>  

而對於動態註冊的receiver信息,就不是從包管理器獲取了,這些信息本來就記錄在AMS之中,此時只需調用:

  1. <code class="hljs ini" style=""><span class="hljs-attr" style="">registeredReceivers</span> = mReceiverResolver.queryIntent(intent, resolvedType, <span class="hljs-literal" style="">false</span>, userId);</code>  

就可以了。注意,此時返回的registeredReceivers中的子項是沒有經過排序的。而關於PKMS的queryIntentReceivers(),我們可以參考PKMS的專題文檔,此處不再贅述。

        如果我們要“並行遞送”廣播, registeredReceivers中的各個receiver會在隨後的queue.scheduleBroadcastsLocked()動作中被並行處理掉。如果大家折回頭看看向並行receivers遞送廣播的代碼,會發現在調用完queue.scheduleBroadcastsLocked()後,registeredReceivers會被強制賦值成null值。

        如果我們要“串行遞送”廣播,那麼必須考慮把registeredReceivers表合併到receivers表中去。我們知道,一開始receivers列表中只記錄了一些靜態receiver,這些receiver將會被“有序遞送”。現在我們只需再遍歷一下registeredReceivers列表,並將其中的每個子項插入到receivers列表的合適地方,就可以合併出一條順序列表了。當然,如果registeredReceivers已經被設爲null了,就無所謂合併了。

        爲什麼靜態聲明的receiver只會“有序遞送”呢?我想也許和這種receiver的複雜性有關係,因爲在需要遞送廣播時,receiver所屬的進程可能還沒有啓動呢,所以也許會涉及到啓動進程的流程,這些都是比較複雜的流程。

        當然,上面所說的是沒有明確指定目標組件的情況,如果intent裏含有明確的目標信息,那麼就不需要調用包管理器的queryIntentReceivers()了,只需new一個ArrayList,並賦值給receivers,然後把目標組件對應的ResolveInfo信息添加進receivers數組列表即可。

 

3.1.8 嘗試逐個向串行receivers遞送廣播

        當receivers列表整理完畢之後,現在要開始嘗試逐個向串行receivers遞送廣播了。正如前文所說,這裏要重新new一個新的BroadcastRecord節點:

  1. <code class="hljs cpp" style=""><span class="hljs-keyword" style="">if</span> ((receivers != null && receivers.size() > <span class="hljs-number" style="">0</span>)  
  2.     || resultTo != null)   
  3. {  
  4.     BroadcastQueue <span class="hljs-built_in" style="">queue</span> = broadcastQueueForIntent(intent);  
  5.     BroadcastRecord r = <span class="hljs-keyword" style="">new</span> BroadcastRecord(<span class="hljs-built_in" style="">queue</span>, intent, callerApp,  
  6.             callerPackage, callingPid, callingUid, requiredPermission,  
  7.             receivers, resultTo, resultCode, resultData, <span class="hljs-built_in" style="">map</span>, ordered,  
  8.             sticky, <span class="hljs-literal" style="">false</span>);  
  9.     . . . . . .  
  10.     boolean replaced = replacePending && <span class="hljs-built_in" style="">queue</span>.replaceOrderedBroadcastLocked(r);   
  11.     <span class="hljs-keyword" style="">if</span> (!replaced) {  
  12.         <span class="hljs-built_in" style="">queue</span>.enqueueOrderedBroadcastLocked(r);  
  13.         <span class="hljs-built_in" style="">queue</span>.scheduleBroadcastsLocked();  
  14.     }  
  15. }</code>  

而scheduleBroadcastsLocked()最終會間接導致走到 BroadcastQueue.java中的processNextBroadcast()。這一點和前文所說的“向並行receivers遞送廣播”的動作基本一致。

        下面我們來看,遞送廣播動作中最重要的processNextBroadcast()。

 

3.2 最重要的processNextBroadcast()

        從processNextBroadcast()的代碼,我們就可以看清楚前面說的“平行廣播”、“有序廣播”和“動態receiver”、“靜態receiver”之間的關係了。

        我們在前文已經說過,所有的靜態receiver都是串行處理的,而動態receiver則會按照發廣播時指定的方式,進行“並行”或“串行”處理。能夠並行處理的廣播,其對應的若干receiver一定都已經存在了,不會牽扯到啓動新進程的操作,所以可以在一個while循環中,一次性全部deliver。而有序廣播,則需要一個一個地處理,其滾動處理的手段是發送事件,也就是說,在一個receiver處理完畢後,會利用廣播隊列(BroadcastQueue)的mHandler,發送一個BROADCAST_INTENT_MSG事件,從而執行下一次的processNextBroadcast()。

        processNextBroadcast()的代碼邏輯大體是這樣的:先嚐試處理BroadcastQueue中的“平行廣播”部分。這需要遍歷並行列表(mParallelBroadcasts)的每一個BroadcastRecord以及其中的receivers列表。對於平行廣播而言,receivers列表中的每個子節點是個BroadcastFilter。我們直接將廣播遞送出去即可:

  1. <code class="hljs gradle" style=""><span class="hljs-keyword" style="">while</span> (mParallelBroadcasts.<span class="hljs-keyword" style="">size</span>() > <span class="hljs-number" style="">0</span>)   
  2. {  
  3.     r = mParallelBroadcasts.remove(<span class="hljs-number" style="">0</span>);  
  4.     r.dispatchTime = SystemClock.uptimeMillis();  
  5.     r.dispatchClockTime = System.currentTimeMillis();  
  6.     <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> N = r.receivers.<span class="hljs-keyword" style="">size</span>();  
  7.     . . . . . .   
  8.     <span class="hljs-keyword" style="">for</span> (<span class="hljs-keyword" style="">int</span> i=<span class="hljs-number" style="">0</span>; i<N; i++)   
  9.     {  
  10.         Object target = r.receivers.get(i);  
  11.         . . . . . .  
  12.         deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, <span class="hljs-keyword" style="">false</span>);  
  13.     }  
  14.     . . . . . .  
  15. }</code>  

 

3.2.1 用deliverToRegisteredReceiverLocked()遞送到平行動態receiver

        deliverToRegisteredReceiverLocked()的代碼截選如下:

【frameworks/base/services/java/com/android/server/am/BroadcastQueue.java】

  1. <code class="hljs swift" style=""><span class="hljs-keyword" style="">private</span> <span class="hljs-keyword" style="">final</span> void deliverToRegisteredReceiverLocked(<span class="hljs-type" style="">BroadcastRecord</span> r,  
  2.                                                      <span class="hljs-type" style="">BroadcastFilter</span> <span class="hljs-built_in" style="">filter</span>,   
  3.                                                      boolean ordered)   
  4. {  
  5.     . . . . . .  
  6.     . . . . . .  
  7.     <span class="hljs-keyword" style="">if</span> (!skip)   
  8.     {  
  9.         <span class="hljs-keyword" style="">if</span> (ordered)   
  10.         {  
  11.             r.receiver = <span class="hljs-built_in" style="">filter</span>.receiverList.receiver.asBinder();  
  12.             r.curFilter = <span class="hljs-built_in" style="">filter</span>;  
  13.             <span class="hljs-built_in" style="">filter</span>.receiverList.curBroadcast = r;  
  14.             r.state = <span class="hljs-type" style="">BroadcastRecord</span>.<span class="hljs-type" style="">CALL_IN_RECEIVE</span>;  
  15.             <span class="hljs-keyword" style="">if</span> (<span class="hljs-built_in" style="">filter</span>.receiverList.app != null)   
  16.             {  
  17.                 r.curApp = <span class="hljs-built_in" style="">filter</span>.receiverList.app;  
  18.                 <span class="hljs-built_in" style="">filter</span>.receiverList.app.curReceiver = r;  
  19.                 mService.updateOomAdjLocked();  
  20.             }  
  21.         }  
  22.           
  23.             . . . . . .  
  24.             performReceiveLocked(<span class="hljs-built_in" style="">filter</span>.receiverList.app,   
  25.                                  <span class="hljs-built_in" style="">filter</span>.receiverList.receiver,  
  26.                                  new <span class="hljs-type" style="">Intent</span>(r.intent), r.resultCode,  
  27.                                  r.resultData, r.resultExtras,   
  28.                                  r.ordered, r.initialSticky);  
  29.             <span class="hljs-keyword" style="">if</span> (ordered)   
  30.             {  
  31.                 r.state = <span class="hljs-type" style="">BroadcastRecord</span>.<span class="hljs-type" style="">CALL_DONE_RECEIVE</span>;  
  32.             }  
  33.           
  34.         . . . . . .  
  35. }</code>  

 

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">private</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">static</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">performReceiveLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(ProcessRecord app, IIntentReceiver receiver,  
  2.         Intent intent, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode, String data, Bundle extras,  
  3.         </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> ordered, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> sticky)</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">throws</span></span></span><span class="hljs-function" style=""> RemoteException   
  4. </span></span>{  
  5.     <span class="hljs-comment" style="">// Send the intent to the receiver asynchronously using one-way binder calls.</span>  
  6.     <span class="hljs-keyword" style="">if</span> (app != <span class="hljs-keyword" style="">null</span> && app.thread != <span class="hljs-keyword" style="">null</span>)   
  7.     {  
  8.         <span class="hljs-comment" style="">// If we have an app thread, do the call through that so it is</span>  
  9.         <span class="hljs-comment" style="">// correctly ordered with other one-way calls.</span>  
  10.         app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,  
  11.                 data, extras, ordered, sticky);  
  12.     }   
  13.     <span class="hljs-keyword" style="">else</span>   
  14.     {  
  15.         receiver.performReceive(intent, resultCode, data, extras, ordered, sticky);  
  16.     }  
  17. }</code>  

終於通過app.thread向用戶進程傳遞語義了。注意scheduleRegisteredReceiver()的receiver參數,它對應的就是前文所說的ReceiverDispatcher的Binder實體——InnerReceiver了。

        總之,當語義傳遞到用戶進程的ApplicationThread以後,走到:

  1. <code class="hljs java" style=""><span class="hljs-comment" style="">// This function exists to make sure all receiver dispatching is</span>  
  2. <span class="hljs-comment" style="">// correctly ordered, since these are one-way calls and the binder driver</span>  
  3. <span class="hljs-comment" style="">// applies transaction ordering per object for such calls.</span>  
  4. <span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">scheduleRegisteredReceiver</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(IIntentReceiver receiver, Intent intent,  
  5.         </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode, String dataStr, Bundle extras, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> ordered,  
  6.         </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> sticky)</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">throws</span></span></span><span class="hljs-function" style=""> RemoteException   
  7. </span></span>{  
  8.     receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky);  
  9. }</code>  

終於走到ReceiverDispatcher的InnerReceiver了:

  1. <code class="hljs java" style=""><span class="hljs-keyword" style="">static</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">ReceiverDispatcher</span></span></span><span class="hljs-class" style="">   
  2. </span></span>{  
  3.     <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">static</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">InnerReceiver</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">extends</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">IIntentReceiver</span></span></span><span class="hljs-class" style="">.</span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">Stub</span></span></span><span class="hljs-class" style="">   
  4.     </span></span>{  
  5.         . . . . . .  
  6.         . . . . . .  
  7.         <span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">performReceive</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(Intent intent, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode,  
  8.                                    String data, Bundle extras,   
  9.                                    </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> ordered, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> sticky)</span></span></span><span class="hljs-function" style="">   
  10.         </span></span>{  
  11.             LoadedApk.ReceiverDispatcher rd = mDispatcher.get();  
  12.             . . . . . .  
  13.             <span class="hljs-keyword" style="">if</span> (rd != <span class="hljs-keyword" style="">null</span>) {  
  14.                 rd.performReceive(intent, resultCode, data, extras,  
  15.                                   ordered, sticky);  
  16.             }   
  17.             . . . . . .  
  18.         }  
  19.     }  
  20.     . . . . . .  
  21.     <span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">performReceive</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(Intent intent, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode,  
  22.                                String data, Bundle extras,   
  23.                                </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> ordered, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> sticky)</span></span></span><span class="hljs-function" style="">   
  24.     </span></span>{  
  25.         . . . . . .  
  26.         Args args = <span class="hljs-keyword" style="">new</span> Args(intent, resultCode, data, extras, ordered, sticky);  
  27.         <span class="hljs-keyword" style="">if</span> (!mActivityThread.post(args)) <span class="hljs-comment" style="">// 請注意這一句!</span>  
  28.         {  
  29.             <span class="hljs-keyword" style="">if</span> (mRegistered && ordered)   
  30.             {  
  31.                 IActivityManager mgr = ActivityManagerNative.getDefault();  
  32.                 <span class="hljs-keyword" style="">if</span> (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,  
  33.                         <span class="hljs-string" style="">"Finishing sync broadcast to "</span> + mReceiver);  
  34.                 args.sendFinished(mgr);  
  35.             }  
  36.         }  
  37.     }  
  38. }</code>  

請注意mActivityThread.post(args)一句,這樣,事件泵最終會回調Args參數的run()成員函數:

  1. <code class="hljs java" style=""><span class="hljs-keyword" style="">final</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">class</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">Args</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">extends</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">BroadcastReceiver</span></span></span><span class="hljs-class" style="">.</span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">PendingResult</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">implements</span></span></span><span class="hljs-class" style=""> </span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">Runnable</span></span></span><span class="hljs-class" style="">   
  2. </span></span>{  
  3.     . . . . . .  
  4.     . . . . . .  
  5.     <span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">run</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">()</span></span></span><span class="hljs-function" style="">   
  6.     </span></span>{  
  7.         <span class="hljs-keyword" style="">final</span> BroadcastReceiver receiver = mReceiver;  
  8.         . . . . . .  
  9.         <span class="hljs-keyword" style="">try</span> {  
  10.             ClassLoader cl =  mReceiver.getClass().getClassLoader();  
  11.             intent.setExtrasClassLoader(cl);  
  12.             setExtrasClassLoader(cl);  
  13.             receiver.setPendingResult(<span class="hljs-keyword" style="">this</span>);  
  14.             receiver.onReceive(mContext, intent);  <span class="hljs-comment" style="">// 回調具體receiver的onReceive()</span>  
  15.         } <span class="hljs-keyword" style="">catch</span> (Exception e) {  
  16.             . . . . . .  
  17.         }  
  18.           
  19.         <span class="hljs-keyword" style="">if</span> (receiver.getPendingResult() != <span class="hljs-keyword" style="">null</span>) {  
  20.             finish();  
  21.         }  
  22.         . . . . . .  
  23.     }  
  24. }</code>  

其中的那句receiver.onReceive(this),正是回調我們具體receiver的onReceive()成員函數的地方。噢,終於看到應用程序員熟悉的onReceive()了。這部分的示意圖如下:

3.2.2 靜態receiver的遞送

        說完動態遞送,我們再來看靜態遞送。對於靜態receiver,情況會複雜很多,因爲靜態receiver所從屬的進程有可能還沒有運行起來呢。此時BroadcastRecord節點中記錄的子列表的節點是ResolveInfo對象。

  1. <code class="hljs tcl" style="">ResolveInfo <span class="hljs-keyword" style="">info</span> = (ResolveInfo)nextReceiver;  
  2. . . . . . .  
  3. r.state = BroadcastRecord.APP_RECEIVE;  
  4. String targetProcess = <span class="hljs-keyword" style="">info</span>.activityInfo.processName;</code>  

那麼我們要先找到receiver所從屬的進程的進程名。

         processNextBroadcast()中啓動進程的代碼如下:

  1. <code class="hljs kotlin" style="">ProcessRecord app = mService.getProcessRecordLocked(targetProcess,   
  2. info.activityInfo.applicationInfo.uid);  
  3. . . . . . .  
  4. <span class="hljs-keyword" style="">if</span> (app != <span class="hljs-literal" style="">null</span> && app.thread != <span class="hljs-literal" style="">null</span>)   
  5. {  
  6.     . . . . . .  
  7.     app.addPackage(info.activityInfo.packageName);  
  8.     processCurBroadcastLocked(r, app);  
  9.     <span class="hljs-keyword" style="">return</span>;  
  10.     . . . . . .  
  11. }  
  12. r.curApp = mService.startProcessLocked(targetProcess,  
  13.                                info.activityInfo.applicationInfo, <span class="hljs-literal" style="">true</span>,  
  14.                                r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,  
  15.                                <span class="hljs-string" style="">"broadcast"</span>, r.curComponent,                
  16.                                (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != <span class="hljs-number" style="">0</span>,   
  17.                                <span class="hljs-literal" style="">false</span>)  
  18. . . . . . .  
  19. mPendingBroadcast = r;  
  20. mPendingBroadcastRecvIndex = recIdx;</code>  

        如果目標進程已經存在了,那麼app.thread肯定不爲null,直接調用processCurBroadcastLocked()即可,否則就需要啓動新進程了。啓動的過程是異步的,可能很耗時,所以要把BroadcastRecord節點記入mPendingBroadcast。

3.2.2.1 processCurBroadcastLocked()

        我們先說processCurBroadcastLocked()。

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">private</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">final</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">processCurBroadcastLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(BroadcastRecord r,  
  2.                         ProcessRecord app)</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">throws</span></span></span><span class="hljs-function" style=""> RemoteException   
  3. </span></span>{  
  4.     . . . . . .  
  5.     r.receiver = app.thread.asBinder();  
  6.     r.curApp = app;  
  7.     app.curReceiver = r;  
  8.     . . . . . .  
  9.     . . . . . .  
  10.         app.thread.scheduleReceiver(<span class="hljs-keyword" style="">new</span> Intent(r.intent), r.curReceiver,  
  11.               mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),  
  12.               r.resultCode, r.resultData, r.resultExtras, r.ordered);  
  13.         . . . . . .  
  14.         started = <span class="hljs-keyword" style="">true</span>;  
  15.     . . . . . .  
  16. }</code>  

其中最重要的是調用app.thread.scheduleReceiver()的那句。在IApplicationThread接口中,是這樣定義scheduleReceiver()函數原型的:

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">scheduleReceiver</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(Intent intent, ActivityInfo info,   
  2.                       CompatibilityInfo compatInfo,                        
  3.                       </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode, String data,   
  4.                       Bundle extras, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> sync)</span></span></span><span class="hljs-function" style="">   
  5.                       </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">throws</span></span></span><span class="hljs-function" style=""> RemoteException</span></span>;</code>  

其中ActivityInfo info參數,記錄着目標receiver的信息。可以看到,遞送靜態receiver時,是不會用到RecevierDispatcher的。

        用戶進程裏handleMessage()

  1. <code class="hljs css" style=""><span class="hljs-selector-tag" style="">case</span> <span class="hljs-selector-tag" style="">RECEIVER</span>:  
  2.     <span class="hljs-selector-tag" style="">Trace</span><span class="hljs-selector-class" style="">.traceBegin</span>(<span class="hljs-selector-tag" style="">Trace</span><span class="hljs-selector-class" style="">.TRACE_TAG_ACTIVITY_MANAGER</span>, "<span class="hljs-selector-tag" style="">broadcastReceiveComp</span>");  
  3.     <span class="hljs-selector-tag" style="">handleReceiver</span>((<span class="hljs-selector-tag" style="">ReceiverData</span>)<span class="hljs-selector-tag" style="">msg</span><span class="hljs-selector-class" style="">.obj</span>);  
  4.     <span class="hljs-selector-tag" style="">maybeSnapshot</span>();  
  5.     <span class="hljs-selector-tag" style="">Trace</span><span class="hljs-selector-class" style="">.traceEnd</span>(<span class="hljs-selector-tag" style="">Trace</span><span class="hljs-selector-class" style="">.TRACE_TAG_ACTIVITY_MANAGER</span>);      
  6.     <span class="hljs-selector-tag" style="">break</span>;</code>  

        ActivityThread中,會運用反射機制,創建出BroadcastReceiver對象,而後回調該對象的onReceive()成員函數。

  1. <code class="hljs haskell" style=""><span class="hljs-title" style="">private</span> void handleReceiver(<span class="hljs-type" style="">ReceiverData</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">) </span></span>  
  2. {  
  3.     . . . . . .  
  4.     <span class="hljs-type" style="">IActivityManager</span> mgr = <span class="hljs-type" style="">ActivityManagerNative</span>.getDefault();  
  5.     <span class="hljs-type" style="">BroadcastReceiver</span> receiver;  
  6.     try {  
  7.         java.lang.<span class="hljs-type" style="">ClassLoader</span> cl = packageInfo.getClassLoader();  
  8.         <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">.intent.setExtrasClassLoader(</span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">cl</span></span></span><span class="hljs-class" style="">);</span></span>  
  9.         <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">.setExtrasClassLoader(</span><span class="hljs-title" style=""><span class="hljs-class" style=""><span class="hljs-title" style="">cl</span></span></span><span class="hljs-class" style="">);</span></span>  
  10.         receiver = (<span class="hljs-type" style="">BroadcastReceiver</span>)cl.loadClass(component).newInstance();  
  11.     } catch (<span class="hljs-type" style="">Exception</span> e) {  
  12.         . . . . . .  
  13.     }  
  14.     try {  
  15.         . . . . . .  
  16.         receiver.setPendingResult(<span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">);</span></span>  
  17.         receiver.onReceive(context.getReceiverRestrictedContext(), <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">.intent);</span></span>  
  18.     } catch (<span class="hljs-type" style="">Exception</span> e) {  
  19.         . . . . . .  
  20.     } finally {  
  21.         sCurrentBroadcastIntent.set(null);  
  22.     }  
  23.     <span class="hljs-keyword" style="">if</span> (receiver.getPendingResult() != null) {  
  24.         <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">.finish();</span></span>  
  25.     }  
  26. }</code>  

3.2.2.2 必要時啓動新進程

        現在我們回過頭來看,在目標進程尚未啓動的情況下,是如何完成遞送的。剛剛我們已經看到調用startProcessLocked()的句子了,只要不出問題,目標進程成功啓動後就會調用AMS的attachApplication()。

        有關attachApplication()的詳情,請參考其他關於AMS的文檔,此處我們只需知道它裏面又會調用attachApplicationLocked()函數。

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">private</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">final</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">boolean</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">attachApplicationLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(IApplicationThread thread, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> pid)</span></span></span></span></code>  

attachApplicationLocked()內有這麼幾句:

  1. <code class="hljs php" style=""><span class="hljs-comment" style="">// Check if a next-broadcast receiver is in this process...</span>  
  2. <span class="hljs-keyword" style="">if</span> (!badApp && isPendingBroadcastProcessLocked(pid)) {  
  3.     <span class="hljs-keyword" style="">try</span> {  
  4.         didSomething = sendPendingBroadcastsLocked(app);  
  5.     } <span class="hljs-keyword" style="">catch</span> (<span class="hljs-keyword" style="">Exception</span> e) {  
  6.         <span class="hljs-comment" style="">// If the app died trying to launch the receiver we declare it 'bad'</span>  
  7.         badApp = <span class="hljs-keyword" style="">true</span>;  
  8.     }  
  9. }</code>  

它們的意思是,如果新啓動的進程就是剛剛mPendingBroadcast所記錄的進程的話,此時AMS就會執行sendPendingBroadcastsLocked(app)一句。

 

  1. <code class="hljs cpp" style=""><span class="hljs-comment" style="">// The app just attached; send any pending broadcasts that it should receive</span>  
  2. <span class="hljs-function" style=""><span class="hljs-function" style="">boolean </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">sendPendingBroadcastsLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(ProcessRecord app)</span></span></span><span class="hljs-function" style=""> </span></span>{  
  3.     boolean didSomething = <span class="hljs-literal" style="">false</span>;  
  4.     <span class="hljs-keyword" style="">for</span> (BroadcastQueue <span class="hljs-built_in" style="">queue</span> : mBroadcastQueues) {  
  5.         didSomething |= <span class="hljs-built_in" style="">queue</span>.sendPendingBroadcastsLocked(app);  
  6.     }  
  7.     <span class="hljs-keyword" style="">return</span> didSomething;  
  8. }</code>  

BroadcastQueue的sendPendingBroadcastsLocked()函數如下:

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">boolean</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">sendPendingBroadcastsLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(ProcessRecord app)</span></span></span><span class="hljs-function" style=""> </span></span>{  
  2.     <span class="hljs-keyword" style="">boolean</span> didSomething = <span class="hljs-keyword" style="">false</span>;  
  3.     <span class="hljs-keyword" style="">final</span> BroadcastRecord br = mPendingBroadcast;  
  4.     <span class="hljs-keyword" style="">if</span> (br != <span class="hljs-keyword" style="">null</span> && br.curApp.pid == app.pid) {  
  5.         <span class="hljs-keyword" style="">try</span> {  
  6.             mPendingBroadcast = <span class="hljs-keyword" style="">null</span>;  
  7.             processCurBroadcastLocked(br, app);  
  8.             didSomething = <span class="hljs-keyword" style="">true</span>;  
  9.         } <span class="hljs-keyword" style="">catch</span> (Exception e) {  
  10.             . . . . . .  
  11.         }  
  12.     }  
  13.     <span class="hljs-keyword" style="">return</span> didSomething;  
  14. }</code>  

可以看到,既然目標進程已經成功啓動了,那麼mPendingBroadcast就可以賦值爲null了。接着,sendPendingBroadcastsLocked()會調用前文剛剛闡述的processCurBroadcastLocked(),其內再通過app.thread.scheduleReceiver(),將語義發送到用戶進程,完成真正的廣播遞送。這部分在上一小節已有闡述,這裏就不多說了。

 

3.2.3 說說有序廣播是如何循環起來的?

        我們知道,平行廣播的循環很簡單,只是在一個while循環裏對每個動態receiver執行deliverToRegisteredReceiverLocked()即可。而對有序廣播來說,原則上每次processNextBroadcast()只會處理一個BroadcastRecord的一個receiver而已。當然,此時摘下的receiver既有可能是動態註冊的,也有可能是靜態的。

        對於動態註冊的receiver,目標進程處理完廣播之後,會間接調用am.finishReceiver()向AMS發出反饋,關於這一步,其實在前面羅列ReceiverDispatcher的performReceive()時已經出現過了,我們再列一下:

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">performReceive</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(Intent intent, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode,  
  2.                            String data, Bundle extras,   
  3.                            </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> ordered, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> sticky)</span></span></span><span class="hljs-function" style="">   
  4. </span></span>{  
  5.     . . . . . .  
  6.     Args args = <span class="hljs-keyword" style="">new</span> Args(intent, resultCode, data, extras, ordered, sticky);  
  7.     <span class="hljs-keyword" style="">if</span> (!mActivityThread.post(args))   
  8.     {  
  9.         <span class="hljs-keyword" style="">if</span> (mRegistered && ordered)   
  10.         {  
  11.             IActivityManager mgr = ActivityManagerNative.getDefault();  
  12.             . . . . . .  
  13.             args.sendFinished(mgr);  <span class="hljs-comment" style="">// 請注意這一句!</span>  
  14.         }  
  15.     }  
  16. }</code>  

Args繼承於BroadcastReceiver.PendingResult,它調用的sendFinished()就是PendingResult的sendFinished():

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">sendFinished</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(IActivityManager am)</span></span></span><span class="hljs-function" style="">   
  2. </span></span>{  
  3.     <span class="hljs-keyword" style="">synchronized</span> (<span class="hljs-keyword" style="">this</span>) {  
  4.         <span class="hljs-keyword" style="">if</span> (mFinished) {  
  5.             <span class="hljs-keyword" style="">throw</span> <span class="hljs-keyword" style="">new</span> IllegalStateException(<span class="hljs-string" style="">"Broadcast already finished"</span>);  
  6.         }  
  7.         mFinished = <span class="hljs-keyword" style="">true</span>;  
  8.       
  9.         <span class="hljs-keyword" style="">try</span> {  
  10.             <span class="hljs-keyword" style="">if</span> (mResultExtras != <span class="hljs-keyword" style="">null</span>) {  
  11.                 mResultExtras.setAllowFds(<span class="hljs-keyword" style="">false</span>);  
  12.             }  
  13.             <span class="hljs-keyword" style="">if</span> (mOrderedHint) {  
  14.                 am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,  
  15.                         mAbortBroadcast);  
  16.             } <span class="hljs-keyword" style="">else</span> {  
  17.                 <span class="hljs-comment" style="">// This broadcast was sent to a component; it is not ordered,</span>  
  18.                 <span class="hljs-comment" style="">// but we still need to tell the activity manager we are done.</span>  
  19.                 am.finishReceiver(mToken, <span class="hljs-number" style="">0</span>, <span class="hljs-keyword" style="">null</span>, <span class="hljs-keyword" style="">null</span>, <span class="hljs-keyword" style="">false</span>);  
  20.             }  
  21.         } <span class="hljs-keyword" style="">catch</span> (RemoteException ex) {  
  22.         }  
  23.     }  
  24. }</code>  

代碼中的am.finishReceiver()會通知AMS,表示用戶側receiver已經處理好了,或者至少告一段落了,請AMS進行下一步動作。

        而對於動態註冊的receiver,情況是類似的,最終也是調用am.finishReceiver()向AMS發出回饋的,只不過發起的動作是在ActivityThread的handleReceiver()動作中。前文已經列過這個函數了,大家注意下面的句子即可:

  1. <code class="hljs haskell" style=""><span class="hljs-title" style="">private</span> void handleReceiver(<span class="hljs-type" style="">ReceiverData</span> <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">) </span></span>  
  2. {  
  3.         . . . . . .  
  4.         receiver.setPendingResult(<span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">);</span></span>  
  5.         receiver.onReceive(context.getReceiverRestrictedContext(),  
  6.                 <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">.intent);</span></span>  
  7.         . . . . . .  
  8.     <span class="hljs-keyword" style="">if</span> (receiver.getPendingResult() != null) {  
  9.         <span class="hljs-class" style=""><span class="hljs-keyword" style=""><span class="hljs-class" style=""><span class="hljs-keyword" style="">data</span></span></span><span class="hljs-class" style="">.finish();</span></span>  
  10.     }  
  11. }</code>  

ReceiverData也是繼承於BroadcastReceiver.PendingResult的,它調用的finish()是PendingResult的finish():

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">final</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">finish</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">()</span></span></span><span class="hljs-function" style="">   
  2. </span></span>{  
  3.     <span class="hljs-keyword" style="">if</span> (mType == TYPE_COMPONENT) {  
  4.         . . . . . .  
  5.     } <span class="hljs-keyword" style="">else</span> <span class="hljs-keyword" style="">if</span> (mOrderedHint && mType != TYPE_UNREGISTERED) {  
  6.         <span class="hljs-keyword" style="">if</span> (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,  
  7.                 <span class="hljs-string" style="">"Finishing broadcast to "</span> + mToken);  
  8.         <span class="hljs-keyword" style="">final</span> IActivityManager mgr = ActivityManagerNative.getDefault();  
  9.         sendFinished(mgr);  
  10.     }  
  11. }</code>  

此處的sendFinished()內部最終也會調用到am.finishReceiver(),向AMS通告receiver已經處理好了。

         AMS側在收到finishReceiver語義後,執行:

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">finishReceiver</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(IBinder who, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">int</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultCode, String resultData,  
  2.         Bundle resultExtras, </span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> resultAbort)</span></span></span><span class="hljs-function" style="">   
  3. </span></span>{  
  4.     . . . . . .  
  5.     <span class="hljs-keyword" style="">try</span> {  
  6.         <span class="hljs-keyword" style="">boolean</span> doNext = <span class="hljs-keyword" style="">false</span>;  
  7.         BroadcastRecord r = <span class="hljs-keyword" style="">null</span>;  
  8.         <span class="hljs-keyword" style="">synchronized</span>(<span class="hljs-keyword" style="">this</span>) {  
  9.             r = broadcastRecordForReceiverLocked(who);  
  10.             <span class="hljs-keyword" style="">if</span> (r != <span class="hljs-keyword" style="">null</span>) {  
  11.                 doNext = r.queue.finishReceiverLocked(r, resultCode,  
  12.                     resultData, resultExtras, resultAbort, <span class="hljs-keyword" style="">true</span>);  
  13.             }  
  14.         }  
  15.         <span class="hljs-keyword" style="">if</span> (doNext) {  
  16.             r.queue.processNextBroadcast(<span class="hljs-keyword" style="">false</span>);  
  17.         }  
  18.         trimApplications();  
  19.     } <span class="hljs-keyword" style="">finally</span> {  
  20.         Binder.restoreCallingIdentity(origId);  
  21.     }  
  22. }</code>  

可以看到,如有必要,會繼續調用processNextBroadcast(),從而完成有序廣播的循環處理。

 

3.2.4 說說有序廣播的timeout處理

        因爲AMS很難知道一次廣播究竟能不能完全成功遞送出去,所以它必須實現一種“時限機制”。前文在闡述broadcastIntentLocked()時,提到過new一個BroadcastRecord節點,並插入一個BroadcastQueue裏的“平行列表”或者“有序列表”。不過當時我們沒有太細說那個BroadcastQueue,現在我們多加一點兒說明。

        實際上系統中有兩個BroadcastQueue,一個叫做“前臺廣播隊列”,另一個叫“後臺廣播隊列”,在AMS裏是這樣定義的:

  1. <code class="hljs nginx" style=""><span class="hljs-attribute" style="">BroadcastQueue</span> mFgBroadcastQueue;  
  2. <span class="hljs-attribute" style="">BroadcastQueue</span> mBgBroadcastQueue;</code>  

爲什麼要搞出兩個隊列呢?我認爲這是因爲系統對“廣播時限”的要求不同導致的。對於前臺廣播隊列而言,它裏面的每個廣播必須在10秒之內把廣播遞送給receiver,而後臺廣播隊列的時限比較寬,只需60秒之內遞送到就可以了。具體時限值請看BroadcastQueue的mTimeoutPeriod域。注意,這個10秒或60秒限制是針對一個receiver而言的。比方說“前臺廣播隊列”的某個BroadcastRecord節點對應了3個receiver,那麼在處理這個廣播節點時,只要能在30秒(3 x 10)之內搞定就可以了。事實上,AMS系統考慮了更多東西,所以它給一個BroadcastRecord的總時限是其所有receiver時限之和的2倍,在此例中就是60秒(2 x 3 x 10)。

        對於平行receiver而言,時限的作用小一點兒,因爲動態receiver是直接遞送到目標進程的,它不考慮目標端是什麼時候處理完這個廣播的。

        然而對於有序receiver來說,時限就比較重要了。因爲receiver之間必須是串行處理的,也就是說上一個receiver在沒處理完時,系統是不會讓下一個receiver進行處理的。從processNextBroadcast()的代碼來看,在處理有序receiver時,BroadcastRecord裏的nextReceiver域會記錄“下一個應該處理的receiver”的標號。只有在BroadcastRecord的所有receiver都處理完後,或者BroadcastRecord的處理時間超過了總時限的情況下,系統纔會把這個BroadcastRecord節點從隊列裏刪除。因此我們在processNextBroadcast()裏看到的獲取當前BroadcastRecord的句子是寫死爲r = mOrderedBroadcasts.get(0)的。

        在拿到當前BroadcastRecord之後,利用nextReceiver值拿到當前該處理的receiver信息:

  1. <code class="hljs cs" style=""><span class="hljs-keyword" style="">int</span> recIdx = r.nextReceiver++;  
  2. . . . . . .  
  3. Object nextReceiver = r.receivers.<span class="hljs-keyword" style="">get</span>(recIdx);</code>  

當然,一開始,nextReceiver的值只會是0,表示第一個receiver有待處理,此時會給BroadcastRecord的dispatchTime域賦值。

  1. <code class="hljs cpp" style=""><span class="hljs-keyword" style="">int</span> recIdx = r.nextReceiver++;  
  2. r.receiverTime = SystemClock.uptimeMillis();<span class="hljs-keyword" style="">if</span> (recIdx == <span class="hljs-number" style="">0</span>) {  
  3.     r.dispatchTime = r.receiverTime;  
  4.     r.dispatchClockTime = System.currentTimeMillis();  
  5.     . . . . . .  
  6. }</code>  

也就是說,dispatchTime的意義是標記實際處理BroadcastRecord的起始時間,那麼這個BroadcastRecord所能允許的最大時限值就是:

dispatchTime + 2 * mTimeoutPeriod * 其receiver總數

一旦超過這個時限,而BroadcastRecord又沒有處理完,那麼就強制結束這個BroadcastRecord節點:

  1. <code class="hljs actionscript" style=""><span class="hljs-keyword" style="">if</span> ((numReceivers > <span class="hljs-number" style="">0</span>) &&  
  2.         (now > r.dispatchTime + (<span class="hljs-number" style="">2</span>*mTimeoutPeriod*numReceivers)))   
  3. {  
  4.     . . . . . .  
  5.     broadcastTimeoutLocked(<span class="hljs-literal" style="">false</span>); <span class="hljs-comment" style="">// forcibly finish this broadcast</span>  
  6.     forceReceive = <span class="hljs-literal" style="">true</span>;  
  7.     r.state = BroadcastRecord.IDLE;  
  8. }</code>  

此處調用的broadcastTimeoutLocked()的參數是boolean fromMsg,表示這個函數是否是在處理“時限消息”的地方調用的,因爲當前是在processNextBroadcast()函數裏調用broadcastTimeoutLocked()的,所以這個參數爲false。從這個參數也可以看出,另一處判斷“處理已經超時”的地方是在消息處理機制裏,在那個地方,fromMsg參數應該設爲true。

        大體上說,每當processNextBroadcast()準備遞送receiver時,會調用setBroadcastTimeoutLocked()設置一個延遲消息:

  1. <code class="hljs bash" style="">long timeoutTime = r.receiverTime + mTimeoutPeriod;  
  2. . . . . . .  
  3. <span class="hljs-built_in" style="">set</span>BroadcastTimeoutLocked(timeoutTime);</code>  

setBroadcastTimeoutLocked()的代碼如下:

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">final</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">setBroadcastTimeoutLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(</span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">long</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> timeoutTime)</span></span></span><span class="hljs-function" style="">   
  2. </span></span>{    <span class="hljs-keyword" style="">if</span> (! mPendingBroadcastTimeoutMessage)   
  3.     {  
  4.         Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, <span class="hljs-keyword" style="">this</span>);  
  5.         mHandler.sendMessageAtTime(msg, timeoutTime);  
  6.         mPendingBroadcastTimeoutMessage = <span class="hljs-keyword" style="">true</span>;  
  7.     }  
  8. }</code>  

只要我們的receiver能及時處理廣播,系統就會cancel上面的延遲消息。這也就是說,但凡事件泵的handleMessage()開始處理這個消息,就說明receiver處理超時了。此時,系統會放棄處理這個receiver,並接着嘗試處理下一個receiver。

  1. <code class="hljs java" style=""><span class="hljs-keyword" style="">final</span> Handler mHandler = <span class="hljs-keyword" style="">new</span> Handler()   
  2. {  
  3.     <span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">public</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">handleMessage</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(Message msg)</span></span></span><span class="hljs-function" style=""> </span></span>{  
  4.         <span class="hljs-keyword" style="">switch</span> (msg.what)   
  5.         {  
  6.             . . . . . .  
  7.             <span class="hljs-keyword" style="">case</span> BROADCAST_TIMEOUT_MSG:   
  8.             {  
  9.                 <span class="hljs-keyword" style="">synchronized</span> (mService)   
  10.                 {  
  11.                     broadcastTimeoutLocked(<span class="hljs-keyword" style="">true</span>);  
  12.                 }  
  13.             } <span class="hljs-keyword" style="">break</span>;  
  14.         }  
  15.     }  
  16. };</code>  

broadcastTimeoutLocked()的代碼截選如下:

  1. <code class="hljs java" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">final</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-keyword" style="">void</span></span></span><span class="hljs-function" style=""> </span><span class="hljs-title" style=""><span class="hljs-function" style=""><span class="hljs-title" style="">broadcastTimeoutLocked</span></span></span><span class="hljs-params" style=""><span class="hljs-function" style=""><span class="hljs-params" style="">(</span></span><span class="hljs-keyword" style=""><span class="hljs-function" style=""><span class="hljs-params" style=""><span class="hljs-keyword" style="">boolean</span></span></span></span><span class="hljs-function" style=""><span class="hljs-params" style=""> fromMsg)</span></span></span><span class="hljs-function" style="">   
  2. </span></span>{  
  3.     <span class="hljs-keyword" style="">if</span> (fromMsg) {  
  4.         mPendingBroadcastTimeoutMessage = <span class="hljs-keyword" style="">false</span>;  
  5.     }  
  6.     <span class="hljs-keyword" style="">if</span> (mOrderedBroadcasts.size() == <span class="hljs-number" style="">0</span>) {  
  7.         <span class="hljs-keyword" style="">return</span>;  
  8.     }  
  9.     <span class="hljs-keyword" style="">long</span> now = SystemClock.uptimeMillis();  
  10.     BroadcastRecord r = mOrderedBroadcasts.get(<span class="hljs-number" style="">0</span>);  
  11.     . . . . . .  
  12.     . . . . . .  
  13.     finishReceiverLocked(r, r.resultCode, r.resultData,  
  14.             r.resultExtras, r.resultAbort, <span class="hljs-keyword" style="">true</span>);  
  15.     scheduleBroadcastsLocked();  
  16.     . . . . . .  
  17. }</code>  

可以看到,當一個receiver超時後,系統會放棄繼續處理它,並再次調用scheduleBroadcastsLocked(),嘗試處理下一個receiver。

 

4 尾聲

        有關Android的廣播機制,我們就先說這麼多吧。品一杯紅茶,說一段代碼,管他雲山霧罩,任那瑣碎冗長,我自冷眼看安卓,瞧他修短隨化。


http://my.oschina.net/youranhongcha

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章