天天看點

android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程

    本文為原創文章,歡迎轉載!轉載時請注明出處:http://blog.csdn.net/windskier

    這篇文章開始從framework的角度來研究一下android四大控件最後一個控件BroadcastReceiver 的機制與原理,BroadcastReceiver 字面意思就是廣播接收器,它能接收來自于系統的以及其他appliaction的廣播消息,是android中非常重要的一個component。 

1. BroadcastReceiver注冊

    BroadcastReceiver的注冊有兩種方式,一種是通過Context.registerReceiver()方法來進行動态注冊,或者通過在AndroidManifest.xml中進行靜态注冊。AMS對兩種不同注冊方式的Receiver有不同的管理方式。靜态注冊就不用說了,靜态注冊的receiver的管理是有PMS負責的,而AMS則需要對動态注冊的receiver注冊和管理。下面就研究一下AMS對receiver的動态管理過程。

1.1 ReceiverDispatcher

    同Service一樣,LoadedApk為receiver提供了一個ReceiverDispatcher類型,類似于Service管理時的ServiceDispatcher類型,對于每個要注冊的receiver,LoadedApk會為其建立一個ReceiverDispatcher。從其名稱來看,ReceiverDispatcher就是向receiver dispatch廣播資訊,下面簡單介紹一下這個類型存在的價值,廣播資訊是經由AMS進行分發管理的,是以需要為AMS向receive分發廣播消息設計一個接口,通過這個接口可以實作receiver與AMS的IPC通信,BroadcastReceiver 本身并不是一個IBinder接口,是以需要單獨設計一個,這個接口就是InnerReceiver,InnerReceiver一方面與AMS進行IPC通信,另一方面還需要與receiver進行互動,為了區分IPC的接口方法和與receiver的互動接口,這裡需要定義了另外一個類型ReceiverDispatcher,這就是ReceiverDispatcher産生的原因。 @ContextImpl.java

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();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
        try {
            return ActivityManagerNative.getDefault().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission);
        } catch (RemoteException e) {
            return null;
        }
    }
           

1.2 InnerReceiver

    上面一節已經介紹了InnerReceiver存在的意義,它其實是一個IInterface,用于AMS和Receiver之間的IPC通信,InnerReceiver實作了接口IIntentReceiver。 @IIntentReceiver.aidl

oneway interface IIntentReceiver {
    void performReceive(in Intent intent, int resultCode,
            String data, in Bundle extras, boolean ordered, boolean sticky);
}
           

1.3 sticky broadcast

    有這麼一種broadcast,在發送并經過AMS分發給對應的receiver後,這個broadcast并不會被丢棄,而是儲存在AMS中,當有新的需要動态注冊的receiver請求AMS注冊時,如果這個receiver能夠接收這個broadcast,那麼AMS會将在receiver注冊成功之後,馬上向receiver發送這個broadcast。這種broadcast我們稱之為stickybroadcast。     當receiver注冊成功後,AMS會将合适的sticky broadcast作為方法registerReceiver()傳回給Component,以便通知Component目前是否有sticky broadcast存在。

public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
           

    如果有多個符合receiver條件的 sticky  broadcast的話,那麼AMS将會隻傳回一個時間較早的sticky broadcast,并且會将這些所有滿足要求的sticky broadcast均發送給reciever,交由receiver去處理。sticky broadcast的發送于取消通過方法sendStickyBroadcast()/sendStickyOrderedBroadcast() 以及removeStickyBroadcast()來控制。 registerReceiver()@ActivityManagerService.java

// Enqueue broadcasts for all existing stickies that match
            // this filter.
            if (allSticky != null) {
                ArrayList receivers = new ArrayList();
                receivers.add(bf);

                int N = allSticky.size();
                for (int i=0; i<N; i++) {
                    Intent intent = (Intent)allSticky.get(i);
                    BroadcastRecord r = new BroadcastRecord(intent, null,
                            null, -1, -1, null, receivers, null, 0, null, null,
                            false, true, true);
                    if (mParallelBroadcasts.size() == 0) {
                        scheduleBroadcastsLocked();
                    }
                    mParallelBroadcasts.add(r);
                }
            }
           

1.4 動态注冊receiver的注冊管理

    receiver 的動态注冊過程就需要掌握以上幾個知識點就可以了,真正的注冊名其曰注冊,其實質就是将其交由 AMS 去管理,是以下面我們來分析研究一下 AMS 是通過哪些資料結構來管理 receiver 的。掌握了這些資料結構的含義以及它們之間的互相關系之後,我們就明白了整個需注冊 receiver 的注冊過程了。     

android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程
android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程

1.4.1 BroadcastFilter  

     對于每個注冊過程,由兩部分組成,一部分是是BroadcastReceiver,另一部分是IntentFilter,BroadcastReceiver通過IntentFilter來指定要接收的Intent BroadCast。而BroadcastFilter則是繼承自IntentFilter,它記錄了目前需注冊的Receiver的IntentFilter資訊。是以每一組BroadcastReceiver和IntentFilter對應一次注冊過程,每個注冊過程即是一個BroadcastReceiver向AMS訓示一個Intent Broadcast的需求,如果有多個Intent Broadcast需求,BroadcastReceiver需要向AMS進行多次注冊。

1.4.2 ReceiverList

    ReceiverList繼承自Arraylist,并且該Arraylist是以BroadcastFilter為元素,同時我們知道BroadcastReceiver的注冊過程的二要素是BroadcastReceiver和IntentFilter,對于同一個BroadcastReceiver可能會接收多個Intent Broadcast,是以每個IIntentReceiver可能會對應多個BroadcastFilter,這也是設計ReceiverList這個Arraylist的主要目的。

1.4.3 mReceiverResolver

     mReceiverResolver的定義如下:

final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
            = new IntentResolver<BroadcastFilter, BroadcastFilter>()
           

    mReceiverResolver是一個IntentResolver類型的對象,通過它AMS可以将BroadcastFilter按照不同的元素,如action、data、category,進行管理,這樣的話在AMS收到broadcast之後,會根據這個broadcast的Intent的元素去query mReceiverResolver,擷取對應的BroadcastFilter。

    既然mReceiverResolver實作了對BroadcastFilter的管理,那麼前面的ReceiverList類型存在的意義是什麼呢? mReceiverResolver和ReceiverList的關系其實并不沖突,AMS可以通過mReceiverResolver來獲得可以接收發來的Broadcast的所有的BroadcastFilter。通過上面的類型關系圖,我們可以看出BroadcastFilter中反向連結到自己所處的ReceiverList,看見兩者之間的關系并不是對立的,而是适用于AMS接收分發過程的不同階段。

1.4.4 BroadcastRecord

    就向前面介紹的android中的其他幾大component一樣,BroadReceiver在被處理時同樣是通過一個BroadcastRecord的Record類型來進行管理的,BroadcastRecord隻有在AMS在處理分發Broadcast時才去建立,它記錄着每個Broadcast的資訊,在Receiver的過程并不涉及Broadcast。

2. Broadcast的發送過程

    上面第一節介紹了Receiver的動态注冊過程,那麼接下來我們分析一下Broadcast的發送處理。

2.1 Broadcast的發送方式  

    Broadcast根據不同的需求有多種不同的發送方式,下面來分析一下具體有幾種發送方式。下面的表格列出了application幾種不同的發送方式以及不同方式對應的方法。

不同的發送方式 對應的方法
普通模式 sendBroadcast(Intent intent)
Broadcast sender 指定了receiver的permission,隻有符合這個permission的receiver才能接收這個Broadcast sendBroadcast(Intent intent, String receiverPermission)
要求符合要求的所有receiver (包括動态注冊和靜态注冊的receiver)按照優先級順序來接收這個Broadcast sendOrderedBroadcast(Intent intent, String receiverPermission)
某些情況下,Broadcast sender在發出Broadcast之後,向AMS指定一個resultReceiver,期望所有的receiver接收并處理完Broadcast之後,将在各個receiver間傳遞的resultExtras傳回給resultReceiver。這個resultExtras既可以由sender指定,也可以由某一個receiver指定,一般情況下隻要receiver需要向sender傳回資料,則可以通過這個resultExtras sendOrderedBroadcast(Intent intent,String receiverPermission, BroadcastReceiver resultReceiver,Handler scheduler, int initialCode, String initialData,Bundle initialExtras)
Sticky Broadcast。前面已描述

sendStickyBroadcast(Intent intent);

endStickyOrderedBroadcast(Intent intent,BroadcastReceiver resultReceiver,Handler scheduler, int initialCode, String initialData,Bundle initialExtras)

2.2 Package manager Broadcast

    AMS在處理收到的Broadcast時,需要對一些特殊的Broadcast做特殊處理,比如Package manager發來的關于Package移除、增加、修改等操作的,由于android系統允許user在package内部component在運作的情況下進行如上所述的操作,是以每當發生這些操作時,PM則會通過發送Broadcast的方式通知AMS進行屬于AMS範疇的一些操作,如當PM移除某個Package的時候,要求AMS停止這個Package相關的所有Component等。

    同樣的,關于timezone的一些事件也是通過Broadcast的方式通知AMS,這裡就不再分析。

2.3 Protected Broadcast

2.3.1 Protected Broadcast作用

    從其名稱看即是受保護的Broadcast,它聲明在AndroidManifest.xml的一級節點中,對應的節點名稱為<protected-broadcast>,一般情況下,protected broadcast定義的是系統級别的broadcast,通常隻能被系統服務或者appliaction發送,下面我們分析那些情況下才能發送protected broadcast。

    首先看一下protectedbroadcast的定義,它的主要元素就是一個action name,這些action隻能被系統發送,我們可以想象一下,系統級别的broadcast可以不受限制的被所有的應用發送的後果,那将是非常嚴重的,不可想象的。是以從安全性的角度出發,android為了保證這些系統級别的broadcast不被第三方的應用濫發,提出了這個protected broadcast的概念。

<protected-broadcast 
android:name=[action String] /> 
           

Android規定了一些可以發送protected broadcast的程序,

●  android:sharedUserId="android.uid.system"

●  android:sharedUserId="android.uid.phone"

●  android:sharedUserId="android.uid.shell"

●  設定android:persistent的appliaction。

對于其他不符合上述四條的程序發送broadcast時,AMS回去檢查該是否是protected broadcast規定的action,如果是,那麼将不允許這個broadcast發送。具體代碼如下:

broadcastIntentLocked()@ActivityManagerService.java

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 BROADCAST_SUCCESS;
            }
        }
           

2.3.2 Protected Broadcast定義

    上面介紹了Protected Broadcast的作用,知道這類Broadcast一般是系統發送的,那麼我們再來分析一下哪些AndroidManifest.xml中可以定義Protected Broadcast。我們可容易的想到,Protected Broadcast也不是随随便便定義在一個application的AndroidManifest.xml。

     隻有系統appliaction才能在其AndroidManifest.xml中定義Protected Broadcast,系統appliaction包括/system/framework、/system/app、vendor/app下的package,是以裝置中安裝的第三方apk中如果定義了Protected Broadcast,那麼這個Protected Broadcast将不生效。下面代碼是對<protected-broadcast>屬性的解析,可以看出解析之後會檢查目前的package是否為系統package,如果是則提取這個ProtectedBroadcast。

parsePackage ()@PackageParser.java

} else if (tagName.equals("protected-broadcast")) {
                sa = res.obtainAttributes(attrs,
                        com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);

                // Note: don't allow this value to be a reference to a resource
                // that may change.
                String name = sa.getNonResourceString(
                       com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name);

                sa.recycle();

                if (name != null && (flags&PARSE_IS_SYSTEM) != 0) {
                    if (pkg.protectedBroadcasts == null) {
                        pkg.protectedBroadcasts = new ArrayList<String>();
                    }
                    if (!pkg.protectedBroadcasts.contains(name)) {
                        pkg.protectedBroadcasts.add(name.intern());
                    }
                }

                XmlUtils.skipCurrentTag(parser);
                
            }
           

2.4 查找receiver

    這一節來分析一下AMS在收到Broadcast之後,查找比對的receiver來向其分發這個Broadcast。前面說過receiver有兩種,一種是動态注冊的,另一種則是預先定義靜态注冊的。不同類型的receiver有不同的管理方式,動态注冊的receiver從上面章節中介紹的IntentResolver mReceiverResolver中擷取;靜态注冊的receiver則是從PM中擷取。

2.5 Broadcast分發模式

    2.1中介紹了幾種android中常用的Broadcast發送方式,sticky模式上面的文章中已經做了介紹,下面主要介紹一些ordered模式和非ordered模式下,不同的分發模式。

2.5.1 非ordered模式

    預設情況下,Broadcast發送方式是非order模式的,這裡所說的ordered和非ordered模式是針對動态注冊的receiver來說的,而對于靜态注冊的receiver則一律是ordered模式。非ordered模式下,AMS對Broadcast的分發是一種非同步的模式,也就是說,不待一個receiver處理完Broadcast,就會将Broadcast分發給下一個receiver。

2.5.2 ordered模式

    ordered模式下,動态注冊的receiver将會采取和靜态注冊的receiver相同的處理模式,類似于一種同步模式,AMS會等待一個reciever處理完這個Broadcast,才會将Broadcast分發給下一個receiver。這種模式下,receiver的處理有了先後順序,是以必須考慮如何布置這個先後順序,這裡按照優先級的順序來給receiver的處理順序進行排序。

    對于動态注冊的receiver,可以在其IntentFilter類型中定義它的優先級;而對于靜态注冊的receiver,同樣的我們可以在其<intent-filter>屬性中定義它的優先級。根據receiver優先級從高到低進行排序,如果動态的注冊的和靜态注冊的receive處在相同的優先級,那麼将優先處理動态注冊的receiver。

broadcastIntentLocked()@ActivityManagerService.java

int NT = receivers != null ? receivers.size() : 0;
            int it = 0;
            ResolveInfo curt = null;
            BroadcastFilter curr = null;
            while (it < NT && ir < NR) {
                if (curt == null) {
                    curt = (ResolveInfo)receivers.get(it);
                }
                if (curr == null) {
                    curr = registeredReceivers.get(ir);
                }
                if (curr.getPriority() >= curt.priority) {
                    // Insert this broadcast record into the final list.
                    receivers.add(it, curr);
                    ir++;
                    curr = null;
                    it++;
                    NT++;
                } else {
                    // Skip to the next ResolveInfo in the final list.
                    it++;
                    curt = null;
                }
            }
        }
        while (ir < NR) {
            if (receivers == null) {
                receivers = new ArrayList();
            }
            receivers.add(registeredReceivers.get(ir));
            ir++;
        }
           

2.6 Broadcast分發過程

    Broadcast分發過程是一個異步過程,AMS在收到Broadcast之後,會根據不同類型的receiver,按照Broadcast發送發指定的發送模式進行分發,現在我們來分析一下Broadcast分發過程。

    AMS向自己的消息隊列發送消息BROADCAST_INTENT_MSG對所有的receiver進行分發操作,所有的分發操作由方法processNextBroadcast()來實作,是以這一部分我們着重分析一下這個方法中的實作過程。

android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程
android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程

2.6.1 非ordered分發

    上面已經對ordered模式和非ordered模式進行了簡單介紹,這裡不再多介紹了。對于非ordered模式的分發過程,AMS則會将Broadcast直接分發給符合要求的receiver的程序的消息隊列,這種分發的方式可以視作一種異步方式,也可以看作是将該Broadcast同時分發給符合要求的動态注冊的receiver,上面章節中我們分析過隻有動态注冊的receiver才會有ordered和非ordered模式之分,而靜态注冊的reciever則無此之分,一概視為ordered模式。

android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程
android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程

2.6.2 ordered分發

    滿足ordered模式的receiver,AMS對其進行Broadcast分發時是一種同步的過程,隻有前一個receiver處理完該Broadcast之後,并且receiver通知AMS該Broadcast已經處理結束,此時AMS才能将Broadcast分發給下一個receiver去處理,如下圖所示:

android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程
android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程

    我們知道需要ordered分發的receiver既包括動态注冊的receiver,又包括靜态注冊的receiver,但是AMS向兩種不同的注冊方式的receiver分發Broadcast的方式有不盡相同,但是最基本的發送及處理過程是按照上面的圖示進行的,兩種注冊方式具體的分發和處理内容如下圖所示:

android Application Component研究之BroadcastReceiver1. BroadcastReceiver注冊2. Broadcast的發送過程

2.7 Broadcast處理逾時

    AMS向各個BroadcastReceiver分發Broadcast過程以及BroadcastReceiver處理Broadcast的過程并不是無限期的,為了保證系統的響應性和穩定性,android給這個過程設定了固定期限,有Broadcast相關的有兩種固定期限,這兩種期限均是針對Ordered模式的Receiver,而對于非Ordered模式的Receiver則不需要考慮這個時限,下面來一一介紹一下。

2.7.1 BroadcastRecord處理總時長

    針對每個BroadcastRecord的處理,均會設定一個總時長,這個總時長是指所有符合BroadcastRecord條件的receiver處理完這個Broadcast的總時長的上限。也就是說如果滿足條件的所有的Receiver均已經接收并處理完了這個Broadcast的時刻,不能超過規定的總時長,總時長跟滿足條件的Receiver總個數有關系,如下面代碼所示:

processNextBroadcast ()@ActivityManagerService.java

int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
if (mProcessesReady && r.dispatchTime > 0) {
    long now = SystemClock.uptimeMillis();
    if ((numReceivers > 0) &&
            (now > r.dispatchTime + (2*BROADCAST_TIMEOUT*numReceivers))) {
        Slog.w(TAG, "Hung broadcast discarded after timeout failure:"
                + " now=" + now
                + " dispatchTime=" + r.dispatchTime
                + " startTime=" + r.receiverTime
                + " intent=" + r.intent
                + " numReceivers=" + numReceivers
                + " nextReceiver=" + r.nextReceiver
                + " state=" + r.state);
        broadcastTimeoutLocked(false); // forcibly finish this broadcast
        forceReceive = true;
        r.state = BroadcastRecord.IDLE;
    }
}
           

2.7.2 BroadcastReceiver處理時長  

    除了設定針對一個BroadcastRecord的處理的總時長外,android針對每個Receiver的處理時長也做了一個時限設定,保證每一個的Receiver的處理時長不超過規定時長,具體如下面代碼所示:

processNextBroadcast ()@ActivityManagerService.java

if (! mPendingBroadcastTimeoutMessage) {
    long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT;
    if (DEBUG_BROADCAST) Slog.v(TAG,
            "Submitting BROADCAST_TIMEOUT_MSG for " + r + " at " + timeoutTime);
    setBroadcastTimeoutLocked(timeoutTime);
}
           

    如果上述2種時長存在逾時的話,AMS将會抛出ANR, 向user報告目前的Receiver在處理Broadcast時存在無響應狀況或者響應時間過長等狀況。

broadcastTimeoutLocked()@ActivityManagerService.java

if (anrMessage != null) {
    // Post the ANR to the handler since we do not want to process ANRs while
    // potentially holding our lock.
    mHandler.post(new AppNotResponding(app, anrMessage));
}
           

繼續閱讀