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