Android廣播注冊過程

簡介

廣播作為Android系統(tǒng)四大組件之一舆吮,起得作用是相當大援所,安卓系統(tǒng)進程之間通信是相當頻繁庐舟,而且解耦工作是重中之重,那么作為這樣的設計初衷住拭,所以誕生了廣播挪略。我們今天就來一步步看看安卓中廣播是怎么樣進行工作的历帚。

使用

  1. 自定義廣播接受者
public class MyReceiver extends BroadcastReceiver {  
      
    private static final String TAG = "MyReceiver";  
      
    @Override  
    public void onReceive(Context context, Intent intent) {  
        String msg = intent.getStringExtra("msg");  
        Log.i(TAG, msg);  
    }  
}  
  1. 注冊廣播
    • 靜態(tài)注冊
    <receiver android:name=".MyReceiver">  
        <intent-filter>  
            <action android:name="android.intent.action.MY_BROADCAST"/>  
            <category android:name="android.intent.category.DEFAULT" />  
        </intent-filter>  
    </receiver>  
    
    • 動態(tài)注冊
    MyReceiver receiver = new MyReceiver();  
    IntentFilter filter = new IntentFilter();  
    filter.addAction("android.intent.action.MY_BROADCAST");  
    registerReceiver(receiver, filter);  
    
  2. 發(fā)送廣播
public void send(View view) {  
    Intent intent = new Intent("android.intent.action.MY_BROADCAST");  
    intent.putExtra("msg", "hello receiver.");  
    sendBroadcast(intent);  
}
  1. 解除廣播
@Override  
protected void onDestroy() {  
    super.onDestroy();  
    unregisterReceiver(receiver);  
}  

補充:

有序廣播的定義案例:


<receiver android:name=".FirstReceiver">  
    <intent-filter android:priority="1000">  
        <action android:name="android.intent.action.MY_BROADCAST"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</receiver>  
<receiver android:name=".SecondReceiver">  
    <intent-filter android:priority="999">  
        <action android:name="android.intent.action.MY_BROADCAST"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</receiver>  
<receiver android:name=".ThirdReceiver">  
    <intent-filter android:priority="998">  
        <action android:name="android.intent.action.MY_BROADCAST"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</receiver>

源碼分析

注冊廣播源碼分析

我們通過ContextImpl對象最后會進入如下代碼:

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
        IntentFilter filter, String broadcastPermission,
        Handler scheduler, Context context) {
    IIntentReceiver rd = null;
    if (receiver != null) {
        //mPackageInfo的類型為LoadedApk
        if (mPackageInfo != null && context != null) {
            //類型是Handler,沒有設置的時候用主線程和處理
            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 {
        final Intent intent = ActivityManagerNative.getDefault().registerReceiver(
                mMainThread.getApplicationThread(), mBasePackageName,
                rd, filter, broadcastPermission, userId);
        if (intent != null) {
            intent.setExtrasClassLoader(getClassLoader());
            intent.prepareToEnterProcess();
        }
        return intent;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

mPackageInfo.getReceiverDispatcher()這個方法主要在LoadedApk的private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers根據BroadcastReceiver r拿到LoadedApk.ReceiverDispatcher如果沒有杠娱,則new ReceiverDispatcher將信息封裝到ReceiverDispatcher中然后保存到數組中挽牢,當new ReceiverDispatcher會創(chuàng)建(mIIntentReceiver = new InnerReceiver(this, !registered);這個是一個binder)

反正上面代碼執(zhí)行完成就要有LoadedApk.ReceiverDispatcher的創(chuàng)建,并且要調用AMS的registerReceiver()
同樣在new ReceiverDispatcher的時候就會創(chuàng)建IIntentReceiver對象墨辛,這個對象是繼承Binder的。

在使用廣播的時候趴俘,我們就會遇到一種情況睹簇,就是廣播導致的anr,由于上面handler是主線程的handler寥闪,所在在oReceiver的時候太惠,一旦超時就會引發(fā)anr問題,我們可能會想和service處理辦法一樣疲憋,由于service的onCreat也是在主線程中調用的凿渊,我們處理會啟動一個子線程,在子線程中完成我們需要的工作缚柳。同理我們可不可以在onReceive方法中啟動子線程呢埃脏?

答案是不可以!如果我們用了子線程可能會產生這樣一種情況秋忙,onReceive的生命周期很短可能我們在子線程中的工作沒有做完彩掐,AMS就銷毀進程了,這個時候就達不到我們需要的結果灰追。

所以廣播也給我們準備了一種辦法堵幽,就是PendingResult,它是定義在BroadcastReceiver中的:

public abstract class BroadcastReceiver {
    private PendingResult mPendingResult;

具體的做法是:

  1. 先調用BroadcastReceiver的goAsync函數得到一個PendingResult對象弹澎,
  2. 然后將該對象放到工作線程中去釋放朴下。

這樣onReceive函數就可以立即返回而不至于阻塞主線程。
同時苦蒿,Android系統(tǒng)將保證BroadcastReceiver對應進程的生命周期殴胧,
直到工作線程處理完廣播消息后,調用PendingResult的finish函數為止佩迟。

private class MyBroadcastReceiver extends BroadcastReceiver {
    ..................
    public void onReceive(final Context context, final Intent intent) {
        //得到PendingResult
        final PendingResult result = goAsync();  
        //放到異步線程中執(zhí)行
        AsyncHandler.post(new Runnable() {  
            @Override  
            public void run() {  
                handleIntent(context, intent);//可進行一些耗時操作  
                result.finish();  
            }  
        });  
    } 
}
final class AsyncHandler {  
    private static final HandlerThread sHandlerThread = new HandlerThread("AsyncHandler");  
    private static final Handler sHandler;  
    static {  
        sHandlerThread.start();  
        sHandler = new Handler(sHandlerThread.getLooper());  
    }  
    public static void post(Runnable r) {  
        sHandler.post(r);  
    }  
    private AsyncHandler() {}  
}  

細節(jié)對應到源碼

public final PendingResult goAsync() {
    PendingResult res = mPendingResult;
    mPendingResult = null;
    return res;
}

在onReceive函數中執(zhí)行異步操作溃肪,主要目的是避免一些操作阻塞了主線程,
但整個操作仍然需要保證在10s內返回結果音五,尤其是處理有序廣播和靜態(tài)廣播時惫撰。
畢竟AMS必須要收到返回結果后,才能向下一個BroadcastReceiver發(fā)送廣播躺涝。

源碼過程

ContextImpl.java

    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            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 {
            final Intent intent = ActivityManagerNative.getDefault().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission, userId);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

首先傳遞進來的核心參數:

  • BroadcastReceiver receiver
  • Context context
  • IntentFilter filter

此方法第一步:

  • scheduler = mMainThread.getHandler();//獲取主線程
  • 獲取IIntentReceiver厨钻,是將receiver封裝的對象
rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
  • 最后通過AMS的registerReceiver將信息注冊到AMS中

我們分析一下這個IIntentReceiver rd到底是什么扼雏?

public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
        Context context, Handler handler,
        Instrumentation instrumentation, boolean registered) {
    synchronized (mReceivers) {
        LoadedApk.ReceiverDispatcher rd = null;
        ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
        if (registered) {
            map = mReceivers.get(context);
            if (map != null) {
                rd = map.get(r);
            }
        }
        if (rd == null) {
            rd = new ReceiverDispatcher(r, context, handler,
                    instrumentation, registered);
            if (registered) {
                if (map == null) {
                    map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                    mReceivers.put(context, map);
                }
                map.put(r, rd);
            }
        } else {
            rd.validate(context, handler);
        }
        rd.mForgotten = false;
        return rd.getIIntentReceiver();
    }
}

這里面的幾個核心數據結構:

  • mReceivers
      private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers;
    
  • map
      ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map;
    
  • rd
      rd = new ReceiverDispatcher(r, context, handler, instrumentation, registered);
    

由上面代碼可以看出來是:

  • 將BroadcastReceiver(r)封裝到ReceiverDispatcher(rd)
  • 將r和rd對應起來到一個map
  • context和map對應起來

最后形成的效果是:

ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>>

方法最后返回final IIntentReceiver.Stub mIIntentReceiver;這個是在ReceiverDispatcher的構造中進行創(chuàng)建的,傳入的參數是ReceiverDispatcher這個對象本身夯膀,也就是說receiver诗充,context等等所有的引用都在這里面。同時它是一個Binder對象诱建,說明可以夸進程傳輸數據蝴蜓。

上面的信息說明,在getReceiverDispatcher這個方法中將receiver俺猿,context等等都保存起來茎匠,并且統(tǒng)一在LoadedApk列表中進行保存。于此同時返回一個Binder對象給調用者(注冊廣播的對象)

小節(jié):

到這里我們明白ContextImpl對象的registerReceiverInternal方法的目的是將信息保存到LoadedApk中押袍,并且生成Binder對象這個對象中存有所有recevire和context等重要引用诵冒,最后AMS就可以通過這個Binder對象的引用和當前注冊廣播的進程進行通信。

AMS.registerReceiver()

public Intent registerReceiver(IApplicationThread caller, String callerPackage,
        IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {

part-1

    ...............
    //保存系統(tǒng)內已有的sticky廣播
    ArrayList<Intent> stickyIntents = null;
    ...............
    synchronized(this) {
        if (caller != null) {
            //根據IApplicationThread得到對應的進程信息
            callerApp = getRecordForAppLocked(caller);
            if (callerApp == null) {
                //拋出異常谊惭,即不允許未登記的進程注冊BroadcastReceiver
                ...............
            }
            if (callerApp.info.uid != Process.SYSTEM_UID &&
                    !callerApp.pkgList.containsKey(callerPackage) &&
                    !"android".equals(callerPackage)) {
                //拋出異常汽馋,即注冊進程必須攜帶Package名稱
                ..................
            }
        } else {
            .............
        }
        ......................
        //為了得到IntentFilter中定義的Action信息,先取出其Iterator
        Iterator<String> actions = filter.actionsIterator();
        if (actions == null) {
            ArrayList<String> noAction = new ArrayList<String>(1);
            noAction.add(null);
            actions = noAction.iterator();
        }
        //可能有多個action圈盔,所以這里得到action的迭代器actions
        
        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
        while (actions.hasNext()) {
            //依次比較BroadcastReceiver關注的Action與Stick廣播是否一致
            String action = actions.next();
            for (int id : userIds) {
                ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                if (stickies != null) {
                    //Sticky廣播中豹芯,有BroadcastReceiver關注的
                    //可能有多個Intent,對應的Action相似驱敲,在此先做一個初步篩選
                    ArrayList<Intent> intents = stickies.get(action);
                    if (intents != null) {
                        //如果stickyIntents==null告组,則創(chuàng)建對應列表
                        if (stickyIntents == null) {
                            stickyIntents = new ArrayList<Intent>();
                        }
                        //將這些廣播保存到stickyIntents中
                        stickyIntents.addAll(intents);
                    }
                }
            }
        }
    }

    ArrayList<Intent> allSticky = null;
    //stickyIntents中保存的是action匹配的
    if (stickyIntents != null) {
        //用于解析intent的type
        final ContentResolver resolver = mContext.getContentResolver();
        // Look for any matching sticky broadcasts...
        for (int i = 0, N = stickyIntents.size(); i < N; i++) {
            Intent intent = stickyIntents.get(i);
            //此時進一步判斷Intent與BroadcastReceiver的IntentFilter是否匹配
            if (filter.match(resolver, intent, true, TAG) >= 0) {
                if (allSticky == null) {
                    allSticky = new ArrayList<Intent>();
                }
                allSticky.add(intent);
            }
        }
    }

第一部分主要工作是:

  • 得到action的迭代器
  • 得到UserAll,當前user的所有粘連廣播列表添加到sticktIntents中
  • stickyIntents中進行匹配將所有對應粘連廣播的intent添加到allSticky中癌佩。

part-2

    //得到粘連掛廣播中的第一個廣播
    Intent sticky = allSticky != null ? allSticky.get(0) : null;
    ................
    synchronized (this) {
        ..............
        //一個Receiver可能監(jiān)聽多個廣播木缝,多個廣播對應的BroadcastFilter組成ReceiverList
        ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
        if (rl == null) {
            //首次注冊,rl為null围辙,進入此分支
            //新建BroadcastReceiver對應的ReceiverList
            rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                    userId, receiver);
            if (rl.app != null) {
                //添加到注冊進程里
                rl.app.receivers.add(rl);
            } else {
                try {
                    //rl監(jiān)聽receiver所在進程的死亡消息
                    receiver.asBinder().linkToDeath(rl, 0);
                } catch (RemoteException e) {
                    return sticky;
                }
                rl.linkedToDeath = true;
            }
            //保存到AMS上
            mRegisteredReceivers.put(receiver.asBinder(), rl);
        } ..........
        ............

這里牽扯到一個數據結構:

  • mRegisteredReceivers
final HashMap<IBinder, ReceiverList> mRegisteredReceivers;

這個主要保存的是注冊的廣播的receiver和ReceiverList我碟,再看看ReceiverList這個數據結構

class ReceiverList extends ArrayList<BroadcastFilter>
主要是存BroadcastFilter

怎么理解呢?就是一個廣播接受者可以對應多個廣播過濾器姚建,也就是BroadcastFilter矫俺,所以就形成了HashMap<IBinder, ReceiverList>這種結構

這種結構也就是說,一個廣播可以注冊多次掸冤,每次filter不同厘托,則會將filter添加到對應的廣播接收器中。

第二部分做的工作主要有:
將filter保存到對應的BroadcastFilter中稿湿,然后將BroadcastFilter保存到對應的ReceiverList中铅匹,也就是說一個recevier對應多個Filter。

part-3

        //創(chuàng)建當前IntentFilter對應的BroadcastFilter
        //AMS收到廣播后饺藤,將根據BroadcastFilter決定是否將廣播遞交給對應的BroadcastReceiver
        //一個BroadcastReceiver可以對應多個IntentFilter
        //這些IntentFilter對應的BroadcastFilter共用一個ReceiverList
        BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                permission, callingUid, userId);
        rl.add(bf);
        ................
        mReceiverResolver.addFilter(bf);

        // Enqueue broadcasts for all existing stickies that match
        // this filter.
        //allSticky不為空包斑,說明有粘性廣播需要發(fā)送給剛注冊的BroadcastReceiver
        if (allSticky != null) {
            ArrayList receivers = new ArrayList();
            //receivers記錄bf
            receivers.add(bf);

            final int stickyCount = allSticky.size();
            for (int i = 0; i < stickyCount; i++) {
                Intent intent = allSticky.get(i);
                //根據intent的flag (FLAG_RECEIVER_FOREGROUND)決定是使用gBroadcastQueue還是BgBroadcastQueue
                BroadcastQueue queue = broadcastQueueForIntent(intent);

                //創(chuàng)建廣播對應的BroadcastRecord
                //BroadcastRecord中包含了Intent流礁,即知道要發(fā)送什么廣播;
                //同時其中含有receivers罗丰,內部包含bf神帅,即知道需要將廣播發(fā)送給哪個BroadcastReceiver
                BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                        null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers,
                        null, 0, null, null, false, true, true, -1);

                //加入到BroadcastQueue中,存入的是并發(fā)廣播對應的隊列
                queue.enqueueParallelBroadcastLocked(r);

                //將發(fā)送BROADCAST_INTENT_MSG萌抵,觸發(fā)AMS發(fā)送廣播
                queue.scheduleBroadcastsLocked();
            }
        }
    }
}

將通過intent匹配的粘連廣播進行發(fā)送找御。

小節(jié)上面1.2.3部分

  1. 將系統(tǒng)中粘連廣播根據intent進行匹配并得到保存在粘連廣播列表
  2. 將傳遞進來的receiver和對應的intentfilterList對應起來保存到對應列表
  3. 發(fā)送匹配到的粘連廣播

這里可以看出粘連廣播的特性,就是如果注冊是廣播是粘連廣播那么就在注冊的時候就會給所有接受這個粘連廣播的接收器發(fā)送廣播绍填。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末霎桅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沐兰,更是在濱河造成了極大的恐慌哆档,老刑警劉巖蔽挠,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件住闯,死亡現(xiàn)場離奇詭異,居然都是意外死亡澳淑,警方通過查閱死者的電腦和手機比原,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杠巡,“玉大人量窘,你說我怎么就攤上這事∏庥担” “怎么了蚌铜?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫩海。 經常有香客問我冬殃,道長,這世上最難降的妖魔是什么叁怪? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任审葬,我火速辦了婚禮,結果婚禮上奕谭,老公的妹妹穿的比我還像新娘涣觉。我一直安慰自己,他們只是感情好血柳,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布官册。 她就那樣靜靜地躺著,像睡著了一般难捌。 火紅的嫁衣襯著肌膚如雪攀隔。 梳的紋絲不亂的頭發(fā)上皂贩,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音昆汹,去河邊找鬼明刷。 笑死,一個胖子當著我的面吹牛满粗,可吹牛的內容都是我干的辈末。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼映皆,長吁一口氣:“原來是場噩夢啊……” “哼挤聘!你這毒婦竟也來了?” 一聲冷哼從身側響起捅彻,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤组去,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后步淹,有當地人在樹林里發(fā)現(xiàn)了一具尸體从隆,經...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年缭裆,在試婚紗的時候發(fā)現(xiàn)自己被綠了键闺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡澈驼,死狀恐怖辛燥,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情缝其,我是刑警寧澤挎塌,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站内边,受9級特大地震影響榴都,放射性物質發(fā)生泄漏。R本人自食惡果不足惜假残,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一缭贡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辉懒,春花似錦阳惹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颠印,卻和暖如春纲岭,著一層夾襖步出監(jiān)牢的瞬間抹竹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工止潮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窃判,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓喇闸,卻偏偏與公主長得像袄琳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子燃乍,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容