前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 四大组件之一:BroadCastReceiver动态注册广播流程

Android 四大组件之一:BroadCastReceiver动态注册广播流程

作者头像
小柔
发布2022-10-09 09:38:43
7330
发布2022-10-09 09:38:43
举报
文章被收录于专栏:小柔博客园

动态注册广播流程

1.在Activity中动态注册广播时,调用registerReceiver方法,会调用到ContextWrapper的registerReceiver方法:

在这里插入图片描述
在这里插入图片描述

2.这个方法内部,mBase其实就是一个ContextImpl类型的对象,所以会执行ContextImpl类的registerReceiver方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

❝ 经过registerReceiver重载最终会调用到registerReceiverInternal这个方法中,在这个方法中会先将scheduler先赋值为主线程的handler(上面传的scheduler为null,这个Hanlder是后面用来分发ActivityManagerService发送过的广播用的); 接下来创建一个IIntentReceiver类型的对象:如果mPackageinfo不为空调用mPackageInfo.getReceiverDispatcher创建,为空调用LoadedApk.ReceiverDispatcher创建(mPackageInfo是LoadedApk类型的对象),其实IIntentReceiver就是LoadedApk.ReceiverDispatcher.InnerReceiver类型的对象,由于InnerReceiver是继承IIntentReceiver.Stub,所以这个InnerReceiver类型的对象其实就是一个Binder,它后面作为app进程和system_server进程进行通信的桥梁(AMS在收到相应的广播时,就是通过这个Binder对象来通知MainActivity来接收的。)。mPackageInfo是LoadedApk类型的对象,接着先分析LoadedApk类的getReceiverDispatcher方法的具体实现: ❞

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

通过LoadedApk的getReceiverDispatcher方法,可以看出,它里面调用了LoadedApk.ReceiverDispatcher的构造方法,从上面贴出的代码可以看到,这个构造方法里面创建了一个InnerReceiver的对象,并将这个对象赋值给mIIntentReceiver这个变量,后面通过getIIntentReceiver方法获取的就是这个InnerReceiver类型的对象。 小结: 1.==在新建广播接收发布器ReceiverDispatcher时,会在构造函数里面创建一个InnerReceiver实例,这是一个Binder对象,实现了IIntentReceiver接口,可以通过ReceiverDispatcher.getIIntentReceiver函数来获得,获得后就会把它传给ActivityManagerService,以便接收广播。== 2.==在ReceiverDispatcher类的构造函数中,还会把传进来的Handle类型的参数activityThread保存下来,以便后面在分发广播的时候使用。==

❝ LockedApk中有一个mReceivers对象,以Context为key, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>为value。每一个Context都有一个这样的hashmap,key就是receiver,value是ReceiverDispatcher。每一个receiver都对应一个ReceiverDispatcher。 总结:就是创建了一个RD对象(ReceiverDispatcher为了之后AMS和收到广播之后远程调用和可以通过handler插入到消息队列中去处理),这个是在LockedApk中创建的,首先LockedApk有一个mReceivers属性,这个属性里面存放的是key是Context,value是这个上下文中所有注册了receiver的集合,key是receiver,value是RD,这个RD构造方法里面保存了hander以便收到消息之后添加到消息队列中,RD里面有一个静态类InnerReceiver继承自这个类IIntentReceiver.Stub是个代理所以可以通过他进行AMS到Rd中的通信 3.在完成了mPackageInfo.getReceiverDispatcher()创建InnerReceiver对象后,继续调用了AMS的registerReceiver方法 ❞

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

拆开进行分析: (1)首先获取注册的进程信息

在这里插入图片描述
在这里插入图片描述

(2)接着迭代filter的actions进行调用getStickLocked函数查找一下有没有对应的sticky intent列表存在。 sticky intent列表:我们在最后一次调用sendStickyBroadcast函数来发送某个Action类型的广播时,系统会把代表这个广播的Intent保存下来,这样,后来调用registerReceiver来注册相同Action类型的广播接收器,就会得到这个最后发出的广播。这就是为什么叫做Sticky Intent了,这个最后发出的广播虽然被处理完了,但是仍然被粘住在ActivityManagerService中,以便下一个注册相应Action类型的广播接收器还能继承处理。

在这里插入图片描述
在这里插入图片描述

(3)接下来把广播接收器receiver保存一个ReceiverList列表中,这个列表的宿主进程是rl.app,在ActivityManagerService中,用一个进程记录块来表示这个应用程序进程,它里面有一个列表receivers,专门用来保存这个进程注册的广播接收器。接着,又把这个ReceiverList列表以receiver为Key值保存在ActivityManagerService的成员变量mRegisteredReceivers中,这些都是为了方便在收到广播时,快速找到对应的广播接收器的。

在这里插入图片描述
在这里插入图片描述

(4)上面只是把广播接收器receiver保存起来了,但是还没有把它和filter关联起来,这里就创建一个BroadcastFilter来把广播接收器列表rl和filter关联起来,然后保存在ActivityManagerService中的成员变量mReceiverResolver中去。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

❝ 这个方法内部,首先获取了最后一个粘性消息(如果有多个action,取最后一个action的最后一次粘性消息,在最后进行返回这个粘性消息。如果注册的receiver传入的是null只传入了actions,那么会直接返回这个最后的粘性消息),接着通过AMS的本地集合变量mRegisteredReceivers通过receiver(key)获取rl(ReceiverList就是value)获取不到进行创建rl,rl构造方法中传入了进程记录块(callerApp)和Binder调用者的uid和pid,还有receiver。创建完后之后把当前rl添加到rl的进程记录块里面的receivers中,然后将receiver为key,rl为value保存添加到mRegisteredReceivers中; receivers保存完之后开始保存filter,创建一个BroadcastFilter(将filter和rl传入),接着把bf添加到rl中,并把bf添加到AMS本地集合mReceiverResolver中。 这样注册过程就完成了。 ❞

广播发送流程

❝ 广播的发送者将广播发送到ActivityManagerService,ActivityManagerService接收到这个广播以后,就会在自己的注册中心查看有哪些广播接收器订阅了该广播,然后把这个广播逐一发送到这些广播接收器中,但是ActivityManagerService并不等待广播接收器处理这些广播就返回了,因此,广播的发送和处理是异步的。概括来说,广播的发送路径就是从发送者到ActivityManagerService,再从ActivityManagerService到接收者,这中间的两个过程都是通过Binder进程间通信机制来完成的 ❞

1.在Activity中发送广播,sendBroadCast方法其实是调用了ContextWrapper的sendBroadcast方法,ContextWrapper类中的sendBroadcast方法,其实也是调用了ContextImpl的sendBroadcast方法 这里的resolvedType表示这个Intent的MIME类型

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  1. 这个方法中,通过进程间通信的的方式,调用了system_server进程的ActivityManagerService的broadcastIntent方法
在这里插入图片描述
在这里插入图片描述

在里面调用了broadcastIntentLocked方法,拆开进行分析: 3. 首先对Intent进行了处理,如果broadCastReceiver所在进程死亡那么将不会收到该广播

在这里插入图片描述
在这里插入图片描述
  1. 如果广播没有指定特性的接受者的话那么会调用到mReceiverResolver.queryIntent()这个方法中,在上面注册的时候BroadcastFilter实例保存在了ActivityManagerService的成员变量mReceiverResolver中,这个BroadcastFilter实例包含了我们所注册的广播接收器,这里就通过mReceiverResolver.queryIntent函数将这个BroadcastFilter实例取回来。由于注册一个广播类型的接收器可能有多个,所以这里把所有符合条件的的BroadcastFilter实例放在一个List中,然后返回来。
在这里插入图片描述
在这里插入图片描述
  1. 这里是查看一下这个intent的Intent.FLAG_RECEIVER_REPLACE_PENDING位有没有设置,如果设置了的话,AMS就会在当前的系统中查看有没有相同的intent还未被处理,如果有的话,就有当前这个新的intent来替换旧的intent。
在这里插入图片描述
在这里插入图片描述
  1. 这个if语句首先创建一个广播记录块BroadcastRecord,里面记录了这个广播是由谁发出的以及要发给谁等相关信息。由于前面得到的replacePending变量为false,这里得到的replaced变量的值也为false,于是执行下面的if语句,没有替换时,才需要将新的广播加入到BroadcastQueue.mParallelBroadcasts队列中,等待进一步处理;进一步处理的操作由函数scheduleBroadcastsLocked进行。处理完成之后会把NR重置
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

❝ 上面分析的是动态广播(NR>0因为是在动态regist的时候会把bf添加到mReceiverResolver这个AMS本地变量中,如果是静态的是查询不到mReceiverResolver变量里面的bf),接下来分析静态注册的广播,静态注册的广播receivers不为null。 ❞

小结:

1.先获取动态注册的广播和静态注册的广播,并将这些广播分别存储到两个不同的列表中

2.判断是否是发送的普通广播,如果是,并且动态注册的广播,则先将动态注册的广播发送, 走后续的广播发送流程,接着判断是否有静态广播,如果有静态广播,则发送静态广播。(从源码代码逻辑可以知道,普通广播的发送,是动态广播优先静态广播发送)如果发送的是有序广播,则会将第一步的两个列表合并到receivers列表中,并按照优先级对广播进行排序,具体的排序规则是,优先级高的排前面,对于相同优先级的,动态优先静态,对于优先级相同,广播类型相同,如果都是动态广播类型,则先注册的优先后注册的,对于都是静态广播类型,则先扫描的由于后扫描的。

3.将广播将第二步的广播列表,添加到BroadcastQueue的mParallelBroadcasts或者mOrderedBroadcasts中,如果广播按照普通方式发送,则将广播存储在mParallelBroadcasts列表中,如果是静态广播存储在mOrderedBroadcasts列表中,如果发送方式是有序广播,则将所有的广播(不管是静态注册的还是动态注册的)都存储在mOrderedBroadcasts列表中。

在这里插入图片描述
在这里插入图片描述
  1. 动态广播:通过调用BroadcastQueue的enqueueOrderedBroadcastLocked方法将发送的广播存储在BroadcastQueue的mParallelBroadcasts列表中,然后通过scheduleBroadcastsLocked方法发送广播。

❝ 这里的mBroadcastsScheduled表示ActivityManagerService当前是不是正在处理其它广播,如果是的话,这里就先不处理直接返回了,保证所有广播串行处理。注意这里处理广播的方式,它是通过消息循环来处理,每当AMS接收到一个广播时,它就把这个广播放进自己的消息队列去就完事了,根本不管这个广播后续是处理的,因此,这里我们可以看出广播的发送和处理是异步的。这里的成员变量mHandler是一个在ActivityManagerService内部定义的Handler类变量,通过它的sendEmptyMessage函数把一个类型为BROADCAST_INTENT_MSG的空消息放进AMS的消息队列中去。这里的空消息是指这个消息除了有类型信息之外,没有任何其它额外的信息,因为前面已经把要处理的广播信息都保存在mParcelBroadcasts中了,等处理这个消息时,从mParcelBroadcasts就可以读回相关的广播信息了,因此,这里不需要把广播信息再放在消息内容中。 ❞

在这里插入图片描述
在这里插入图片描述
  1. 接下来分析processNextBroadcast函数,这个函数有点长进行分步分析
在这里插入图片描述
在这里插入图片描述

(8.1)这里传进来的参数fromMsg为true,于是把mBroadcastScheduled重新设为false,这样,下一个广播就能进入到消息队列中进行处理了。

在这里插入图片描述
在这里插入图片描述

(8.2)无序广播,并调用deliverToRegisteredReceiverLocked方法发送无序广播

在这里插入图片描述
在这里插入图片描述

(8.3)如果是发送的有序广播,则判断广播所属的进程是否存在,并且进程未被kill,则执行processCurBroadcastLocked处理后续的流程,如果广播所属的进程不存在,则新建一个进程,并重复广播的发送过程,这些情况的后续流程。如果只是发送普通股广播,并且广播就所属当前进程,那么直接执行deliverToRegisteredReceiverLocked方法

在这里插入图片描述
在这里插入图片描述

(8.4)deliverToRegisteredReceiverLocked方法中,又会调用到performReceiveLocked方法,performReceiveLocked方法中,会调用app.thread.scheduleRegisteredReceiver方法,app.thread其实是ApplicationThread类型的对象,这个在之前就分析过,代码的执行又回到了ApplicationThread类中 前面bt中app属性保存的就是注册广播的进程,receiver属性保存的是对应的所有广播接收器。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

(8.5)这个receiver就是上面ASM注册广播的时候传进来的LockedApk中的InnerReceiver

在这里插入图片描述
在这里插入图片描述

(8.6)在注册的时候rd会在构造方法中创建InnerReceiver,并把当前rd传入到InnerReceiver的构造函数中,并用弱引用保存rd

在这里插入图片描述
在这里插入图片描述

(8.7)把消息放在消息队列中,然后就返回了,这个消息最终会在传进来的Runnable类型的参数的run成员函数中进行处理。

在这里插入图片描述
在这里插入图片描述

(8.8)分发到了接收器的onReceiver方法中

在这里插入图片描述
在这里插入图片描述

参考链接: https://blog.csdn.net/luoshengyang/article/details/6730748

https://blog.csdn.net/luoshengyang/article/details/6737352

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 动态注册广播流程
  • 广播发送流程
  • 小结:
相关产品与服务
消息队列 CMQ 版
消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档