Android源碼解析四大組件系列(五)---廣播的注冊過程

廣播這個篇幅打算用四篇文章來寫,分別為廣播注冊、廣播處理、廣播的發送,廣播深入細節理解,如果都寫到一篇文章會比較長,所以拆分成四篇來寫。

第一篇
Android源碼解析---廣播的注冊過程
第二篇
Android源碼解析---廣播的處理過程
第三篇
Android源碼解析---廣播的發送過程
第四篇
Android源碼解析---廣播深入細節理解

想收到廣播(Broadcast),必須先要注冊接收廣播的組件---廣播接收者(receiver),廣播接收者的注冊分為動態注冊和靜態注冊,而注冊中心就是AMS,AMS再把廣播分發到各個廣播接收者(receiver)。

image.png

一個廣播可以有多個receiver來接收它,注冊的方式分為兩種,一種是靜態注冊,一種是動態注冊,動態注冊廣播不是常駐型廣播,也就是說廣播跟隨Activity的生命周期,在Activity結束前,需要移除廣播接收器。 靜態注冊是常駐型,也就是說當應用程序關閉后,如果有信息廣播來,程序也會被系統調用自動運行。

1.1 動態廣播注冊

動態注冊是由ContextImpl的registerReceiver方法調用registerReceiverInternal來注冊的

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) {
                 //AMS并不是直接給廣播接收者發送廣播的,當廣播到達應用程序進程的時候,
      //會被封裝成一個Message,然后push到主線程消息隊列中,然后才會給接
      //收者處理,你也可以指定一個處理的Handler,將onReceive()調度在非主線程執行。
                   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 {
         //將rd,filter等發送給AMS
           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();
       }
   }

傳進來的receiver不是直接發送給AMS的,首先會把receiver封裝成一個IIntentReceiver對象rd,這個rd是一個binder本地對象,具備了跨進程通信的能力。mPackageInfo是LoadedApk,LoadedApk這個類包含了當前加載的apk的主要的信息,其中成員變量mReceivers表就記錄了所有動態注冊的receiver。

private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
      = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();

rd的獲取有兩種,當mPackageInfo存在時候,就通過mPackageInfo.getReceiverDispatcher()來獲取。

   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;
        //registered傳進來的是true
           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 {
           //檢查廣播分發者的context、handler是否一致
               rd.validate(context, handler);
           }
           rd.mForgotten = false;
           return rd.getIIntentReceiver();
       }
   }

這個方法內部維護了一張表 ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null,每一個廣播接收者對應一個ReceiverDispatcher(廣播分發者),并且把最后把這個map以Context為key存在mReceivers中,而這個Context跟廣播的發送方有關系,如果是在Activity中發送的,這個Context就指向與這個Activity,如果是在Service中發送的,這個Context就指向了這個Service。那么這個廣播分發者有什么用呢?用map來存儲,表示ReceiverDispatcher跟BroadcastReceiver是一一對應的,每個廣播接收者對應一個廣播分發者, 當AMS向app發送廣播時會調用到app進程的廣播分發者,然后再將廣播以message形式post到app的主線程,來執行onReceive()方法。

假設我的app只有兩個Activity(HomeActivity和DetailActivity),這個app被打包成xxx.apk,那么在內存中,這個xxx.apk由LoadedApk來描述,如果HomeActivity和DetailActivity都注冊了廣播,那么LoadedApk內部維持的mReceivers的長度就為2。現在梳理一下上面的代碼,當一個BroadcastReceiver要注冊的時候,會優先使用Context查看一下,這個組件有沒有注冊過廣播,如果有就取出來,類型是一個ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> 的map。如果沒有,就把map創建出來,并且存到mReceivers中。有了map之后,需要把ReceiverDispatcher(廣播分發者)存到map里面去。第一次注冊時候,肯定走的是if(rd==null)的代碼塊里面,這樣rd對象被創建出來。現在分析一下ReceiverDispatcher這個類。

static final class ReceiverDispatcher {

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

       .......

       final IIntentReceiver.Stub mIIntentReceiver;
       final BroadcastReceiver mReceiver;
       final Context mContext;
       final Handler mActivityThread;
       .......
       ReceiverDispatcher(BroadcastReceiver receiver, Context context,
               Handler activityThread, Instrumentation instrumentation,
               boolean registered) {
           if (activityThread == null) {
               throw new NullPointerException("Handler must not be null");
           }
           mIIntentReceiver = new InnerReceiver(this, !registered);
           //廣播接收者
           mReceiver = receiver;
           //表示哪個發送的廣播
           mContext = context;
           //主線程
           mActivityThread = activityThread;
            .......
       }
       .......
       IIntentReceiver getIIntentReceiver() {
           return mIIntentReceiver;
       }
      .......
   }

在內部會創建InnerReceiver,InnerReceiver是ReceiverDispatcher的內部類,是一個實現Binder的本地對象,前面也說過了,最終是將一個InnerReceiver對象注冊到了AMS中。

OK,繞了這么一大圈子,其實就是為了封裝一個InnerReceiver用于和AMS通信,我也不知道谷歌這幫程序員怎么想的,有點麻煩。忽略跨進程的代碼,現在由用戶進程走到SystemServer進程了,即走到AMS的registerReceiver方法。

public Intent registerReceiver(IApplicationThread caller, String callerPackage,
           IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
       enforceNotIsolatedCaller("registerReceiver");
       ArrayList<Intent> stickyIntents = null;
       ProcessRecord callerApp = null;
       int callingUid;
       int callingPid;
       synchronized(this) {
           if (caller != null) {
             //由caller獲取當前進程對象
               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 != Process.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();
           }

           userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
                   ALLOW_FULL_ONLY, "registerReceiver", callerPackage);

          //獲取IntentFilter中的action
           Iterator<String> actions = filter.actionsIterator();
           if (actions == null) {
               ArrayList<String> noAction = new ArrayList<String>(1);
               noAction.add(null);
               actions = noAction.iterator();
           }

           //從actions中,先把粘性廣播帥選出來,放進stickyIntents中
           int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
           while (actions.hasNext()) {
               String action = actions.next();
               for (int id : userIds) {
               //從mStickyBroadcasts中查看用戶的sticky Intent,mStickyBroadcasts存了系統所有的粘性廣播
                   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;
       if (stickyIntents != null) {
           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);
               // 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);
               }
           }
       }

       // The first sticky in the list is returned directly back to the client.
       Intent sticky = allSticky != null ? allSticky.get(0) : null;
       if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
       //如果receiver為空,就直接返回了
       if (receiver == null) {
           return sticky;
       }

       synchronized (this) {
           if (callerApp != null && (callerApp.thread == null
                   || callerApp.thread.asBinder() != caller.asBinder())) {
               // 進程不存在(死亡了),也是不能注冊成功的
               return null;
           }
          //mRegisteredReceivers表存了所有動態注冊的廣播接收者,
     //由receiver作為key,獲取到ReceiverList,為什么是ReceiverList,
    //而不是一個Receiver呢,因為一個廣播可能會有多個接收者,
     //最好整成一個隊列或者鏈表的形式,而ReceiverList繼承ArrayList,滿足這個需求。
     //每個ReceiverList都對應著Client端的一個ReceiverDispatcher。
           ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
           if (rl == null) {
               rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                       userId, receiver);
               if (rl.app != null) {
                   //把廣播接收者列表加到這個進程對象的receivers中
                   rl.app.receivers.add(rl);
               } else {
                   try {
                       //進程不存在,注冊死亡通知
                       receiver.asBinder().linkToDeath(rl, 0);
                   } catch (RemoteException e) {
                       return sticky;
                   }
                   rl.linkedToDeath = true;
               }
               //新創建的接收者隊列,添加到已注冊廣播隊列。
               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);
           } else if (rl.pid != callingPid) {
               throw new IllegalArgumentException(
                       "Receiver requested to register for pid " + callingPid
                       + " was previously registered for pid " + rl.pid);
           } else if (rl.userId != userId) {
               throw new IllegalArgumentException(
                       "Receiver requested to register for user " + userId
                       + " was previously registered for user " + rl.userId);
           }
         //在AMS內部,廣播接收者實際上是BroadcastFilter來描述的,
    //由filter等參數創建BroadcastFilter對象,并添加到接收者隊列,
    //注意只有registerReceiver()過程才會創建BroadcastFilter,也就是該對
    //象用于動態注冊的廣播Receiver;,靜態的接收者對象不是BroadcastFilter。
           BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                   permission, callingUid, userId);
           rl.add(bf);
           if (!bf.debugCheck()) {
               Slog.w(TAG, "==> For Dynamic broadcast");
           }
           mReceiverResolver.addFilter(bf);

           //如果是粘性廣播,創建BroadcastRecord,并添加到
   //BroadcastQueue的并行廣播隊列(mParallelBroadcasts),
    //注冊后調用AMS來盡快處理該廣播。
           if (allSticky != null) {
               ArrayList receivers = new ArrayList();
               receivers.add(bf);

               final int stickyCount = allSticky.size();
               for (int i = 0; i < stickyCount; i++) {
                   Intent intent = allSticky.get(i);
                   BroadcastQueue queue = broadcastQueueForIntent(intent);
                   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);
                   queue.enqueueParallelBroadcastLocked(r);
                   queue.scheduleBroadcastsLocked();
               }
           }

         //返回值是一個Intent
           return sticky;
       }
   }

總結一下:動態注冊是調用registerReceiver來注冊的,大致流程如下:

在Android系統中,系統每加載一個apk,就會有一個LoadedApk對象。而每個LoadedApk對象里會有一張名字為mReceivers的HashMap,用來記錄每個apk里面動態注冊了那些廣播接收者。mReceivers的類型是ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>>,泛型中的Context代表是誰注冊的,后面的值也是個map ,ArrayMap<BroadcastReceiver, ReceiverDispatcher>表示BroadcastReceiver與ReceiverDispatcher一一對應,一個BroadcastReceiver對應一個ReceiverDispatcher,通過這個定義可以知道,不管一個Activity注冊了多少BroadcastReceiver,ReceiverDispatcher只有一個。ReceiverDispatcher內部有一個InnerReceiver的Binder對象,最終是把這個InnerReceiver發送給了AMS,AMS內部也維護了一張表mRegisteredReceivers,用來記錄所有動態注冊的接收者,首先會根據傳進來的InnerReceiver對象取出來一個ReceiverList,ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder()),所以實質上每個ReceiverList都對應著Client端的一個ReceiverDispatcher。最后把創建的對象BroadcastFilter(AMS內部廣播接收者用BroadcastFilter來表示)加到ReceiverList接收者隊列中,注冊完畢!

1.2 靜態廣播注冊

靜態注冊就是在manifest中注冊。

<receiver android:name=".MyReceiver">
   <intent-filter>
       <action android:name="android.intent.action.MY_BROADCAST"/>
       <category android:name="android.intent.category.DEFAULT" />
   </intent-filter>
</receiver>

它們的信息會在系統啟動時,由PackageManagerService(PMS)解析(在該類的構造方法中會對各個應用安裝目錄的apk文件進行掃描解析)并記錄下來。

if (tagName.equals("activity")) {
               Activity a = parseActivity(owner, res, parser, flags, outError, false,
                       owner.baseHardwareAccelerated);
               if (a == null) {
                   mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                   return false;
               }
               owner.activities.add(a);
           } else if (tagName.equals("receiver")) {
               Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
               if (a == null) {
                   mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                   return false;
               }
               owner.receivers.add(a);
           } else if (tagName.equals("service")) {
               Service s = parseService(owner, res, parser, flags, outError);
               if (s == null) {
                   mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                   return false;
               }
               owner.services.add(s);
           } else if (tagName.equals("provider")) {
               Provider p = parseProvider(owner, res, parser, flags, outError);
               if (p == null) {
                   mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                   return false;
               }
               owner.providers.add(p);
           }

經過上面的解析receiver就被保存到了owner.receivers中去了。然后AM會調用PMS的接口來查詢“和intent匹配的組件”時,PMS內部就會去查詢當初記錄下來的數據,并把結果返回AMS。

 List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
                       .queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
@Override
   public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
           String resolvedType, int flags, int userId) {
       return new ParceledListSlice<>(
               queryIntentReceiversInternal(intent, resolvedType, flags, userId));
   }

   private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
           String resolvedType, int flags, int userId) {
       if (!sUserManager.exists(userId)) return Collections.emptyList();
       flags = updateFlagsForResolve(flags, userId, intent);
       ComponentName comp = intent.getComponent();
       if (comp == null) {
           if (intent.getSelector() != null) {
               intent = intent.getSelector();
               comp = intent.getComponent();
           }
       }
       if (comp != null) {
           List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
           ActivityInfo ai = getReceiverInfo(comp, flags, userId);
           if (ai != null) {
               ResolveInfo ri = new ResolveInfo();
               ri.activityInfo = ai;
               list.add(ri);
           }
           return list;
       }

       // reader
       synchronized (mPackages) {
           String pkgName = intent.getPackage();
           if (pkgName == null) {
               return mReceivers.queryIntent(intent, resolvedType, flags, userId);
           }
           final PackageParser.Package pkg = mPackages.get(pkgName);
           if (pkg != null) {
               return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
                       userId);
           }
           return Collections.emptyList();
       }
   }

因為涉及PMS,這段邏輯想寫清楚篇幅會比較大,所以,不深入討論,以上關于廣播的動態注冊和靜態注冊就介紹完了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容