Android Broadcast原理分析之registerReceiver詳解

1. BroadcastReceiver概述

廣播作為四大組件之一,在平時開發過程中會大量使用到,使用方式也是多種多樣的,既可以自己在manifest中註冊,也可以在java代碼中動態註冊,既可以接收由系統發出的廣播,也可以接受自己定義並發送的廣播。廣播可以實現進程內以及跨進程之間的通信。從本文開始將分別介紹廣播的註冊,廣播的派發,本地廣播(LocalBroadcast)以及Android O上對廣播的限制,本文主要介紹廣播動態註冊。

2. BroadcastReceiver分類

從註冊方式上區分:動態註冊以及靜態註冊(顯示廣播和隱式廣播)
從發送方式上區分:無序廣播和有序廣播
從處理類型上區分:前臺廣播和後臺廣播
從運行方式上區分:普通廣播和Sticky廣播(已棄用)
從發送者區分:系統廣播和自定義廣播
此外還有protect broadcast(隻允許指定應用可以發送)
sticky廣播:系統保存瞭一部分廣播的狀態,當你註冊的時候,不需要等到下次這個廣播發出來,直接把最近上一次發送的這個廣播返回給你

以上的這些概念在接下來的介紹中都會逐個涉及。

3. registerReceiver流程圖

registerReceiver

其中的APP,ContextImpl,LoadedApk,ActivityManagerProxy都在APP本身的進程中,ActivityManagerService在system_server進程中。

  1. 首先在APP的進程中發起廣播的註冊,通過registerReceiver接口,這個方法有很多重載方法,但是最終的入口都是在ContextImpl中,後面會詳細介紹
  2. 從之前的Context的學習可以知道,registerReceiver最終調用的實現在ContextImpl
  3. 如果沒有指定處理廣播的handler,則默認使用主線程的handler
  4. 獲取要註冊的ReceiverDispatcher,在註冊的Context相同的情況下,每個Receiver對應一個ReceiverDispatcher
  5. 通過binder call到systemserver進行廣播註冊

4. 源碼解析

4.1 ContextImpl.registerReceiverInternal

    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context, int flags) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    // 註冊receiver的時候可以指定接受recover的Handler
                    // 如果沒有指定,則默認用主線程的handler處理
                    scheduler = mMainThread.getHandler();
                }
                // 獲取IIntentReceiver
                // 這個是一個Binder對象,當廣播來臨時,用於AMS向客戶端發起回調
                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 {
            // binder call至AMS,進行廣播註冊
            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

參數解析:

receiver:將要註冊的receiver
userId:用戶空間標志,默認情況下我們都隻有一個user,現在一些手機推出的分身,其實就是用的第二個user,這種情況下userid會變,否則默認主空間的都是0
IntentFilter:要註冊的廣播的filter
broadcastPermission:指定要註冊的廣播的權限
scheduler:指定廣播接受(也就是onReceive)所在的線程,也就是說註冊的時候就可以指定好廣播處理放在哪個線程,如果receiver中事情太多,可以放在另外一個線程,這樣可以避免主線程被卡住
context:通過getOuterContext獲取到,前面在介紹context的時候有提到,application/service/activity中獲取到的是不一樣的
flags:註冊廣播所攜帶的flag

4.2 LoadedApk.getReceiverDispatcher

    public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
            Context context, Handler handler,
            Instrumentation instrumentation, boolean registered) {
        synchronized (mReceivers) {
            // 如果Context相同,每個receiver對應一個ReceiverDispatcher
            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是一個二級map,一級key是context,二級key是BroadcastReceiver,value是ReceiverDispatcher。

這裡的ReceiverDispatcher又是什麼呢?

它是LoadedApk中的一個內部類,保存瞭這個receiver的信息,用於在廣播派發到本進程的時候執行,上面這方法最重要的是getIIntentReceiver,這個就非常重要瞭,它是一個Binder對象,說在廣播註冊的時候將這個binder對象傳遞到瞭AMS,然後當廣播派發到本進程的時候,通過這個binder對象再會調回來,它在ReceiverDispatcher創建的時候創建。

    static final class ReceiverDispatcher {
        // 是一個binder對象
        final static class InnerReceiver extends IIntentReceiver.Stub {
            final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
            final LoadedApk.ReceiverDispatcher mStrongRef;

            InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
                mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
                mStrongRef = strong ? rd : null;
            }

            @Override
            public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                    // 這裡就是廣播真正派發到本進程的時候從systemserver binder call過來執行的
                ...
        }

        final IIntentReceiver.Stub mIIntentReceiver;
        final BroadcastReceiver mReceiver;
        final Context mContext;
        final Handler mActivityThread;
        final Instrumentation mInstrumentation;
        final boolean mRegistered;
        final IntentReceiverLeaked mLocation;
        RuntimeException mUnregisterLocation;
        boolean mForgotten;
        ...
    }

到這裡,廣播註冊在APP進程的流程就走完瞭,主要做瞭幾件事:

  1. 獲取handler
  2. 獲取ReceiverDispatcher
  3. 獲取InnerReceiver
  4. 將上面這些連帶receiver的相關信息,發起binder call到ActivityManagerService

4.3 ActivityManagerService.registerReceiver

    public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
            int flags) {
        enforceNotIsolatedCaller("registerReceiver");
        ArrayList<Intent> stickyIntents = null;
        ProcessRecord callerApp = null;
        final boolean visibleToInstantApps
                = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
        int callingUid;
        int callingPid;
        boolean instantApp;
        synchronized(this) {
            if (caller != null) {
                // 正常來講caller是發起binder call的客戶端進程對應的ApplicationThread對象
                // 如果為null則拋異常
                callerApp = getRecordForAppLocked(caller);
                if (callerApp == null) {
                    throw new SecurityException(
                            "Unable to find app for caller " + caller
                            + " (pid=" + Binder.getCallingPid()
                            + ") when registering receiver " + receiver);
                }
                if (callerApp.info.uid != SYSTEM_UID &&
                        !callerApp.pkgList.containsKey(callerPackage) &&
                        !"android".equals(callerPackage)) {
                    throw new SecurityException("Given caller package " + callerPackage
                            + " is not running in process " + callerApp);
                }
                callingUid = callerApp.info.uid;
                callingPid = callerApp.pid;
            } else {
                callerPackage = null;
                callingUid = Binder.getCallingUid();
                callingPid = Binder.getCallingPid();
            }

            // 判斷caller是否為instant app
            instantApp = isInstantApp(callerApp, callerPackage, callingUid);
            userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
                    ALLOW_FULL_ONLY, "registerReceiver", callerPackage);

            // 獲取廣播註冊的filter中的action封裝到list中
            Iterator<String> actions = filter.actionsIterator();
            if (actions == null) {
                ArrayList<String> noAction = new ArrayList<String>(1);
                noAction.add(null);
                actions = noAction.iterator();
            }

            // mStickyBroadcasts是一個二級map
            // 一級key是userId,二級key是廣播對應的action,value是廣播對應intent的list(一般隻有一個intent)
            // 這裡是為瞭查詢對於當前user,本次註冊的所有action對應的sticky廣播的intent
            int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
            while (actions.hasNext()) {
                String action = actions.next();
                for (int id : userIds) {
                    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                    if (stickies != null) {
                        ArrayList<Intent> intents = stickies.get(action);
                        if (intents != null) {
                            if (stickyIntents == null) {
                                stickyIntents = new ArrayList<Intent>();
                            }
                            stickyIntents.addAll(intents);
                        }
                    }
                }
            }
        }

        ArrayList<Intent> allSticky = null;
        // 這裡不為null表示本次註冊的廣播中有sticky廣播
        if (stickyIntents != null) {
            final ContentResolver resolver = mContext.getContentResolver();
            // 查找匹配的sticky廣播
            for (int i = 0, N = stickyIntents.size(); i < N; i++) {
                Intent intent = stickyIntents.get(i);
                // 如果caller是instant app,且intent的flag不允許對instant可見,則跳過
                if (instantApp &&
                        (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
                    continue;
                }
                // If intent has scheme "content", it will need to acccess
                // provider that needs to lock mProviderMap in ActivityThread
                // and also it may need to wait application response, so we
                // cannot lock ActivityManagerService here.
                if (filter.match(resolver, intent, true, TAG) >= 0) {
                    if (allSticky == null) {
                        allSticky = new ArrayList<Intent>();
                    }
                    allSticky.add(intent);
                }
            }
        }

        // 直接把最近的一個匹配到的sticky廣播返回
        Intent sticky = allSticky != null ? allSticky.get(0) : null;
        // 廣播註冊的時候receiver是可以為null的,這種情況下這裡直接return
        if (receiver == null) {
            return sticky;
        }

        synchronized (this) {
            // 校驗caller進程是否正常
            if (callerApp != null && (callerApp.thread == null
                    || callerApp.thread.asBinder() != caller.asBinder())) {
                // Original caller already died
                return null;
            }
            // mRegisteredReceivers中存放瞭所有的已註冊的receiver
            // 每個BroadcastReceiver對應一個InnerReceiver,即Binder對象
            // binder對象做key,value是ReceiverList
            // ReceiverList是一個ArrayList
            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
            if (rl == null) {
                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver);
                if (rl.app != null) {
                    rl.app.receivers.add(rl);
                } else {
                    try {
                        // 如果是新創建的receiver,還需要linkToDeath
                        receiver.asBinder().linkToDeath(rl, 0);
                    } catch (RemoteException e) {
                        return sticky;
                    }
                    rl.linkedToDeath = true;
                }
                // 放入mRegisteredReceivers
                mRegisteredReceivers.put(receiver.asBinder(), rl);
            } else if (rl.uid != callingUid) {
                throw new IllegalArgumentException(
                        "Receiver requested to register for uid " + callingUid
                        + " was previously registered for uid " + rl.uid
                        + " callerPackage is " + callerPackage);
            } else if (rl.pid != callingPid) {
                throw new IllegalArgumentException(
                        "Receiver requested to register for pid " + callingPid
                        + " was previously registered for pid " + rl.pid
                        + " callerPackage is " + callerPackage);
            } else if (rl.userId != userId) {
                throw new IllegalArgumentException(
                        "Receiver requested to register for user " + userId
                        + " was previously registered for user " + rl.userId
                        + " callerPackage is " + callerPackage);
            }
            // 每一個IntentFilter對應一個BroadcastFilter
            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                    permission, callingUid, userId, instantApp, visibleToInstantApps);
            // receiverList中存放瞭通過這個receiver註冊的所有的filter
            // 每調用一次register就會add一次
            rl.add(bf);
            // mReceiverResolver中存放所有的BroadcastFilter
            mReceiverResolver.addFilter(bf);

            // 有匹配的sticky廣播,則直接開始調度派發
            if (allSticky != null) {
                ArrayList receivers = new ArrayList();
                receivers.add(bf);

                // 對於每一個sticky廣播,創建BroadcastRecord並入隊(並行)
                final int stickyCount = allSticky.size();
                for (int i = 0; i < stickyCount; i++) {
                    Intent intent = allSticky.get(i);
                    // 根據flag是否有FLAG_RECEIVER_FOREGROUND判斷入隊是前臺還是後臺隊列
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                            null, -1, -1, false, null, null, AppOpsManager.OP_NONE, null, receivers,
                            null, 0, null, null, false, true, true, -1);
                    // 入隊,並行隊列
                    queue.enqueueParallelBroadcastLocked(r);
                    // 啟動廣播的調度,也就是開始派發廣播
                    queue.scheduleBroadcastsLocked();
                }
            }
            return sticky;
        }
    }

上面主要做瞭幾件事情:

  1. 對caller的判斷
  2. 遍歷action,查詢是否有匹配的sticky廣播
  3. 將本次註冊的廣播放到mRegisteredReceivers中記錄
  4. 如果是sticky廣播,開始派發

5. 總結

本文從App的一次廣播註冊發起開始,到systemserver的註冊流程,整體上流程還是比較簡單的,顧名思義,註冊,正是把要註冊的廣播在systemserver中進行登記,等到這個事件真正來臨的時候,從登記中取出需要被通知的receiver,這也就是後面廣播的派發瞭。
從設計模式的角度講,這正是經典的觀察者模式。

到此這篇關於Android Broadcast原理分析之registerReceiver詳解的文章就介紹到這瞭,更多相關Android Broadcast原理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: