Broadcast廣播內部機制

原文地址 http://my.oschina.net/youranhongcha/blog/226274?fromerr=kZfhK8dQ

廣播機制異常複雜,跟裹腳布一樣,下面這篇文章較爲詳細,特此轉載記錄

1 概述

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

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

 

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

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

 

2 兩種receiver

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

2.1 動態receiver

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

?
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 
{    
    return registerReceiver(receiver, filter, nullnull);
}
@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()代碼的截選如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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】

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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表的定義如下:

?
1
2
3
private final 
HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mReceivers
    new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();

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

圖:客戶進程中的mReceivers表

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

?
1
2
3
ActivityManagerNative.getDefault().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission);

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

?
1
ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());

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

        ReceiverList的定義截選如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
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數組列表。

?
1
2
3
4
5
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>列表。

        我們舉個實際的例子:

?
1
2
3
4
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
List<ResolveInfo> ris = null;try {
    ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null00);
catch (RemoteException e) {}

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

         ResolveInfo的定義截選如下:

?
1
2
3
4
5
6
7
8
9
10
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()的代碼片段如下:

?
1
2
3
4
5
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】

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
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, nullnullnullfalsefalse,
            Binder.getOrigCallingUser());
    catch (RemoteException e) {
    }
}

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

 

3.1 AMS一側的broadcastIntentLocked()

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

?
1
2
3
4
5
6
7
8
9
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標記

        對應的代碼爲:

?
1
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是否要激活“處於停止狀態的”應用。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 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()會判斷當前是否有權力發出廣播,代碼截選如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
 * 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,其大致代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    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的定義是這樣的:

?
1
2
    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()。相關代碼截選如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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】

?
1
2
3
4
5
6
7
8
9
10
public void scheduleBroadcastsLocked() 
{
    . . . . . .
    if (mBroadcastsScheduled) 
    {
        return;
    }
    mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
    mBroadcastsScheduled = true;
}

發出BROADCAST_INTENT_MSG消息。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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:

?
1
2
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;

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

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

?
1
receivers = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, userId);

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

?
1
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節點:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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。我們直接將廣播遞送出去即可:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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】

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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;
            }
         
        . . . . . .
}

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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以後,走到:

?
1
2
3
4
5
6
7
8
9
// 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了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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()成員函數:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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對象。

?
1
2
3
4
ResolveInfo info = (ResolveInfo)nextReceiver;
. . . . . .
r.state = BroadcastRecord.APP_RECEIVE;
String targetProcess = info.activityInfo.processName;

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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()。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()函數原型的:

?
1
2
3
4
5
void scheduleReceiver(Intent intent, ActivityInfo info, 
                      CompatibilityInfo compatInfo,                      
                      int resultCode, String data, 
                      Bundle extras, boolean sync) 
                      throws RemoteException;

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

        用戶進程裏handleMessage()

?
1
2
3
4
5
6
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()成員函數。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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()函數。

?
1
private final boolean attachApplicationLocked(IApplicationThread thread, int pid)

attachApplicationLocked()內有這麼幾句:

?
1
2
3
4
5
6
7
8
9
// 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)一句。

 

?
1
2
3
4
5
6
7
8
// 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()函數如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()時已經出現過了,我們再列一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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():

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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, 0nullnullfalse);
            }
        catch (RemoteException ex) {
        }
    }
}

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

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

?
1
2
3
4
5
6
7
8
9
10
11
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():

?
1
2
3
4
5
6
7
8
9
10
11
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語義後,執行:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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裏是這樣定義的:

?
1
2
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信息:

?
1
2
3
int recIdx = r.nextReceiver++;
. . . . . .
Object nextReceiver = r.receivers.get(recIdx);

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

?
1
2
3
4
5
6
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節點:

?
1
2
3
4
5
6
7
8
if ((numReceivers > 0) &&
        (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) 
{
    . . . . . .
    broadcastTimeoutLocked(false); // forcibly finish this broadcast
    forceReceive = true;
    r.state = BroadcastRecord.IDLE;
}

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

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

?
1
2
3
long timeoutTime = r.receiverTime + mTimeoutPeriod;
. . . . . .
setBroadcastTimeoutLocked(timeoutTime);

setBroadcastTimeoutLocked()的代碼如下:

?
1
2
3
4
5
6
7
8
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。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final Handler mHandler = new Handler() 
{
    public void handleMessage(Message msg) {
        switch (msg.what) 
        {
            . . . . . .
            case BROADCAST_TIMEOUT_MSG: 
            {
                synchronized (mService) 
                {
                    broadcastTimeoutLocked(true);
                }
            break;
        }
    }
};

broadcastTimeoutLocked()的代碼截選如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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。


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