转载自 牛晓伟
本文摘要
通过本文您将了解广播的发送接收流程,广播是如何发送的,如何收集接收者,广播如何入队以及入哪个队,有序广播是如何发送的,超时机制,是如何接收广播的等等。
注:AMS是ActivityManagerService的简称,PMS是PackageManagerService的简称
本文大纲
本文大纲
6. 广播发送接收流程
在一文彻底搞懂Android广播的所有知识 (上)文中分别从广播发送者、广播接收者、广播分发中心三方面介绍了广播机制,而广播发送接收流程就是从一个整体的角度来把它们串联起来,看它们之间是如何配合来保证广播的发送和接收的。
同样我也绘制了一幅图如下:
image
图解
上图展示了广播发送和接收的流程,其实整个流程的图要比上面的复杂多了,我一贯遵循先简后难的原则,该流程大致分为发送广播、收集、入队、分发、接收广播这五个步骤,那就从这五个步骤来介绍下广播是如何发送的?广播又是如何被接受的?
6.1 发送广播
在一文彻底搞懂Android广播的所有知识 (上)文中介绍过广播按是否有序分为有序广播和无序广播;广播按是否是后台分为后台广播和前台广播;当然以前广播还有sticky广播粘性广播,但是该广播由于安全原因已经被废弃了,故不再赘述。
而广播发送者调用Context的sendBroadcastXXX和sendOrderedBroadcastXXX方法时可以发送无序广播和有序广播。在发送广播时为Intent对象增加Intent.FLAG_RECEIVER_FOREGROUND属性即可发送前台广播,不加则发送后台广播。
发送广播调用的是ActivityManager的broadcastIntentWithFeature方法,而该方法是一个binder调用,最终会调用到AMS的broadcastIntentWithFeature方法。也就是广播发送者就是一个“甩手掌柜”,它只需要把发送的广播数据 (广播数据存放于Intent对象中) 通过binder通信传递给广播分发中心,那接下来的“重任”就落在了广播分发中心“身上”了。
6.2 收集、入队
收集、入队很明显这是两个过程,先有收集,后才有入队,那就先从收集讲起。
6.2.1 收集
大家可以想一想,一些比较复杂的观察者模式例子,比如鼎鼎有名的EventBus,发送者发送了一个event事件后,要做的第一件事就是把观察该event的观察者收集起来,其次在把event事件分发给这些观察者们。
同样广播分发中心在收到一个广播数据后,要做的第一件事情就是收集,收集就是把观察该广播的所有接收者 (也可以称为注册信息) 收集起来。
收集receiver的过程分为收集静态接收者和收集动态接收者。
收集静态接收者
静态接收者指的是在AndroidManifest文件中声明的BroadcastReceiver,而收集静态接收者就是从PMS根据Intent对象中的action等信息把所有的BroadcastReceiver都收集起来,当然收集静态接收者有个前提就是发送者没有指定只把广播发送给动态接收者。每一个静态接收者信息是存放在ResolveInfo对象中的,如下是它的相关属性:
| 属性 | 说明 |
|---|---|
| activityInfo:ActivityInfo | activityInfo就代表的是配置在AndroidManifest中的BroadcastReceiver信息 |
| filter:IntentFilter | filter对应了在AndroidManifest中配置的BroadcastReceiver的intent-filter信息,比如action |
如下是相关的代码:
//ActivityManagerService
final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
@Nullable String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions,
String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid,
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList) {
省略代码······
List receivers = null;
//没有指定只有动态广播接收者接收广播
if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
//从pms中收集所有的接受者,接收者是一个ResolveInfo列表
receivers = collectReceiverComponents(
intent, resolvedType, callingUid, users, broadcastAllowList);
}
省略代码······
}
收集动态接收者
动态接收者即所谓的通过代码的方式注册的广播接收者,因为在注册模块中存储了所有的注册信息,因此收集动态接收者就是从注册模块中根据Intent对象的action信息把所有的接收者收集起来。在一文彻底搞懂Android广播的所有知识 (上)文中介绍过注册模块以及注册信息的存放。每个动态接收者信息是存放在BroadcastFilter对象中的,那我再带大家来回顾下BroadcastFilter类的主要属性:
BroadcastFilter
还是看上面类图,BroadcastFilter继承了IntentFilter类,该类存储了广播相关的信息,下面罗列了它的几个关键属性:
| 属性 | 说明 |
|---|---|
| receiverList:ReceiverList | receiverList存储了类型为IIntentReceiver的receiver以及进程相关信息 |
| packageName:String | 包名信息 |
而广播的action信息存储在它的父类的mActions属性中,如下:
| 属性 | 说明 |
|---|---|
| mActions:ArrayList | mActions存储了所有的action |
也就是说一个BroadcastFilter对象是可以包含多个广播的,而通过BroadcastFilter对象的receiverList属性找到ReceiverList对象。那再来看下ReceiverList类。
ReceiverList
该类的主要作用就是把注册的信息都存储起来,下面列出了该类的几个关键属性:
| 属性 | 说明 |
|---|---|
| receiver:IIntentReceiver | receiver可以理解为是BroadcastReceiver的“代理人”,receiver如果是App进程传递过来它的类型是BinderProxy;如果是systemserver进程传递过来它的类型是Binder |
| app:ProcessRecord | app对应App进程的进程信息,如进程id、进程状态等 |
| pid:int | pid代表App进程的进程id |
| uid:int | uid对应App的唯一id |
小结下BroadcastFilter
一个BroadcastFilter对象可以包含多个action,一个action对应一个广播,可以通过BroadcastFilter对象的receiverList属性,找到远端BroadcastReceiver对象的“代理人”receiver,它的类型为IIntentReceiver是一个匿名binder服务,请记住这个 “代理人”receiver可是非常重要。
如下是收集动态接收者的相关代码:
//ActivityManagerService
final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
@Nullable String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions,
String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid,
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList) {
省略代码······
List<BroadcastFilter> registeredReceivers = null;
if (intent.getComponent() == null) {
final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
//不走着
if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
省略代码······
} else {
//从mReceiverResolver对象的queryIntent方法中根据intent中的action,查找所有的BroadcastFilter
registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
resolvedType, false /*defaultOnly*/, userId);
}
}
小结
收集阶段会把观察该广播的所有动态接收者和所有静态接收者全部收集起来,每一个动态接收者是存放于BroadcastFilter对象,每一个静态接收者是存放于ResolveInfo对象,所有的动态接收者存放于类型为List的registeredReceivers变量中,所有静态接收者存放于类型为List的receivers变量中。
既然广播相关的接收者都已经收集起来了,那接下来介绍下入队。
6.2.2 入队
为啥要“入队”呢?其实广播分发中心就是一个消费者/生产者模式,因为需要分发的广播非常多,因此需要被分发的广播是需要放入队列中的,而在分发广播时,再从队列中取出广播进行分发。因此入队就是把广播放入队列中,那这里提到的广播到底是啥呢?
这里提到的广播就是BroadcastRecord对象,BroadcastRecord对象来广播信息和接收者等信息封装起来 (在一文彻底搞懂Android广播的所有知识 (上)文中介绍过BroadcastRecord),进而把BroadcastRecord对象放入队列中。那这里的队列到底是啥呢?在揭开谜底之前还是先来回顾下BroadcastRecord类吧。
队列元素
BroadcastRecord作为队列的元素,这个类的作用主要是用来记录一个广播及其他信息,先看下该类的主要属性:
| 属性 | 说明 |
|---|---|
| intent:Intent | 广播发送者发送广播时的intent信息,广播信息以及参数都在intent对象中 |
| callerApp:ProcessRecord | 广播发送者的进程信息,比如pid、进程状态等 |
| callerPackage:String | 广播发送者的包名 |
| callingPid:int | 广播发送者的进程id |
| callerPackage:String | 广播发送者的包名 |
| callingPid:int | 广播发送者的进程id |
| ordered:boolean | 广播是否是有序广播 |
| sticky:boolean | 广播是否是粘性广播 |
| receivers:List | 广播的接收者信息,接收者既包含动态广播接收者也包含静态广播接收者 |
| delivery:int[] | 对应receivers,每个广播接收者的状态 |
| duration:long[] | 对应receivers,每个广播接收者从接收到处理完广播所花的时间 |
| deferred:boolean | 该BroadcastRecord是否被延迟 |
| enqueueTime:long | 该BroadcastRecord对象入队列的时间,所有的BroadcastRecord是需要放入队列中,才能被发送 |
| dispatchTime:long | 开始分发该广播的时间 |
| timeoutExempt:boolean | 是否豁免超时机制 |
| nextReceiver:int | 下一个广播接收者的索引值,因为所有的广播接收者都是放在List中的 |
| anrCount:int | 发生anr的次数 |
| curFilter:BroadcastFilter | 当前的动态广播接收者 |
| curApp:ProcessRecord | 当前广播接收者对应的进程信息 |
其中receivers属性是List类型的,该属性存放了该广播的所有接收者,请注意该属性在后面会用到它。回顾完BroadcastRecord来看下都有哪些队列吧。
都有哪些队列
在一文彻底搞懂Android广播的所有知识 (上)文中介绍过广播分发中心中有有序广播队列和无序广播队列两种类型队列,这只是按队列是否存放有序还是无序广播划分的,还有另外一种划法按分发广播模块种类划分。同样在一文彻底搞懂Android广播的所有知识 (上)文中介绍过因为有序广播的分发会出现延迟,导致后面的广播分发也会被延迟,因此对分发广播模块进行划分,作为专模块专用的目的,分发广播模划分为offload分发广播模块、后台广播分发模块、前台广播分发模块,而每个分发模块都有自己的有序广播队列和无序广播队列。
我同样绘制了一幅图:
image
因此在入队之前先要选择是哪个分发广播模块,默认情况下是选用后台分发广播模块,如果发送者发送的是前台广播,则会选用前台分发广播模块。至于offload分发模块不是为普通App使用的,故不赘述。
下面是相关代码:
//ActivityManagerService
BroadcastQueue broadcastQueueForIntent(Intent intent) {
if (isOnFgOffloadQueue(intent.getFlags())) {
return mFgOffloadBroadcastQueue;
}
if (isOnBgOffloadQueue(intent.getFlags())) {
return mBgOffloadBroadcastQueue;
}
//intent中的flags包含FLAG_RECEIVER_FOREGROUND则认为是 前台分发广播模块
final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
//isFg为true,则是 前台广播模块 否则是 后台广播模块
return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
}
分发广播模块选定了,接下来该看到底是入队到有序广播队列还是无序广播队列,那我就从有序广播和无序广播分别来介绍下吧。
有序广播
对于发送者发送的是有序广播,不管广播的接收者是动态的还是静态的,构造的BroadcastRecord对象都是入队到有序广播队列,入队到有序广播的BroadcastRecord对象可是有一个特别要注意的地方:BroadcastRecord的receivers属性中存放的所有接收者是有优先级一说的。
这也很好理解,因为有序广播这里的有序指的是接收者是按一个一个的顺序来接收广播的,凡是有了顺序那就会存在特权或者优先级一说,比如在现实生活中排队购买火车票,军人和老人的优先级要高于普通人,它们可以优先购买。而对于广播的接收者来说也存在优先级一说,谁的优先级最高谁就会最先收到广播。
首先接收者的优先级是可以配置的,对于静态接收者的优先级是在AndroidManifest中配置的,如下代码:
//AndroidManifest.xml
<receiver android:name=".MyReceiver" android:exported="true">
## 通过priority 把优先级设置为100
<intent-filter android:priority="100">
<action android:name="com.niu.broadcast.demo"/>
</intent-filter>
</receiver>
对于动态接收者的优先级是在注册时进行设置的,如下代码:
IntentFilter intentFilter = new IntentFilter("com.niu.broadcast.demo");
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
//设置接收者的优先级
intentFilter.setPriority(100);
registerReceiver(br,intentFilter);
如果动态接收者优先级与静态接收者优先级相同,那该如何排序呢?
如果相同,则动态接收者要排于静态接收者之前,这也很好理解,动态接收者对应的App进程肯定是活着的,而静态接收者对应的进程不一定活着,没活着就会存在启动对应进程的过程,因此遵循活着的优先原则。
基于以上原则可以得出如下图:
image
如上图,BroadcastRecord对象中的receivers,接收者优先级最高,该接收者排在最前面,如果动态接收者优先级与静态接收者优先级相同,则动态接收者要排于静态接收者之前。
题外话:对于有序广播,如果想要让接收者优先接收到广播,则把接收者的优先级提高。
下面是有关优先级排序的代码:
//ActivityManagerService
final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
@Nullable String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions,
String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid,
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList) {
省略代码······
if (receivers != null) {
省略代码······
int NT = receivers != null ? receivers.size() : 0;
int it = 0;
ResolveInfo curt = null;
BroadcastFilter curr = null;
//合并ResolveInfo和BroadcastFilter,
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++;
}
省略代码·····
}
无序广播
对于发送者发送的无序广播,需要根据动态接收者和静态接收者,来分别构造不同的BroadcastRecord对象,存放于不同的队列。啥无序广播尽然这么复杂吗?
是的,对于所有的动态接收者,需要构建BroadcastRecord对象,并且把它放入无序广播队列,而对于所有的静态接收者,需要构建BroadcastRecord对象,并且把它放入有序广播队列。这样做的目的还是因为无序广播对于接收者来说是无序的,并且要求所有的接收者的进程都必须活着。因此基于此就有了上面的结果。
下面是无序广播入队的相关代码:
//ActivityManagerService
final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
@Nullable String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions,
String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid,
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList) {
省略代码······
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;//niu registeredReceivers动态注册的接收器
if (!ordered && NR > 0) {//niu 非有序广播,并且存在动态广播接收器
// If we are not serializing this broadcast, then send the
// registered receivers separately so they don't wait for the
// components to be launched.
if (isCallerSystem) {
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, registeredReceivers);
}
final BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered,
sticky, false, userId, allowBackgroundActivityStarts,
backgroundActivityStartsToken, timeoutExempt);
省略代码······
if (!replaced) {
//把r放入无序广播队列
queue.enqueueParallelBroadcastLocked(r);
//通知发送广播
queue.scheduleBroadcastsLocked();
}
registeredReceivers = null;
NR = 0;
}
省略代码······
}
6.2.3 小结
收集、入队分为收集和入队两个步骤。
收集就是根据广播的action把所有的静态接收者和动态接收者都收集起来。入队就是创建BroadcastRecord对象把广播信息及接收者等信息封装起来,放入队列中 ,而具体是放入哪个分发广播模块**的哪个队列中,完全与发送者发送的广播是前台广播还是后台广播,或者是有序广播还是无序广播有关。
既然BroadcastRecord对象被放入了队列,可不要天真的认为BroadcastRecord对象立马就会被取出来进行分发的,因为在队列中还有很多的BroadcastRecord对象需要被分发,那就只能默默的等待了。
6.3 分发广播
我绘制了一幅分发广播的简单图:
image
图解
如上图,每一轮都是先进行发送所有无序广播,在进行发送有序广播,接着在继续下一轮,就这样循环下去。因为发送所有无序广播已经在一文彻底搞懂Android广播的所有知识 (上)文中详细的介绍过了,在这就不在赘述了。再次详细的介绍下发送有序广播。
6.3.1 发送有序广播
发送有序广播可是比发送所有无序广播复杂的多多了,发送所有无序广播就是把无序广播队列中的所有广播发送出去即可,并且所有的广播接收者都是动态接收者,也就是它们所在的进程都活着。而发送有序广播多么的复杂,请看下图便知:
image
图解
如上图展示了一轮发送有序广播的流程,该轮结束后接着进入下一轮,就这样依次不断地循环下去。那就结合上图来详细的介绍下发送有序广播吧。
注:以下流程涉及的相关代码都是在BroadcastQueue的processNextBroadcastLocked方法
1. 是否有挂起的广播
先说下为啥有此过程吧,在有序广播的接收者中会存在静态接收者,而静态接收者会存在一种情况就是接收者对应的App进程是没启动的,针对此情况就需要先把接收者对应的App进程启动,而这个启动过程是比较耗时的,即使耗时也必须等待,等待该App进程对应的接收者处理了该广播,在此等待期间,是不能分发其他的有序广播的。而处于以上状态的广播 (BroadcastRecord) 就处于挂起状态。
下面是相关代码:
//以下代码位于BroadcastQueue的processNextBroadcastLocked方法
//挂起的BroadcastRecord对象会赋值给mPendingBroadcast
if (mPendingBroadcast != null) {
boolean isDead;
if (mPendingBroadcast.curApp.getPid() > 0) {
synchronized (mService.mPidsSelfLocked) {
ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.getPid());
isDead = proc == null || proc.mErrorState.isCrashing();
}
} else {
final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
isDead = proc == null || !proc.isPendingStart();
}
//对应的app进程没有死掉,返回等待接收者反馈结果
if (!isDead) {
// It's still alive, so keep waiting
return;
} else {
//对应的app进程死掉了,则忽略该接受者,把广播发送给下个接收者
mPendingBroadcast.state = BroadcastRecord.IDLE;
//下个接收者
mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
mPendingBroadcast = null;
}
}
如上代码,如果存在挂起广播并且对应进程没有死掉,则只能继续等待,有序广播队列中的所有广播都处于等待状态。但是无序广播还是可以正常发送的。
2. 获取有序BroadcastRecord
在一文彻底搞懂Android广播的所有知识 (上)文中介绍过需要调用BroadcastDispatcher对象的getNextBroadcastLocked方法获取有序BroadcastRecord对象,BroadcastDispatcher对象其中包含了有序广播队列。关于更具体的获取过程,请看此文
2.1 是否为null
就是判断获取的BroadcastRecord对象是否为null,为null表明有序广播队列中没有任何BroadcastRecord,直接结束本轮即可;否则进入下一步骤。
2.2 状态是否不为IDLE
每一个BroadcastRecord对象都有一个state属性,它表明了当前BroadcastRecord对象所处的状态,对应的状态有IDLE、APP_RECEIVE、CALL_IN_RECEIVE、CALL_DONE_RECEIVE等,当state属性不为IDLE时,表明当前BroadcastRecord对象正在处于忙碌中,比如等待接收者发送反馈消息,这时候结束本轮,有序广播队列中的所有广播都处于等待状态;否则进入下一步骤。
相应代码如下:
//以下代码位于BroadcastQueue的processNextBroadcastLocked方法
//广播的状态不等于IDLE,则返回,比如该r正在等待一BroadcastReceiver处理完毕的返回
if (r.state != BroadcastRecord.IDLE) {
return;
}
2.3 是否分发完毕
是否分发完毕是指当前的BroadcastRecord对象的所有接收者是否都接收完词广播了,是的话就需要做相应的处理,比如把该BroadcastRecord对象放入历史记录中,取消超时机制,把该BroadcastRecord对象从有序广播队列中移除,再次获取下一个有序BroadcastRecord;否则进入下一步骤。
2.4 是否是第一次分发
是否是第一次分发是指当前的BroadcastRecord对象是不是第一次分发,是的话就需要设置该BroadcastRecord对象的一些属性,并且进入下一步;否则也需要进入下一步。
下面是相关代码:
//以下代码位于BroadcastQueue的processNextBroadcastLocked方法
//recIdx为0代表是第一次分发该广播
if (recIdx == 0) {
//把BroadcastRecord对象的各种dispatch属性,也就是分发时间 记录下来
r.dispatchTime = r.receiverTime;
r.dispatchRealTime = SystemClock.elapsedRealtime();
r.dispatchClockTime = System.currentTimeMillis();
省略其他代码······
}
3. 尝试启动超时机制
启动超时机制前面为啥要加尝试俩字呢,其原因是如果已经启动了超时机制,则没必要启动了,超时机制就是为了避免接收者处理广播的时间过长而设置的,对于后台广播的超时时间是60s,前台广播的超时时间是10s。超时机制实现原理非常简单就是在超时时间后进行超时处理。
如下是相关代码:
//以下代码位于BroadcastQueue的processNextBroadcastLocked方法
//如果mPendingBroadcastTimeoutMessage为false,则启动超时机制
if (! mPendingBroadcastTimeoutMessage) {
//超时时间,mConstants.TIMEOUT后台广播为60s,前台为10s
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
//调用该方法启动超时机制
setBroadcastTimeoutLocked(timeoutTime);
}
//setBroadcastTimeoutLocked方法
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
//创建一个what为BROADCAST_TIMEOUT_MSG的Message
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
//在timeouttime后发送上面的Message
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}
4. 取出广播接收者
超时机制启动后,就可以把BroadcastRecord对象的receivers属性中的接收者取出来了,receivers属性是一个List队列,取时是从0索引位置开始往后取。
如下是相关代码:
//以下代码位于BroadcastQueue的processNextBroadcastLocked方法
//recIdx代表索引值
final Object nextReceiver = r.receivers.get(recIdx);
如上代码取出的nextReceiver既可以是动态接收者,也可以是静态接收者。
5. 分发广播给动态接收者
如果接收者为动态,则分发广播给动态接收者。还记得在上面收集动态接收者时,一个动态接收者对应一个BroadcastFilter对象。在分发之前要做的第一件事情是验证,比如被发送的广播是需要权限的,则需要验证接收者的权限与要求的权限是否一致,不一致则不能分发,否则继续分发。
还记得可以通过BroadcastFilter对象的receiverList属性,找到远端BroadcastReceiver对象的“代理人”receiver,它的类型为IIntentReceiver是一个匿名binder服务。这时候类型为IIntentReceiver的receiver对象可就非常有用的。
在分发广播时会调用IApplicationThread的scheduleRegisteredReceiver方法,该方法是一个binder调用,它的主要参数如下:
| 参数 | 说明 |
|---|---|
| receiver:IIntentReceiver | 在注册动态接收者时会传递它,是远端BroadcastReceiver对象的“代理人” |
| intent:Intent | 广播的action以及参数都存放于此 |
| ordered:boolean | 是否是有序广播 |
| sticky:boolean | 是否是粘性广播 |
其中receiver被传递到App进程,App进程通过它可以找到最终的BroadcastReceiver对象;intent存放了广播的action以及其他参数,BroadcastReceiver对象的onReceive方法会被调用,其中的参数就是该intent。
在把广播数据发送给接收者后,还需要把当前的BroadcastRecord对象的state属性置为CALL_DONE_RECEIVE状态,代表正在等待接收者的反馈。因为这时候有超时机制,如果接收者在超时时间内没有给反馈信息,则认为已经超时,也即该接收者对应的App进程发生ANR。
下面是相关代码:
//以下代码位于BroadcastQueue的processNextBroadcastLocked方法
//是动态接收者则进入下面流程
if (nextReceiver instanceof BroadcastFilter) {
BroadcastFilter filter = (BroadcastFilter)nextReceiver;
//分发广播
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
省略其他代码······
return;
}
6. 分发广播给静态接收者
如果接收者为静态,则分发广播给静态接收者。还记得在上面收集静态接收者时,一个静态接收者对应一个ResolveInfo对象。在分发之前同样要做一系列的验证,验证通过则继续分发;否则停止分发。
广播分发给静态接收者的过程可是比动态接收者麻烦很多,因为静态接收者会存在对应的App进程没有启动的情况,针对此情况还需要等待App进程启动;如果App进程启动了则无此过程。
因为静态接收者是像动态接收者有一个注册的过程,因此不存在对应的IIntentReceiver类型的“代理人”。分发广播给静态接收者是调用IApplicationThread的scheduleReceiver方法,该方法同样也是一个binder调用,它的主要参数如下:
| 参数 | 说明 |
|---|---|
| info:ActivityInfo | 在AndroidManifest文件中配置的BroadcastReceiver信息存放于此 |
| intent:Intent | 广播的action以及参数都存放于此 |
其中info的作用与启动Activity时一样,App进程在接收到它后,会初始化对应的BroadcastReceiver对象。
在把广播数据发送给接收者后,还需要把当前的BroadcastRecord对象的state属性置为APP_RECEIVE状态,因为这时候有超时机制,如果接收者在超时时间内没有给反馈信息,则认为已经超时,也即该接收者对应的App进程发生ANR。
如下是相关代码:
//以下代码位于BroadcastQueue的processNextBroadcastLocked方法
//静态接收者对应的进程已经启动了
if (app != null && app.getThread() != null && !app.isKilled()) {
try {
省略代码······
//分发广播
processCurBroadcastLocked(r, app,
BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when sending broadcast to "
+ r.curComponent, e);
} catch (RuntimeException e) {
省略代码······
//接收者在处理广播时抛异常,则把当前广播标记为完成
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
//分发下一个广播给接收者
scheduleBroadcastsLocked();
//必须把状态标记为IDLE
r.state = BroadcastRecord.IDLE;
return;
}
}
//静态接收者的进程没有启动,则调用AMS的startProcessLocked方法开始启动进程
r.curApp = mService.startProcessLocked(targetProcess,
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
r.intent.getAction()),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
//进程没有启动成功,则接着下发下一个广播
if (r.curApp == null) {
// Ah, this recipient is unavailable. Finish it if necessary,
// and mark the broadcast record as ready for the next.
Slog.w(TAG, "Unable to launch app "
+ info.activityInfo.applicationInfo.packageName + "/"
+ receiverUid + " for broadcast "
+ r.intent + ": process is bad");
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
r.state = BroadcastRecord.IDLE;
return;
}
maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
//把该广播信息挂起,当App进程启动后会根据mPendingBroadcast判断是否需要接收该广播
mPendingBroadcast = r;
//记录接收者的索引值
mPendingBroadcastRecvIndex = recIdx;
6.3.2 小结
分发广播会分为N多轮,每一轮都是先发送所有无序广播,在发送有序广播,发送有序广播可是比发送无序广播复杂多了,发送无序广播只需要把广播发送给所有接收者即可,也不需要等待它们的反馈,并且也没有超时机制。
而发送有序广播的接收者会存在动态接收者和静态接收者,因为是有序的所以需要接收者处理完毕广播后发送反馈信息,再收到反馈信息后才能接着把广播发送给下一个接收者。如果一个接收者处理广播的时间很长,那这种情况就会导致后面的广播延迟发送,针对此情况设计了超时机制,如果接收者在规定的超时时间内没有发送反馈信息,则认为它已经超时,则该接收者对应的进程会报ANR。
因为发送有序广播的接收者存在静态和动态之分,对于静态接收者和动态接收者也分别使用不同的接口把广播数据发送给它们。那就来看下接收者如何接收广播的。
6.4 接收广播
App进程的BroadcastReceiver对象接收广播同样也分为动态接收者接收无序广播、动态接收者接收有序广播、静态接收者接收有序广播,那就依次来介绍下它们吧。
6.4.1 动态接收者接收无序广播
无序广播发送给动态接收者是通过IApplicationThread的scheduleRegisteredReceiver方法,最终会调用ApplicationThread对象的scheduleRegisteredReceiver方法,其中该方法比较重要的参数如下:
| 参数 | 说明 |
|---|---|
| receiver:IIntentReceiver | 在注册动态接收者时会传递它,是远端BroadcastReceiver对象的“代理人” |
| intent:Intent | 广播的action以及参数都存放于此 |
| ordered:boolean | 是否是有序广播 |
| sticky:boolean | 是否是粘性广播 |
App进程收到的类型为IIntentReceiver的receiver参数已经被binder驱动底层转换为原先注册时的Binder对象,而通过receiver最终可以找到BroadcastReceiver对象,并且把intent对象传递给BroadcastReceiver对象的onReceive方法,因为是无序广播因此不需要给AMS发送反馈消息。
如下是相关代码:
//LoadedApk.java
final class Args extends BroadcastReceiver.PendingResult {
public final Runnable getRunnable() {
return () -> {
//mReceiver为注册时的BroadcastReceiver对象
final BroadcastReceiver receiver = mReceiver;
final boolean ordered = mOrdered;
省略代码······
try {
省略代码······
receiver.setPendingResult(this);
//调用BroadcastReceiver的onReceive方法,把Context和intent传递给它
receiver.onReceive(mContext, intent);
} catch (Exception e) {
//注册并且时有序广播,需要发送反馈信息给AMS
if (mRegistered && ordered) {
//发送反馈信息
sendFinished(mgr);
}
省略代码······
}
if (receiver.getPendingResult() != null) {
//正常处理结束,会发送反馈信息给AMS
finish();
}
}
6.4.2 动态接收者接收有序广播
动态接收者接收有序广播与接收无序广播的流程基本一致,除了调用ActivityManager的finishReceiver方法发送反馈信息外,对于有序广播来说必须发送反馈信息,这一切都已经在LoadedApk类中做了处理,开发者完全对于这一切无感,只需要在BroadcastReceiver对象的onReceive方法中把自己的逻辑处理好即可。但是要千万注意onReceive方法中觉得不能有耗时的操作,有耗时操作会影响其他接收者接收广播,并且有可能会报ANR。
6.4.3 静态接收者接收有序广播
对于静态接收者来说不管发送者发送的是有序广播还是会无序广播,它都是当有序广播来接收,接收有序广播就需要有一个必须条件接收者处理完毕需要调用ActivityManager的finishReceiver方法发送反馈信息给AMS。
静态接收者是通过调用ApplicationThread对象的scheduleReceiver方法来接收广播的,该方法的主要参数如下:
| 参数 | 说明 |
|---|---|
| info:ActivityInfo | 在AndroidManifest文件中配置的BroadcastReceiver信息存放于此 |
| intent:Intent | 广播的action以及参数都存放于此 |
静态接收者因为没有注册的过程,因此需要使用类型为ActivityInfo的info参数来构建BroadcastReceiver对象,别看到ActivityInfo是与Activity有关的,其实它与BroadcastReceiver也是有关系的,info存储了在AndroidManifest文件中声明的BroadcastReceiver信息。因此可以根据它来创建BroadcastReceiver对象,进而调用BroadcastReceiver对象的onReceive方法,把intent参数传递给它。
如下是相关代码:
//ActivityThread
private void handleReceiver(ReceiverData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
String component = data.intent.getComponent().getClassName();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
IActivityManager mgr = ActivityManager.getService();
Application app;
BroadcastReceiver receiver;
ContextImpl context;
try {
//获取Application对象
app = packageInfo.makeApplicationInner(false, mInstrumentation);
//获取context对象
context = (ContextImpl) app.getBaseContext();
省略代码······
//获取CLassLoader对象
java.lang.ClassLoader cl = context.getClassLoader();
省略代码······
//创建receiver实例
receiver = packageInfo.getAppFactory()
.instantiateReceiver(cl, data.info.name, data.intent);
} catch (Exception e) {
省略代码······
}
try {
省略代码······
receiver.setPendingResult(data);
//调用receiver的onReceive方法
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
} catch (Exception e) {
省略代码······
} finally {
sCurrentBroadcastIntent.set(null);
}
//不为null
if (receiver.getPendingResult() != null) {
//发送反馈消息给AMS
data.finish();
}
}
6.4.4 小结
不管是动态接收者接收有序广播还是静态接收者接收有序广播都需要调用ActivityManager的finishReceiver方法发送反馈信息即处理完毕信息给AMS,AMS会把该信息转交给广播分发中心,进而广播分发中心把超时机制取消了,并且接着继续分发下一个广播。而动态接收者接收无序广播是不需要发送处理完毕消息给AMS的。
6.5 小结
本节从发送广播、收集、入队、分发广播、接收广播四步介绍了广播发送和接收流程。广播的发送和接收流程其实就是一个生产者/消费者模式,发送接收过程如下:
- 广播发送者可以发送有序广播/无序广播/后台广播/前台广播
- 而广播分发中心在收到广播后会进行收集、入队的操作,收集就是把观察该广播的动态接收者和静态接收者都收集起来,入队就是使用BroadcastRecord对象把广播和所有的接收者以及其他信息都封装起来,并且放入队列中,而这里的队列到底是哪个广播分发模块的有序广播队列还是无序广播队列都需要根据广播有序广播还是无序广播,是前台广播还是后台广播,是否存在静态接收者等信息来决定
- 不管是哪个广播分发模块分发广播都在同一个线程内分发,也就是只存在一个消费者。在每一轮分发广播时都会先分发所有无序广播,再分发有序广播,有序广播的分发要比无序广播复杂的多,基于此有序广播存在超时机制,而无序广播没有超时机制。
- App进程在收到广播后,对于有序广播需要在处理完广播后发送处理完毕消息给AMS,AMS在把消息转交给广播分发中心,广播分发中心会取消超时机制,同时 会接着分发下一个广播。对于无序广播则不需要发送处理完毕消息给AMS。
7. 总结
本文从广播机制、广播机制原理、广播接收者、广播发送者、广播分发中心、广播发送和接收流程介绍了广播相关的所有知识:
- 广播机制:介绍了广播的使用和作用
- 广播机制原理:介绍了广播机制的基本原理是啥,都由哪些部分构成
- 广播接收者:介绍了广播的接收者分为动态接收者和静态接收者,动态接收者的注册过程
- 广播发送者:介绍了广播发送者发送的广播的类别都有哪些
- 广播分发中心:分别从注册模块和分发广播模块介绍了动态广播的注册,及广播的分发
- 广播发送和接收流程:从一个更全面的角度介绍了广播发送者、广播接收者、广播分发中心之间是如何配合来保证广播机制的完美工作的