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對象完成:
- <code class="hljs less" style=""><span class="hljs-variable" style="">@Override</span>
- public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
- {
- return registerReceiver(receiver, filter, null, null);
- }
- <span class="hljs-variable" style="">@Override</span>
- public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
- String broadcastPermission, Handler scheduler)
- {
- return registerReceiverInternal(receiver, filter, broadcastPermission,
- scheduler, getOuterContext());
- }</code>
註冊之時,用戶會把一個自定義的receiver對象作爲第一個參數傳入。當然,用戶的receiver都是繼承於BroadcastReceiver的。使用過廣播機制的程序員,對這個BroadcastReceiver應該都不陌生,這裏就不多說了。我們需要關心的是,這個registerReceiverInternal()內部還包含了什麼重要的細節。
registerReceiverInternal()代碼的截選如下:
- <code class="hljs kotlin" style=""><span class="hljs-keyword" style="">private</span> Intent registerReceiverInternal(BroadcastReceiver receiver,
- IntentFilter filter, String broadcastPermission,
- Handler scheduler, Context context)
- {
- IIntentReceiver rd = <span class="hljs-literal" style="">null</span>;
- <span class="hljs-keyword" style="">if</span> (receiver != <span class="hljs-literal" style="">null</span>)
- {
- <span class="hljs-keyword" style="">if</span> (mPackageInfo != <span class="hljs-literal" style="">null</span> && context != <span class="hljs-literal" style="">null</span>)
- {
- <span class="hljs-keyword" style="">if</span> (scheduler == <span class="hljs-literal" style="">null</span>)
- {
- scheduler = mMainThread.getHandler();
- }
- <span class="hljs-comment" style="">// 查找和context對應的“子哈希表”裏的ReceiverDispatcher,如果找不到,就重新new一個</span>
- rd = mPackageInfo.getReceiverDispatcher(receiver, context, scheduler,
- mMainThread.getInstrumentation(), <span class="hljs-literal" style="">true</span>);
- }
- . . . . . .
- }
- <span class="hljs-keyword" style="">try</span>
- {
- <span class="hljs-keyword" style="">return</span> ActivityManagerNative.getDefault().registerReceiver(
- mMainThread.getApplicationThread(), mBasePackageName,
- rd, filter, broadcastPermission);
- }
- <span class="hljs-keyword" style="">catch</span> (RemoteException e)
- {
- <span class="hljs-keyword" style="">return</span> <span class="hljs-literal" style="">null</span>;
- }
- }</code>
請大家注意那個rd對象(IIntentReceiver rd)。我們知道,在Android架構中,廣播動作最終其實都是由AMS遞送出來的。AMS利用binder機制,將語義傳遞給各個應用進程,應用進程再輾轉調用到receiver的onReceive(),完成這次廣播。而此處的rd對象正是承擔“語義傳遞工作“的binder實體。
爲了管理這個重要的binder實體,Android搞出了一個叫做ReceiveDispatcher的類。該類的定義截選如下:
【frameworks/base/core/Java/android/app/LoadedApk.java】
- <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="">
- </span></span>{
- <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>{
- . . . . . .
- . . . . . .
- }
- <span class="hljs-keyword" style="">final</span> IIntentReceiver.Stub mIIntentReceiver; <span class="hljs-comment" style="">// 請注意這個域!它就是傳到外面的rd。</span>
- <span class="hljs-keyword" style="">final</span> BroadcastReceiver mReceiver;
- <span class="hljs-keyword" style="">final</span> Context mContext;
- <span class="hljs-keyword" style="">final</span> Handler mActivityThread;
- <span class="hljs-keyword" style="">final</span> Instrumentation mInstrumentation;
- <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">boolean</span> mRegistered;
- <span class="hljs-keyword" style="">final</span> IntentReceiverLeaked mLocation;
- RuntimeException mUnregisterLocation;
- <span class="hljs-keyword" style="">boolean</span> mForgotten;
- . . . . . .</code>
這樣看來,“動態註冊的BroadcastReceiver”和“ReceiverDispatcher節點”具有一一對應的關係。示意圖如下:
一個應用裏可能會註冊多個動態receiver,所以這種一一對應關係最好整理成表,這個表就位於LoadedApk中。前文mPackageInfo.getReceiverDispatcher()一句中的mPackageInfo就是LoadedApk對象。
在Android的架構裏,應用進程裏是用LoadedApk來對應一個apk的,進程里加載了多少個apk,就會有多少LoadedApk。每個LoadedApk裏會有一張“關於本apk動態註冊的所有receiver”的哈希表(mReceivers)。當然,在LoadedApk初創之時,這張表只是個空表。
mReceivers表的定義如下:
- <code class="hljs ruby" style="">private final
- HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher<span class="hljs-meta" style="">>> </span>mReceivers
- = 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(),它最終調用到
- <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>(
- <span class="hljs-selector-tag" style="">mMainThread</span><span class="hljs-selector-class" style="">.getApplicationThread</span>(), <span class="hljs-selector-tag" style="">mBasePackageName</span>,
- <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端,我們可以這樣訪問相應的彙總表:
- <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的定義截選如下:
- <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="">>
- </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="">
- </span></span>{
- <span class="hljs-keyword" style="">final</span> ActivityManagerService owner;
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> IIntentReceiver receiver;
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> ProcessRecord app;
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> pid;
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> uid;
- BroadcastRecord curBroadcast = <span class="hljs-keyword" style="">null</span>;
- <span class="hljs-keyword" style="">boolean</span> linkedToDeath = <span class="hljs-keyword" style="">false</span>;
- String stringName;
- . . . . . .</code>
ReceiverList繼承於ArrayList<BroadcastFilter>,而BroadcastFilter又繼承於IntentFilter,所以ReceiverList可以被理解爲一個IntentFilter數組列表。
- <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>{
- <span class="hljs-keyword" style="">final</span> ReceiverList receiverList;
- <span class="hljs-keyword" style="">final</span> String packageName;
- <span class="hljs-keyword" style="">final</span> String requiredPermission;
- . . . . . .</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>列表。
我們舉個實際的例子:
- <code class="hljs php" style="">Intent intent = <span class="hljs-keyword" style="">new</span> Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
- <span class="hljs-keyword" style="">List</span><ResolveInfo> ris = <span class="hljs-keyword" style="">null</span>;<span class="hljs-keyword" style="">try</span> {
- 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>);
- } <span class="hljs-keyword" style="">catch</span> (RemoteException e) {}</code>
這是AMS的systemReady()函數裏的一段代碼,意思是查找有多少receiver對ACTION_PRE_BOOT_COMPLETED感興趣。
ResolveInfo的定義截選如下:
- <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="">
- </span></span>{
- <span class="hljs-keyword" style="">public</span> ActivityInfo activityInfo;
- <span class="hljs-keyword" style="">public</span> ServiceInfo serviceInfo;
- <span class="hljs-keyword" style="">public</span> IntentFilter filter;
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">int</span> priority;
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">int</span> preferredOrder;
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">int</span> match;
- . . . . . .
- . . . . . .</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()的代碼片段如下:
- <code class="hljs actionscript" style="">mContext = getApplicationContext();
- Intent intent = <span class="hljs-keyword" style="">new</span> Intent();
- intent.setAction(<span class="hljs-string" style="">"com.android.xxxxx"</span>);
- intent.setFlags(<span class="hljs-number" style="">1</span>);
- mContext.sendBroadcast(intent);</code>
上面的mContext的內部其實是在調用一個ContextImpl對象的同名函數,所以我們繼續查看ContextImpl.java文件。
【frameworks/base/core/java/android/app/ContextImpl.java】
- <code class="hljs java" style=""><span class="hljs-meta" style="">@Override</span>
- <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="">
- </span></span>{
- String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
- <span class="hljs-keyword" style="">try</span>
- {
- intent.setAllowFds(<span class="hljs-keyword" style="">false</span>);
- ActivityManagerNative.getDefault().broadcastIntent(
- mMainThread.getApplicationThread(), intent, resolvedType, <span class="hljs-keyword" style="">null</span>,
- 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>,
- Binder.getOrigCallingUser());
- } <span class="hljs-keyword" style="">catch</span> (RemoteException e) {
- }
- }</code>
簡單地調用broadcastIntent()向AMS發出請求了。
3.1 AMS一側的broadcastIntentLocked()
用戶進程把發送廣播的語義傳遞到AMS之後,最終會由AMS的broadcastIntentLocked()處理。其原型如下:
- <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,
- String callerPackage,
- Intent intent, String resolvedType,
- 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,
- String resultData,
- Bundle map, String requiredPermission,
- </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 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,
- </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標記
對應的代碼爲:
- <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是否要激活“處於停止狀態的”應用。
- <code class="hljs gradle" style=""><span class="hljs-comment" style="">/**
- * If set, this intent will not match any components in packages that
- * are currently stopped. If this is not set, then the default behavior
- * is to include such applications in the result.
- */</span>
- <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;
- <span class="hljs-comment" style="">/**
- * If set, this intent will always match any components in packages that
- * are currently stopped. This is the default behavior when
- * {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set. If both of these
- * flags are set, this one wins (it allows overriding of exclude for
- * places where the framework may automatically set the exclude flag).
- */</span>
- <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()會判斷當前是否有權力發出廣播,代碼截選如下:
- <code class="hljs lisp" style="">/*
- * Prevent non-system code (<span class="hljs-name" style="">defined</span> here to be non-persistent
- * processes) from sending protected broadcasts.
- */
- if (<span class="hljs-name" style="">callingUid</span> == Process.SYSTEM_UID || callingUid == Process.PHONE_UID
- || callingUid == Process.SHELL_UID || callingUid == <span class="hljs-number" style="">0</span>)
- {
- // Always okay.
- }
- else if (<span class="hljs-name" style="">callerApp</span> == null || !callerApp.persistent)
- {
- try
- {
- if (<span class="hljs-name" style="">AppGlobals</span>.getPackageManager().isProtectedBroadcast(<span class="hljs-name" style="">intent</span>.getAction()))
- {
- String msg = <span class="hljs-string" style="">"Permission Denial: not allowed to send broadcast "</span>
- + intent.getAction() + <span class="hljs-string" style="">" from pid="</span>
- + callingPid + <span class="hljs-string" style="">", uid="</span> + callingUid<span class="hljs-comment" style="">;</span>
- Slog.w(<span class="hljs-name" style="">TAG</span>, msg)<span class="hljs-comment" style="">;</span>
- throw new SecurityException(<span class="hljs-name" style="">msg</span>)<span class="hljs-comment" style="">;</span>
- }
- }
- catch (<span class="hljs-name" style="">RemoteException</span> e)
- {
- Slog.w(<span class="hljs-name" style="">TAG</span>, <span class="hljs-string" style="">"Remote exception"</span>, e)<span class="hljs-comment" style="">;</span>
- return ActivityManager.BROADCAST_SUCCESS<span class="hljs-comment" style="">;</span>
- }
- }</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,其大致代碼如下:
- <code class="hljs cpp" style=""> ArrayList<Intent> <span class="hljs-built_in" style="">list</span> = mStickyBroadcasts.get(intent.getAction());
- <span class="hljs-keyword" style="">if</span> (<span class="hljs-built_in" style="">list</span> == null)
- {
- <span class="hljs-built_in" style="">list</span> = <span class="hljs-keyword" style="">new</span> ArrayList<Intent>();
- mStickyBroadcasts.put(intent.getAction(), <span class="hljs-built_in" style="">list</span>);
- }
- <span class="hljs-keyword" style="">int</span> N = <span class="hljs-built_in" style="">list</span>.size();
- <span class="hljs-keyword" style="">int</span> i;
- <span class="hljs-keyword" style="">for</span> (i=<span class="hljs-number" style="">0</span>; i<N; i++)
- {
- <span class="hljs-keyword" style="">if</span> (intent.filterEquals(<span class="hljs-built_in" style="">list</span>.get(i)))
- {
- <span class="hljs-comment" style="">// This sticky already exists, replace it.</span>
- <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));
- <span class="hljs-keyword" style="">break</span>;
- }
- }
- <span class="hljs-keyword" style="">if</span> (i >= N)
- {
- <span class="hljs-built_in" style="">list</span>.add(<span class="hljs-keyword" style="">new</span> Intent(intent));
- }</code>
mStickyBroadcasts的定義是這樣的:
- <code class="hljs javascript" style=""> final HashMap<<span class="hljs-built_in" style="">String</span>, ArrayList<Intent>> mStickyBroadcasts =
- <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()。相關代碼截選如下:
- <code class="hljs cpp" style=""><span class="hljs-keyword" style="">int</span> NR = registeredReceivers != null ? registeredReceivers.size() : <span class="hljs-number" style="">0</span>;
- <span class="hljs-keyword" style="">if</span> (!ordered && NR > <span class="hljs-number" style="">0</span>)
- {
- <span class="hljs-comment" style="">// If we are not serializing this broadcast, then send the</span>
- <span class="hljs-comment" style="">// registered receivers separately so they don't wait for the</span>
- <span class="hljs-comment" style="">// components to be launched.</span>
- final BroadcastQueue <span class="hljs-built_in" style="">queue</span> = broadcastQueueForIntent(intent);
- BroadcastRecord r = <span class="hljs-keyword" style="">new</span> BroadcastRecord(<span class="hljs-built_in" style="">queue</span>, intent, callerApp,
- callerPackage, callingPid, callingUid, requiredPermission,
- registeredReceivers, resultTo, resultCode, resultData, <span class="hljs-built_in" style="">map</span>,
- ordered, sticky, <span class="hljs-literal" style="">false</span>);
- <span class="hljs-keyword" style="">if</span> (DEBUG_BROADCAST) Slog.v(
- TAG, <span class="hljs-string" style="">"Enqueueing parallel broadcast "</span> + r);
- final boolean replaced = replacePending && <span class="hljs-built_in" style="">queue</span>.replaceParallelBroadcastLocked(r);
- <span class="hljs-keyword" style="">if</span> (!replaced) {
- <span class="hljs-built_in" style="">queue</span>.enqueueParallelBroadcastLocked(r);
- <span class="hljs-built_in" style="">queue</span>.scheduleBroadcastsLocked(); <span class="hljs-comment" style="">// 注意這句。。。</span>
- }
- registeredReceivers = null;
- NR = <span class="hljs-number" style="">0</span>;
- }</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】
- <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="">
- </span></span>{
- . . . . . .
- <span class="hljs-keyword" style="">if</span> (mBroadcastsScheduled)
- {
- <span class="hljs-keyword" style="">return</span>;
- }
- mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, <span class="hljs-keyword" style="">this</span>));
- mBroadcastsScheduled = <span class="hljs-literal" style="">true</span>;
- }</code>
發出BROADCAST_INTENT_MSG消息。
上面用到的mHandler是這樣創建的:
- <code class="hljs groovy" style=""><span class="hljs-keyword" style="">final</span> Handler mHandler = <span class="hljs-keyword" style="">new</span> Handler()
- {
- <span class="hljs-keyword" style="">public</span> <span class="hljs-keyword" style="">void</span> handleMessage(Message msg)
- {
- <span class="hljs-keyword" style="">switch</span> (msg.what)
- {
- <span class="hljs-keyword" style="">case</span> <span class="hljs-string" style="">BROADCAST_INTENT_MSG:</span>
- {
- <span class="hljs-keyword" style="">if</span> (DEBUG_BROADCAST)
- Slog.v(TAG, <span class="hljs-string" style="">"Received BROADCAST_INTENT_MSG"</span>);
- processNextBroadcast(<span class="hljs-literal" style="">true</span>);
- }
- <span class="hljs-keyword" style="">break</span>;
- <span class="hljs-keyword" style="">case</span> <span class="hljs-string" style="">BROADCAST_TIMEOUT_MSG:</span>
- {
- <span class="hljs-keyword" style="">synchronized</span> (mService)
- {
- broadcastTimeoutLocked(<span class="hljs-literal" style="">true</span>);
- }
- }
- <span class="hljs-keyword" style="">break</span>;
- }
- }
- };</code>
也就是說,AMS端會在BroadcastQueue.java中的processNextBroadcast()具體處理廣播。
3.1.7 整理兩個receiver列表
我們前文已經說過,有些廣播是需要有序遞送的。爲了合理處理“有序遞送”和“平行遞送”,broadcastIntentLocked()函數內部搞出了兩個list:
- <code class="hljs php" style=""><span class="hljs-keyword" style="">List</span> receivers = <span class="hljs-keyword" style="">null</span>;
- <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變量,代碼如下:
- <code class="hljs ini" style=""><span class="hljs-attr" style="">receivers</span> = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, userId);</code>
而對於動態註冊的receiver信息,就不是從包管理器獲取了,這些信息本來就記錄在AMS之中,此時只需調用:
- <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節點:
- <code class="hljs cpp" style=""><span class="hljs-keyword" style="">if</span> ((receivers != null && receivers.size() > <span class="hljs-number" style="">0</span>)
- || resultTo != null)
- {
- BroadcastQueue <span class="hljs-built_in" style="">queue</span> = broadcastQueueForIntent(intent);
- BroadcastRecord r = <span class="hljs-keyword" style="">new</span> BroadcastRecord(<span class="hljs-built_in" style="">queue</span>, intent, callerApp,
- callerPackage, callingPid, callingUid, requiredPermission,
- receivers, resultTo, resultCode, resultData, <span class="hljs-built_in" style="">map</span>, ordered,
- sticky, <span class="hljs-literal" style="">false</span>);
- . . . . . .
- boolean replaced = replacePending && <span class="hljs-built_in" style="">queue</span>.replaceOrderedBroadcastLocked(r);
- <span class="hljs-keyword" style="">if</span> (!replaced) {
- <span class="hljs-built_in" style="">queue</span>.enqueueOrderedBroadcastLocked(r);
- <span class="hljs-built_in" style="">queue</span>.scheduleBroadcastsLocked();
- }
- }</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。我們直接將廣播遞送出去即可:
- <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>)
- {
- r = mParallelBroadcasts.remove(<span class="hljs-number" style="">0</span>);
- r.dispatchTime = SystemClock.uptimeMillis();
- r.dispatchClockTime = System.currentTimeMillis();
- <span class="hljs-keyword" style="">final</span> <span class="hljs-keyword" style="">int</span> N = r.receivers.<span class="hljs-keyword" style="">size</span>();
- . . . . . .
- <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++)
- {
- Object target = r.receivers.get(i);
- . . . . . .
- deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, <span class="hljs-keyword" style="">false</span>);
- }
- . . . . . .
- }</code>
3.2.1 用deliverToRegisteredReceiverLocked()遞送到平行動態receiver
deliverToRegisteredReceiverLocked()的代碼截選如下:
【frameworks/base/services/java/com/android/server/am/BroadcastQueue.java】
- <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,
- <span class="hljs-type" style="">BroadcastFilter</span> <span class="hljs-built_in" style="">filter</span>,
- boolean ordered)
- {
- . . . . . .
- . . . . . .
- <span class="hljs-keyword" style="">if</span> (!skip)
- {
- <span class="hljs-keyword" style="">if</span> (ordered)
- {
- r.receiver = <span class="hljs-built_in" style="">filter</span>.receiverList.receiver.asBinder();
- r.curFilter = <span class="hljs-built_in" style="">filter</span>;
- <span class="hljs-built_in" style="">filter</span>.receiverList.curBroadcast = r;
- r.state = <span class="hljs-type" style="">BroadcastRecord</span>.<span class="hljs-type" style="">CALL_IN_RECEIVE</span>;
- <span class="hljs-keyword" style="">if</span> (<span class="hljs-built_in" style="">filter</span>.receiverList.app != null)
- {
- r.curApp = <span class="hljs-built_in" style="">filter</span>.receiverList.app;
- <span class="hljs-built_in" style="">filter</span>.receiverList.app.curReceiver = r;
- mService.updateOomAdjLocked();
- }
- }
- . . . . . .
- performReceiveLocked(<span class="hljs-built_in" style="">filter</span>.receiverList.app,
- <span class="hljs-built_in" style="">filter</span>.receiverList.receiver,
- new <span class="hljs-type" style="">Intent</span>(r.intent), r.resultCode,
- r.resultData, r.resultExtras,
- r.ordered, r.initialSticky);
- <span class="hljs-keyword" style="">if</span> (ordered)
- {
- r.state = <span class="hljs-type" style="">BroadcastRecord</span>.<span class="hljs-type" style="">CALL_DONE_RECEIVE</span>;
- }
- . . . . . .
- }</code>
- <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,
- 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,
- </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
- </span></span>{
- <span class="hljs-comment" style="">// Send the intent to the receiver asynchronously using one-way binder calls.</span>
- <span class="hljs-keyword" style="">if</span> (app != <span class="hljs-keyword" style="">null</span> && app.thread != <span class="hljs-keyword" style="">null</span>)
- {
- <span class="hljs-comment" style="">// If we have an app thread, do the call through that so it is</span>
- <span class="hljs-comment" style="">// correctly ordered with other one-way calls.</span>
- app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
- data, extras, ordered, sticky);
- }
- <span class="hljs-keyword" style="">else</span>
- {
- receiver.performReceive(intent, resultCode, data, extras, ordered, sticky);
- }
- }</code>
終於通過app.thread向用戶進程傳遞語義了。注意scheduleRegisteredReceiver()的receiver參數,它對應的就是前文所說的ReceiverDispatcher的Binder實體——InnerReceiver了。
總之,當語義傳遞到用戶進程的ApplicationThread以後,走到:
- <code class="hljs java" style=""><span class="hljs-comment" style="">// This function exists to make sure all receiver dispatching is</span>
- <span class="hljs-comment" style="">// correctly ordered, since these are one-way calls and the binder driver</span>
- <span class="hljs-comment" style="">// applies transaction ordering per object for such calls.</span>
- <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,
- </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,
- </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
- </span></span>{
- receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky);
- }</code>
終於走到ReceiverDispatcher的InnerReceiver了:
- <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="">
- </span></span>{
- <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>{
- . . . . . .
- . . . . . .
- <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,
- String data, 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, </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>{
- LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
- . . . . . .
- <span class="hljs-keyword" style="">if</span> (rd != <span class="hljs-keyword" style="">null</span>) {
- rd.performReceive(intent, resultCode, data, extras,
- ordered, sticky);
- }
- . . . . . .
- }
- }
- . . . . . .
- <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,
- String data, 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, </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>{
- . . . . . .
- Args args = <span class="hljs-keyword" style="">new</span> Args(intent, resultCode, data, extras, ordered, sticky);
- <span class="hljs-keyword" style="">if</span> (!mActivityThread.post(args)) <span class="hljs-comment" style="">// 請注意這一句!</span>
- {
- <span class="hljs-keyword" style="">if</span> (mRegistered && ordered)
- {
- IActivityManager mgr = ActivityManagerNative.getDefault();
- <span class="hljs-keyword" style="">if</span> (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
- <span class="hljs-string" style="">"Finishing sync broadcast to "</span> + mReceiver);
- args.sendFinished(mgr);
- }
- }
- }
- }</code>
請注意mActivityThread.post(args)一句,這樣,事件泵最終會回調Args參數的run()成員函數:
- <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="">
- </span></span>{
- . . . . . .
- . . . . . .
- <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="">
- </span></span>{
- <span class="hljs-keyword" style="">final</span> BroadcastReceiver receiver = mReceiver;
- . . . . . .
- <span class="hljs-keyword" style="">try</span> {
- ClassLoader cl = mReceiver.getClass().getClassLoader();
- intent.setExtrasClassLoader(cl);
- setExtrasClassLoader(cl);
- receiver.setPendingResult(<span class="hljs-keyword" style="">this</span>);
- receiver.onReceive(mContext, intent); <span class="hljs-comment" style="">// 回調具體receiver的onReceive()</span>
- } <span class="hljs-keyword" style="">catch</span> (Exception e) {
- . . . . . .
- }
- <span class="hljs-keyword" style="">if</span> (receiver.getPendingResult() != <span class="hljs-keyword" style="">null</span>) {
- finish();
- }
- . . . . . .
- }
- }</code>
其中的那句receiver.onReceive(this),正是回調我們具體receiver的onReceive()成員函數的地方。噢,終於看到應用程序員熟悉的onReceive()了。這部分的示意圖如下:
3.2.2 靜態receiver的遞送
說完動態遞送,我們再來看靜態遞送。對於靜態receiver,情況會複雜很多,因爲靜態receiver所從屬的進程有可能還沒有運行起來呢。此時BroadcastRecord節點中記錄的子列表的節點是ResolveInfo對象。
- <code class="hljs tcl" style="">ResolveInfo <span class="hljs-keyword" style="">info</span> = (ResolveInfo)nextReceiver;
- . . . . . .
- r.state = BroadcastRecord.APP_RECEIVE;
- String targetProcess = <span class="hljs-keyword" style="">info</span>.activityInfo.processName;</code>
那麼我們要先找到receiver所從屬的進程的進程名。
processNextBroadcast()中啓動進程的代碼如下:
- <code class="hljs kotlin" style="">ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
- info.activityInfo.applicationInfo.uid);
- . . . . . .
- <span class="hljs-keyword" style="">if</span> (app != <span class="hljs-literal" style="">null</span> && app.thread != <span class="hljs-literal" style="">null</span>)
- {
- . . . . . .
- app.addPackage(info.activityInfo.packageName);
- processCurBroadcastLocked(r, app);
- <span class="hljs-keyword" style="">return</span>;
- . . . . . .
- }
- r.curApp = mService.startProcessLocked(targetProcess,
- info.activityInfo.applicationInfo, <span class="hljs-literal" style="">true</span>,
- r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
- <span class="hljs-string" style="">"broadcast"</span>, r.curComponent,
- (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != <span class="hljs-number" style="">0</span>,
- <span class="hljs-literal" style="">false</span>)
- . . . . . .
- mPendingBroadcast = r;
- mPendingBroadcastRecvIndex = recIdx;</code>
如果目標進程已經存在了,那麼app.thread肯定不爲null,直接調用processCurBroadcastLocked()即可,否則就需要啓動新進程了。啓動的過程是異步的,可能很耗時,所以要把BroadcastRecord節點記入mPendingBroadcast。
3.2.2.1 processCurBroadcastLocked()
我們先說processCurBroadcastLocked()。
- <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,
- 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
- </span></span>{
- . . . . . .
- r.receiver = app.thread.asBinder();
- r.curApp = app;
- app.curReceiver = r;
- . . . . . .
- . . . . . .
- app.thread.scheduleReceiver(<span class="hljs-keyword" style="">new</span> Intent(r.intent), r.curReceiver,
- mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
- r.resultCode, r.resultData, r.resultExtras, r.ordered);
- . . . . . .
- started = <span class="hljs-keyword" style="">true</span>;
- . . . . . .
- }</code>
其中最重要的是調用app.thread.scheduleReceiver()的那句。在IApplicationThread接口中,是這樣定義scheduleReceiver()函數原型的:
- <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,
- CompatibilityInfo compatInfo,
- </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, </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="">
- </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()
- <code class="hljs css" style=""><span class="hljs-selector-tag" style="">case</span> <span class="hljs-selector-tag" style="">RECEIVER</span>:
- <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>");
- <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>);
- <span class="hljs-selector-tag" style="">maybeSnapshot</span>();
- <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>);
- <span class="hljs-selector-tag" style="">break</span>;</code>
ActivityThread中,會運用反射機制,創建出BroadcastReceiver對象,而後回調該對象的onReceive()成員函數。
- <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>
- {
- . . . . . .
- <span class="hljs-type" style="">IActivityManager</span> mgr = <span class="hljs-type" style="">ActivityManagerNative</span>.getDefault();
- <span class="hljs-type" style="">BroadcastReceiver</span> receiver;
- try {
- java.lang.<span class="hljs-type" style="">ClassLoader</span> cl = packageInfo.getClassLoader();
- <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>
- <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>
- receiver = (<span class="hljs-type" style="">BroadcastReceiver</span>)cl.loadClass(component).newInstance();
- } catch (<span class="hljs-type" style="">Exception</span> e) {
- . . . . . .
- }
- try {
- . . . . . .
- 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>
- 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>
- } catch (<span class="hljs-type" style="">Exception</span> e) {
- . . . . . .
- } finally {
- sCurrentBroadcastIntent.set(null);
- }
- <span class="hljs-keyword" style="">if</span> (receiver.getPendingResult() != null) {
- <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>
- }
- }</code>
3.2.2.2 必要時啓動新進程
現在我們回過頭來看,在目標進程尚未啓動的情況下,是如何完成遞送的。剛剛我們已經看到調用startProcessLocked()的句子了,只要不出問題,目標進程成功啓動後就會調用AMS的attachApplication()。
有關attachApplication()的詳情,請參考其他關於AMS的文檔,此處我們只需知道它裏面又會調用attachApplicationLocked()函數。
- <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()內有這麼幾句:
- <code class="hljs php" style=""><span class="hljs-comment" style="">// Check if a next-broadcast receiver is in this process...</span>
- <span class="hljs-keyword" style="">if</span> (!badApp && isPendingBroadcastProcessLocked(pid)) {
- <span class="hljs-keyword" style="">try</span> {
- didSomething = sendPendingBroadcastsLocked(app);
- } <span class="hljs-keyword" style="">catch</span> (<span class="hljs-keyword" style="">Exception</span> e) {
- <span class="hljs-comment" style="">// If the app died trying to launch the receiver we declare it 'bad'</span>
- badApp = <span class="hljs-keyword" style="">true</span>;
- }
- }</code>
它們的意思是,如果新啓動的進程就是剛剛mPendingBroadcast所記錄的進程的話,此時AMS就會執行sendPendingBroadcastsLocked(app)一句。
- <code class="hljs cpp" style=""><span class="hljs-comment" style="">// The app just attached; send any pending broadcasts that it should receive</span>
- <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>{
- boolean didSomething = <span class="hljs-literal" style="">false</span>;
- <span class="hljs-keyword" style="">for</span> (BroadcastQueue <span class="hljs-built_in" style="">queue</span> : mBroadcastQueues) {
- didSomething |= <span class="hljs-built_in" style="">queue</span>.sendPendingBroadcastsLocked(app);
- }
- <span class="hljs-keyword" style="">return</span> didSomething;
- }</code>
BroadcastQueue的sendPendingBroadcastsLocked()函數如下:
- <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>{
- <span class="hljs-keyword" style="">boolean</span> didSomething = <span class="hljs-keyword" style="">false</span>;
- <span class="hljs-keyword" style="">final</span> BroadcastRecord br = mPendingBroadcast;
- <span class="hljs-keyword" style="">if</span> (br != <span class="hljs-keyword" style="">null</span> && br.curApp.pid == app.pid) {
- <span class="hljs-keyword" style="">try</span> {
- mPendingBroadcast = <span class="hljs-keyword" style="">null</span>;
- processCurBroadcastLocked(br, app);
- didSomething = <span class="hljs-keyword" style="">true</span>;
- } <span class="hljs-keyword" style="">catch</span> (Exception e) {
- . . . . . .
- }
- }
- <span class="hljs-keyword" style="">return</span> didSomething;
- }</code>
可以看到,既然目標進程已經成功啓動了,那麼mPendingBroadcast就可以賦值爲null了。接着,sendPendingBroadcastsLocked()會調用前文剛剛闡述的processCurBroadcastLocked(),其內再通過app.thread.scheduleReceiver(),將語義發送到用戶進程,完成真正的廣播遞送。這部分在上一小節已有闡述,這裏就不多說了。
3.2.3 說說有序廣播是如何循環起來的?
我們知道,平行廣播的循環很簡單,只是在一個while循環裏對每個動態receiver執行deliverToRegisteredReceiverLocked()即可。而對有序廣播來說,原則上每次processNextBroadcast()只會處理一個BroadcastRecord的一個receiver而已。當然,此時摘下的receiver既有可能是動態註冊的,也有可能是靜態的。
對於動態註冊的receiver,目標進程處理完廣播之後,會間接調用am.finishReceiver()向AMS發出反饋,關於這一步,其實在前面羅列ReceiverDispatcher的performReceive()時已經出現過了,我們再列一下:
- <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,
- String data, 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, </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>{
- . . . . . .
- Args args = <span class="hljs-keyword" style="">new</span> Args(intent, resultCode, data, extras, ordered, sticky);
- <span class="hljs-keyword" style="">if</span> (!mActivityThread.post(args))
- {
- <span class="hljs-keyword" style="">if</span> (mRegistered && ordered)
- {
- IActivityManager mgr = ActivityManagerNative.getDefault();
- . . . . . .
- args.sendFinished(mgr); <span class="hljs-comment" style="">// 請注意這一句!</span>
- }
- }
- }</code>
Args繼承於BroadcastReceiver.PendingResult,它調用的sendFinished()就是PendingResult的sendFinished():
- <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="">
- </span></span>{
- <span class="hljs-keyword" style="">synchronized</span> (<span class="hljs-keyword" style="">this</span>) {
- <span class="hljs-keyword" style="">if</span> (mFinished) {
- <span class="hljs-keyword" style="">throw</span> <span class="hljs-keyword" style="">new</span> IllegalStateException(<span class="hljs-string" style="">"Broadcast already finished"</span>);
- }
- mFinished = <span class="hljs-keyword" style="">true</span>;
- <span class="hljs-keyword" style="">try</span> {
- <span class="hljs-keyword" style="">if</span> (mResultExtras != <span class="hljs-keyword" style="">null</span>) {
- mResultExtras.setAllowFds(<span class="hljs-keyword" style="">false</span>);
- }
- <span class="hljs-keyword" style="">if</span> (mOrderedHint) {
- am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
- mAbortBroadcast);
- } <span class="hljs-keyword" style="">else</span> {
- <span class="hljs-comment" style="">// This broadcast was sent to a component; it is not ordered,</span>
- <span class="hljs-comment" style="">// but we still need to tell the activity manager we are done.</span>
- 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>);
- }
- } <span class="hljs-keyword" style="">catch</span> (RemoteException ex) {
- }
- }
- }</code>
代碼中的am.finishReceiver()會通知AMS,表示用戶側receiver已經處理好了,或者至少告一段落了,請AMS進行下一步動作。
而對於動態註冊的receiver,情況是類似的,最終也是調用am.finishReceiver()向AMS發出回饋的,只不過發起的動作是在ActivityThread的handleReceiver()動作中。前文已經列過這個函數了,大家注意下面的句子即可:
- <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>
- {
- . . . . . .
- 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>
- 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>
- . . . . . .
- <span class="hljs-keyword" style="">if</span> (receiver.getPendingResult() != null) {
- <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>
- }
- }</code>
ReceiverData也是繼承於BroadcastReceiver.PendingResult的,它調用的finish()是PendingResult的finish():
- <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="">
- </span></span>{
- <span class="hljs-keyword" style="">if</span> (mType == TYPE_COMPONENT) {
- . . . . . .
- } <span class="hljs-keyword" style="">else</span> <span class="hljs-keyword" style="">if</span> (mOrderedHint && mType != TYPE_UNREGISTERED) {
- <span class="hljs-keyword" style="">if</span> (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
- <span class="hljs-string" style="">"Finishing broadcast to "</span> + mToken);
- <span class="hljs-keyword" style="">final</span> IActivityManager mgr = ActivityManagerNative.getDefault();
- sendFinished(mgr);
- }
- }</code>
此處的sendFinished()內部最終也會調用到am.finishReceiver(),向AMS通告receiver已經處理好了。
AMS側在收到finishReceiver語義後,執行:
- <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,
- 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="">
- </span></span>{
- . . . . . .
- <span class="hljs-keyword" style="">try</span> {
- <span class="hljs-keyword" style="">boolean</span> doNext = <span class="hljs-keyword" style="">false</span>;
- BroadcastRecord r = <span class="hljs-keyword" style="">null</span>;
- <span class="hljs-keyword" style="">synchronized</span>(<span class="hljs-keyword" style="">this</span>) {
- r = broadcastRecordForReceiverLocked(who);
- <span class="hljs-keyword" style="">if</span> (r != <span class="hljs-keyword" style="">null</span>) {
- doNext = r.queue.finishReceiverLocked(r, resultCode,
- resultData, resultExtras, resultAbort, <span class="hljs-keyword" style="">true</span>);
- }
- }
- <span class="hljs-keyword" style="">if</span> (doNext) {
- r.queue.processNextBroadcast(<span class="hljs-keyword" style="">false</span>);
- }
- trimApplications();
- } <span class="hljs-keyword" style="">finally</span> {
- Binder.restoreCallingIdentity(origId);
- }
- }</code>
可以看到,如有必要,會繼續調用processNextBroadcast(),從而完成有序廣播的循環處理。
3.2.4 說說有序廣播的timeout處理
因爲AMS很難知道一次廣播究竟能不能完全成功遞送出去,所以它必須實現一種“時限機制”。前文在闡述broadcastIntentLocked()時,提到過new一個BroadcastRecord節點,並插入一個BroadcastQueue裏的“平行列表”或者“有序列表”。不過當時我們沒有太細說那個BroadcastQueue,現在我們多加一點兒說明。
實際上系統中有兩個BroadcastQueue,一個叫做“前臺廣播隊列”,另一個叫“後臺廣播隊列”,在AMS裏是這樣定義的:
- <code class="hljs nginx" style=""><span class="hljs-attribute" style="">BroadcastQueue</span> mFgBroadcastQueue;
- <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信息:
- <code class="hljs cs" style=""><span class="hljs-keyword" style="">int</span> recIdx = r.nextReceiver++;
- . . . . . .
- Object nextReceiver = r.receivers.<span class="hljs-keyword" style="">get</span>(recIdx);</code>
當然,一開始,nextReceiver的值只會是0,表示第一個receiver有待處理,此時會給BroadcastRecord的dispatchTime域賦值。
- <code class="hljs cpp" style=""><span class="hljs-keyword" style="">int</span> recIdx = r.nextReceiver++;
- r.receiverTime = SystemClock.uptimeMillis();<span class="hljs-keyword" style="">if</span> (recIdx == <span class="hljs-number" style="">0</span>) {
- r.dispatchTime = r.receiverTime;
- r.dispatchClockTime = System.currentTimeMillis();
- . . . . . .
- }</code>
也就是說,dispatchTime的意義是標記實際處理BroadcastRecord的起始時間,那麼這個BroadcastRecord所能允許的最大時限值就是:
dispatchTime + 2 * mTimeoutPeriod * 其receiver總數
一旦超過這個時限,而BroadcastRecord又沒有處理完,那麼就強制結束這個BroadcastRecord節點:
- <code class="hljs actionscript" style=""><span class="hljs-keyword" style="">if</span> ((numReceivers > <span class="hljs-number" style="">0</span>) &&
- (now > r.dispatchTime + (<span class="hljs-number" style="">2</span>*mTimeoutPeriod*numReceivers)))
- {
- . . . . . .
- broadcastTimeoutLocked(<span class="hljs-literal" style="">false</span>); <span class="hljs-comment" style="">// forcibly finish this broadcast</span>
- forceReceive = <span class="hljs-literal" style="">true</span>;
- r.state = BroadcastRecord.IDLE;
- }</code>
此處調用的broadcastTimeoutLocked()的參數是boolean fromMsg,表示這個函數是否是在處理“時限消息”的地方調用的,因爲當前是在processNextBroadcast()函數裏調用broadcastTimeoutLocked()的,所以這個參數爲false。從這個參數也可以看出,另一處判斷“處理已經超時”的地方是在消息處理機制裏,在那個地方,fromMsg參數應該設爲true。
大體上說,每當processNextBroadcast()準備遞送receiver時,會調用setBroadcastTimeoutLocked()設置一個延遲消息:
- <code class="hljs bash" style="">long timeoutTime = r.receiverTime + mTimeoutPeriod;
- . . . . . .
- <span class="hljs-built_in" style="">set</span>BroadcastTimeoutLocked(timeoutTime);</code>
setBroadcastTimeoutLocked()的代碼如下:
- <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="">
- </span></span>{ <span class="hljs-keyword" style="">if</span> (! mPendingBroadcastTimeoutMessage)
- {
- Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, <span class="hljs-keyword" style="">this</span>);
- mHandler.sendMessageAtTime(msg, timeoutTime);
- mPendingBroadcastTimeoutMessage = <span class="hljs-keyword" style="">true</span>;
- }
- }</code>
只要我們的receiver能及時處理廣播,系統就會cancel上面的延遲消息。這也就是說,但凡事件泵的handleMessage()開始處理這個消息,就說明receiver處理超時了。此時,系統會放棄處理這個receiver,並接着嘗試處理下一個receiver。
- <code class="hljs java" style=""><span class="hljs-keyword" style="">final</span> Handler mHandler = <span class="hljs-keyword" style="">new</span> Handler()
- {
- <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>{
- <span class="hljs-keyword" style="">switch</span> (msg.what)
- {
- . . . . . .
- <span class="hljs-keyword" style="">case</span> BROADCAST_TIMEOUT_MSG:
- {
- <span class="hljs-keyword" style="">synchronized</span> (mService)
- {
- broadcastTimeoutLocked(<span class="hljs-keyword" style="">true</span>);
- }
- } <span class="hljs-keyword" style="">break</span>;
- }
- }
- };</code>
broadcastTimeoutLocked()的代碼截選如下:
- <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="">
- </span></span>{
- <span class="hljs-keyword" style="">if</span> (fromMsg) {
- mPendingBroadcastTimeoutMessage = <span class="hljs-keyword" style="">false</span>;
- }
- <span class="hljs-keyword" style="">if</span> (mOrderedBroadcasts.size() == <span class="hljs-number" style="">0</span>) {
- <span class="hljs-keyword" style="">return</span>;
- }
- <span class="hljs-keyword" style="">long</span> now = SystemClock.uptimeMillis();
- BroadcastRecord r = mOrderedBroadcasts.get(<span class="hljs-number" style="">0</span>);
- . . . . . .
- . . . . . .
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, <span class="hljs-keyword" style="">true</span>);
- scheduleBroadcastsLocked();
- . . . . . .
- }</code>
可以看到,當一個receiver超時後,系統會放棄繼續處理它,並再次調用scheduleBroadcastsLocked(),嘗試處理下一個receiver。
4 尾聲
有關Android的廣播機制,我們就先說這麼多吧。品一杯紅茶,說一段代碼,管他雲山霧罩,任那瑣碎冗長,我自冷眼看安卓,瞧他修短隨化。