前言:
一开始的目标是解决各种各样的ANR问题的,但是我们知道,ANR总体上分有四种类型,这四种ANR类型有三种是和四大组件相对应的,所以,如果想了解ANR发生的根因,对安卓四大组件的实现原理必须要懂。
所以作者会写一系列的文章,来分析四大组建的实现原理,同时也会写相应的文章来讲解四种类型的ANR是如何发生的。
本篇文章的配套文章,介绍BroadCast中的ANR是如何产生的如下:
ANR系列之三:broadcast类型ANR产生原理讲解
本篇文章主要会讲以下内容:
1.广播的基本介绍,以及广播流程中涉及到的核心类介绍;
2.动态广播接收者的注册流程;
3.APP侧发送广播事件及系统侧接收;
4.无序广播的发送流程;
5.有序广播的发送流程;
6.静态广播的发送流程;
7.一些关于广播的扩展性问题;
8.总结
PS:本文基于android13的源码进行讲解。
一.基本概念和流程
1.1 广播流程核心类介绍:
首先,我们介绍整个流程中会涉及到的一些核心类。
ContextImp:Context的最终实现类,activity,application中的各种功能最终都是委托给其来处理的,广播功能自然也不例外。其负责和系统的AMS进行通信。
ActivityManagerService:负责所有应用的Activity的流程,实际上,四大组件都是由AMS负责处理和分发的。至于为什么不单独拆开,比如搞一个BroadcastManagerService,估计是觉得类似广播功能简单不需要单独设计Service吧,而且Service属于后台服务,还是越少越好的。
BroadcastReceiver:广播接收者
IntentResolver:用来记录所广播接收者的对象
BroadCastQueue:负责具体广播的分发工作。一种有三种,分别对应前台,后台,离线。
1.2 广播基本流程:
动态广播的整套流程相对于事件传播,还是算简单的,整套流程都在java层执行的,不涉及到native流程。
广播首先按照注册方式来区分有动态广播和静态广播两种,我们分开来讲;
1.2.1 动态广播基本流程
动态广播流程图可以概括成下面这张图:
首先是动态广播接收者注册,其向AMS注册广播接收器,AMS会把其注册信息记录到IntentResolver中。也就是说IntentResolver存放到了所有的动态广播接收器。
然后广播发出者向AMS发出广播,这时候AMS首先会通过IntentResolver查找出所有的广播接收者,然后会交给BroadcastQueue来执行传播流程。
1.2.2 静态广播基本流程
动态广播流程可以概括成下面这张图:
静态广播自然是没有广播接收者注册流程的。
当广播发出者向AMS发出广播后,AMS会首先通过PackageManager进行相关广播接收者相关配置的查询,得到一个接收者集合receivers,集合中元素的类型为ResolveInfo,然后遍历集合执行相关广播的传播流程。
这里要额外说明的是,静态广播和有序广播,实际上是执行了一样的流程,所以我们一起放到了第四章来讲解。
二.动态广播接收者注册流程
2.1 APP侧进行注册
广播分为两种,动态广播和静态广播。动态广播,是首先注册广播接收者。然后再发送广播,如果存在对应的广播Action,则会把事件传递到广播接收者上面。如果是静态广播,走的是另外一个流程,我们后面会讲。
整个动态广播接收者的注册的流程图如下,我们来一一解释下
上面有说,所有的Context类的操作,其实最终都会交给ContextImpl来处理,所以最终都会调用到ContextImpl的registerReceive方法。最终会调用到registerReceiverInternal()方法,在这个方法中,通过binder方法registerReceiverWithFeature通知AMS。
2.2 AMS一侧接收
AMS一侧,自然接收的方法也是registerReceiverWithFeature。该方法中,其实主要做了两件事:
首先,生成BroadcastFilter类型对象,注册到IntentResolver中的mFilters和mActionToFilter中。
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps);
if (rl.containsFilter(filter)) {
Slog.w(TAG, "Receiver with filter " + filter
+ " already registered for pid " + rl.pid
+ ", callerPackage is " + callerPackage);
} else {
rl.add(bf);
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadcast");
}
mReceiverResolver.addFilter(bf);
}
其次,进行粘性广播对象的注册。高版本的粘性广播谷歌已经废弃了,但是server侧的代码还没有删掉,所以这里也就不扩展讲解了,这里只列一下代码,如下:
这里也吐槽下谷歌的安卓源码项目,总是不断添加新功能,废弃的老功能不删,导致项目代码越来越大,编译后的项目也越来越大。
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, null, -1, -1, false, null, null, null, null, OP_NONE, null,
receivers, null, 0, null, null, false, true, true, -1, false, null,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
}
2.3 小结:
所以,我们可以知道,动态广播最终是注册到了mReceiverResolver里面的成员变量中,其实现类是IntentResolver。首先注册到IntentResolver.mFilters上,然后根据action的类型,在具体负责注册到mActionToFilter或者mTypedActionToFilter上。
自然是注册到了mReceiverResolver这个对象上面,那么下面讲动态广播传播的流程,自然也会使用到这个对象。
三.广播事件发送以及系统侧接收
广播有多种类型,无序广播,有序广播,粘性广播等。因为粘性在安卓的高版本已经废弃,所以这里也就不讲了,我们这里只讲无序广播和有序广播的流程。
3.1 APP侧发送
无序广播,通过的是ContextImpl中sendBroadcast方法,而有序广播通过的是sendOrderedBroadcast方法。无论哪种,其实调用的都是AMS提供的binder方法broadcastIntentWithFeature,只是参数不同而已。其实甚于粘性广播调用的也是这个方法。方法代码如下,倒数第3个参数区分是否是有序广播,倒数第2的参数区分是否是粘性广播。
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
rd, initialCode, initialData, initialExtras, receiverPermissions,
null /*excludedPermissions=*/, null, appOp, options, true, false,
user.getIdentifier());
3.2 系统侧接收
AMS一侧自然是broadcastIntentWithFeature方法接收的。主要执行的如下的流程:
broadcastIntentWithFeature首先会通知到AMS的broadcastIntentLocked方法中。
在该方法中,我们上面有讲过,动态广播数注册到mReceiverResolver中的。所以这里自然先从mReceiverResolver中查询,看是否存在接收者,如果存在,加入到registeredReceivers集合中。
代码如下:
registeredReceivers = mReceiverResolver.queryIntent(intent,
resolvedType, false /*defaultOnly*/, userId);
3.3 系统侧处理广播
broadcastIntentLocked方法是系统册处理广播的核心,下面代码的讲解也主要都是在broadcastIntentLocked方法中执行的。
首先,会进行一些安全性的检查,比如进程是否存在,是否有对应广播的权限,发送的是否是保护性广播(比如开机广播这种就不可能允许任意进程随便发)等等,如果不具有对应的权限,则返回失败。
然后进行粘性广播的逻辑判断,粘性广播的逻辑这里就不扩展讲解了。
接下来会去查找静态广播接收者和动态广播接收者。我从代码中可以看到有两个集合,分别是receivers和registeredReceivers,对应的分别就是查找出来的静态广播接收者和动态广播接收者。
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
// Need to resolve the intent to interested receivers...
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
== 0) {
receivers = collectReceiverComponents(
intent, resolvedType, callingUid, users, broadcastAllowList);
}
if (intent.getComponent() == null) {
if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
//这里是特殊逻辑,不用关心
} else {
registeredReceivers = mReceiverResolver.queryIntent(intent,
resolvedType, false /*defaultOnly*/, userId);
}
}
由于动态广播和静态广播的处理逻辑是不一样的,代码也是分开的,所以我们接下来分别来讲这两者的流程。
四.动态广播事件传递流程
4.1 本章概要
本章内容主要都是发生在AMS中的broadcastIntentLocked方法中。
动态广播流程中,首先我们要查找动态广播的接受者,这个我们放到4.2小节中来讲;
然后开启广播的发送流程,因为有序广播和无序广播的流程是不一样的,所以4.3我们主要讲一下两者的区别;
4.4中,我们讲一下无序广播的发送流程,有序广播的流程我们下一章来讲;
最后的4.5小节,我们讲一下server侧的广播是如何传递到APP一侧的。
4.2 查找动态广播接收者
这里对应的核心就是上面的queryIntent方法中。queryIntent方法中,按照不同类型进行查询:
如果resolvedType不为空,则从mTypeToFilter,mBaseTypeToFilter,mTypedActionToFilter,mWildTypeToFilter等集合查询。
如果scheme不为空,则从mSchemeToFilter集合中查询;
如果resolvedType为空,则从mActionToFilter集合中查询;
最后,会把上面查找的所有结果按照优先级进行排序,最终进行返回,返回值就是registeredReceivers集合。如果同一个广播满足多个条件,也只会在集合中存在一份。
4.3 分别开启无序广播和有序广播发送流程
找到了广播接收者之后,我们就可以准备开始广播流程了。这里无序广播和有序广播的流程是不一样的。
4.3.1 首先我们来看下无序广播:
//代码有删减,只保留核心内容
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
final BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(...);
...
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
我们可以看到,首先查找对应的BroadcastQueue,BroadcastQueue是负责具体发送任务的。分别前台,后台,离线三种。这里对应的其实也就是我们发送广播时的前台/后台广播。
然后生成广播对象BroadcastRecord,加入到BroadcastQueue中mParallelBroadcasts集合中。
最后,通过scheduleBroadcastsLocked开启广播流程。
4.3.2 然后我们来看下有序广播流程
首先,把上面动态广播接收者集合registeredReceivers中的对象,都加入到receivers中。也就是说,后续流程中receivers集合其实是包含了有序广播和静态广播的集合,receivers中同时存在两种元素类型BroadcastFilter和ResolveInfo。相关代码如下:
void broadcastIntentLocked(){
...
while (ir < NR) {
if (receivers == null) {
receivers = new ArrayList();
}
receivers.add(registeredReceivers.get(ir));
ir++;
}
...
}
然后处理receivers集合中的对象,该流程和无序广播的流程基本上是一致的。
首先根据receivers生成广播对象BroadcastRecord,然后加入到BroadcastQueue.BroadcastDispatcher中的mOrderedBroadcasts集合中,最后通过scheduleBroadcastsLocked方法开启广播流程。
if ((receivers != null && receivers.size() > 0)
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(...);
queue.enqueueOrderedBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
4.3.3 最后我们来看下两者区别
有序的流程前面和无序广播前面的流程是一样的,也是说4.2.4之前,有序和无序执行的是一样的流程。而区别就是从4.2.4开始的,我花了一张简单的流程图,来展示一下两者的区别。
有序广播和无序广播的区别,就是加入到的BroadcastQueue的集合对象不一样,有序广播会把查找到的广播接对象BroadcastRecord加入到BroadcastDispatcher中的mOrderedBroadcasts集合,而无序广播则会加入到mParallelBroadcasts中。
但是后续的广播发送流程是一致的,两者都会调用scheduleBroadcastsLocked方法开启广播流程,该流程中会先处理无序广播的流程,然后再执行有序广播的流程,最后执行静态广播的流程。
4.4 发送无序广播流程
本章主要讲无序广播的流程,有序广播的我们下一章再讲。所以通过scheduleBroadcastsLocked方法开启广播流程后,我们接着往下看:
1.scheduleBroadcastsLocked中首先判断是否正在发送的流程中。如果正在发送流程中则直接忽略本次操作,否则进入发送流程。
public void scheduleBroadcastsLocked() {
if (mBroadcastsScheduled) {
return;
}
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
mBroadcastsScheduled = true;
}
2.通过handler转发之后,切换到主线程,执行processNextBroadcast方法。processNextBroadcast方法中加了个锁,调用processNextBroadcastLocked方法,解决多线程问题。
为什么会有多线程问题?实话实说,我认为这里是一个源码中疏忽的地方,因为经过handler的切换,这里一定会是在系统侧的主线程执行,就不可能存在多线程的问题。
//线程切换
case BROADCAST_INTENT_MSG: {
processNextBroadcast(true);
//加锁
private void processNextBroadcast(boolean fromMsg) {
synchronized (mService) {
processNextBroadcastLocked(fromMsg, false);
}
}
3.因为无序广播和有序广播其实都通过processNextBroadcastLocked方法来执行发送流程的,所以这里讲processNextBroadcastLocked方法的时候,先只讲无序广播的部分,下一小节再讲有序广播的部分。
该方法中,首先遍历mParallelBroadcasts集合,针对每个广播事件记录BroadcastRecord,通过deliverToRegisteredReceiverLocked方法进行对应的客户端进程(mParallelBroadcasts集合中的BroadCastRecord就是我们刚刚添加的)。
至此,动态广播中的无序广播发送流程已经完成,此时的客户端已经成功收到了无序广播。deliverToRegisteredReceiverLocked方法负责把具体的广播事件发送到APP一侧,这个流程我们4.5中来讲。
4.5 把广播事件传递给客户端
这一小节其实主要就是针对deliverToRegisteredReceiverLocked方法的讲解。
首先是进行一列的检查,如下
1.进行相关的权限检查,看发送者具有有接收者所要求的相关权限;
if (filter.requiredPermission != null) {
int perm = mService.checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Permission Denial: broadcasting "
...
skip = true;
} else {
...
}
}
2.检查接收者是否为空
if (!skip && (filter.receiverList.app == null || filter.receiverList.app.isKilled()
|| filter.receiverList.app.mErrorState.isCrashing())) {
Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
+ " to " + filter.receiverList + ": process gone or crashing");
skip = true;
}
3.进行一系列其他的安全检查
4.检查启动安全模式,高版本是严格,低版本放的比较松。之前作者就在这里踩过坑。
if (!skip) {
final int allowed = mService.getAppStartModeLOSP(
info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
// We won't allow this receiver to be launched if the app has been
// completely disabled from launches, or it was not explicitly sent
// to it and the app is in a state that should not receive it
// (depending on how getAppStartModeLOSP has determined that).
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
Slog.w(TAG, "Background execution disabled: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
skip = true;
} else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
&& !isSignaturePerm(r.requiredPermissions))) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
Slog.w(TAG, "Background execution not allowed: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
skip = true;
}
}
}
5.调用performReceiveLocked方法完成向APP的发送。
performReceiveLocked方法中,首先获取APP侧的Binder对象IApplicationThread,然后通过其binder方法scheduleRegisteredReceiver发送给APP一侧,或者直接通过binder对象完成跨进程的通信。
void performReceiveLocked(...) throws RemoteException {
if (app != null) {
final IApplicationThread thread = app.getThread();
if (thread != null) {
try {
thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
data, extras, ordered, sticky, sendingUser,
app.mState.getReportedProcState());
...
}else{
receiver.performReceive(intent, resultCode, data, extras, ordered,
sticky, sendingUser);
}
}
6.APP一侧自然是ActivityThread中的scheduleRegisteredReceiver方法完成的接收,其逻辑也很简单,直接交给binder对象receiver来处理,这逻辑,就和5中的else逻辑一致了。
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState) throws RemoteException {
updateProcessState(processState, false);
receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
sticky, sendingUser);
}
7.receiver是一个binder,其server是在LoadedApk.ReceiverDispatcher中的InnerReceiver类。所以,最终是InnerReceiver中的performReceive方法收到了这个binder通知。方法如下:
@Override
public void performReceive(...) {
final LoadedApk.ReceiverDispatcher rd;
...
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
ordered, sticky, sendingUser);
} else {
...
}
}
我们所以看到,又调用到了LoadedApk.ReceiverDispatcher中的performReceive方法。
8.LoadedApk.ReceiverDispatcher的performReceive方法如下:
public void performReceive(...){
...
if (intent == null || !mActivityThread.post(args.getRunnable())) {
if (mRegistered && ordered) {
IActivityManager mgr = ActivityManager.getService();
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing sync broadcast to " + mReceiver);
args.sendFinished(mgr);
}
}
}
我们可以看到,通过handler进行一个线程切换,切换到主线程执行Args中的Runnable。
9.那么我们最后就来看下这个在主线程中将要被执行的Args中的Runnable,代码如下:
public final Runnable getRunnable() {
return () -> {
final BroadcastReceiver receiver = mReceiver;
final boolean ordered = mOrdered;
...
final IActivityManager mgr = ActivityManager.getService();
final Intent intent = mCurIntent;
...
try {
ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl); intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent),
mContext.getAttributionSource());
setExtrasClassLoader(cl);
receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);
} catch (Exception e) {
...
}
if (receiver.getPendingResult() != null) {
finish();
}
};
}
通过代码,我们可以看到,主要做了两件事:
首先,因为其持有BroadcastReceiver对象receiver,所以直接通知了BroadcastReceiver的onReceive方法。
然后,如果是有序广播,还要调用finish方法通知系统已完成此次广播的传递流程。
五.有序广播的传播流程
5.1 有序广播的主要流程
有序广播,也是通过scheduleBroadcastsLocked方法开启的流程,其前面的流程执行流程和无序广播是一样的,区别是从processNextBroadcastLocked方法开始的。
processNextBroadcastLocked中首先处理有序广播, 无序广播处理完成后,就开始有序广播的处理流程了,甚至静态广播的处理流程也在该方法的最后。
有序广播按照我的理解,主要分为以下4个阶段:
1.找到待发送的有序广播对象;
2.执行一系列的安全判断;
3.发送前的准备操作;
4.执行有序广播的发送;
5.2 找到待发送的广播对象
有序广播的发送流程,主要是BroadcastDispatcher来负责的。其首先通过getNextBroadcastLocked方法找出下一个该发送哪个广播,这个广播对象就是BroadcastRecord。
r = mDispatcher.getNextBroadcastLocked(now);
该方法中,只要执行以下的逻辑:
public BroadcastRecord getNextBroadcastLocked(final long now) {
//逻辑1
if (mCurrentBroadcast != null) {
return mCurrentBroadcast;
}
final boolean someQueued = !mOrderedBroadcasts.isEmpty();
BroadcastRecord next = null;
//逻辑2
if (!mAlarmBroadcasts.isEmpty()) {
next = popLocked(mAlarmBroadcasts);
if (DEBUG_BROADCAST_DEFERRAL && next != null) {
Slog.i(TAG, "Next broadcast from alarm targets: " + next);
}
}
//逻辑3
if (next == null && !mDeferredBroadcasts.isEmpty()) {
// We're going to deliver either:
// 1. the next "overdue" deferral; or
// 2. the next ordinary ordered broadcast; *or*
// 3. the next not-yet-overdue deferral.
for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
Deferrals d = mDeferredBroadcasts.get(i);
if (now < d.deferUntil && someQueued) {
// stop looking when we haven't hit the next time-out boundary
// but only if we have un-deferred broadcasts waiting,
// otherwise we can deliver whatever deferred broadcast
// is next available.
break;
}
if (d.broadcasts.size() > 0) {
next = d.broadcasts.remove(0);
// apply deferral-interval decay policy and move this uid's
// deferred broadcasts down in the delivery queue accordingly
mDeferredBroadcasts.remove(i); // already 'd'
d.deferredBy = calculateDeferral(d.deferredBy);
d.deferUntil += d.deferredBy;
insertLocked(mDeferredBroadcasts, d);
if (DEBUG_BROADCAST_DEFERRAL) {
Slog.i(TAG, "Next broadcast from deferrals " + next
+ ", deferUntil now " + d.deferUntil);
}
break;
}
}
}
//逻辑4
if (next == null && someQueued) {
next = mOrderedBroadcasts.remove(0);
if (DEBUG_BROADCAST_DEFERRAL) {
Slog.i(TAG, "Next broadcast from main queue: " + next);
}
}
mCurrentBroadcast = next;
return next;
}
1.如果当前的广播还未处理完成,则直接返回当前正在处理的广播mCurrentBroadcast。
2.如果有定时广播,则从定制广播中取出第一个返回。
3.如果有延迟广播,则从延迟广播中取出第一个返回。
4.上面都没有的情况下,最后轮到了有序广播。从有序广播的队列mOrderedBroadcasts中取出第一个返回。上面我们有讲到,mOrderedBroadcasts集合的加入,是在5.3中完成的。
5.3 执行一系列的安全判断
找到了待发送的广播对象后,就开始执行一系列的安全检查了。
//流程1
if (r == null) {
return;
}
//流程2
int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
if ((numReceivers > 0) &&
(now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
broadcastTimeoutLocked(false); // forcibly finish this broadcast
forceReceive = true;
r.state = BroadcastRecord.IDLE;
}
//流程3
if (r.state != BroadcastRecord.IDLE) {
return;
}
//流程4
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
...
mDispatcher.retireBroadcastLocked(r);
r = null;
looped = true;
}
1.如果对象为空,则直接结束掉当前流程;
2.进行相关的超市判断,如果超时,则把forceReceive设置为true。
3.如果当前广播对象不属于空闲, 则说明已经进入到了发送流程中,则直接结束掉当前流程。
4.最终进行一系列的安全判断,满足下面任意条件,则进入到异常场景并进行异常处理。异常处理的最后,会把r设置为null,进行下一轮循环找到合适的BroadcastRecord对象。
广播对象的接收者为空;
广播对象当前执行到的位置,已经大于等于接收者的总数;
广播对象被放弃;
广播对象已经满足超时条件了,2中已经进行了判断。
5.4 发送前的准备操作;
经过一系列的判断,找到了合法的将要被处理的广播对象r,则首先进行一些发送前的准备操作。
1.因为这里是有序广播的流程,所以首先看当前已经执行到了第几个接收者了。
// Get the next receiver...
int recIdx = r.nextReceiver++;
2.重置发送时间,广播的超时是和这个值相关的。
r.receiverTime = SystemClock.uptimeMillis();
3.如果有序广播是第0位,则会更新两个时间值,如下:
if (recIdx == 0) {
r.dispatchTime = r.receiverTime;
r.dispatchClockTime = System.currentTimeMillis();
...
}
更新BroadcastRecord的dispatchTime和dispatchClockTime时间。dispatchTime时间可以理解为开始执行广播流程的时间,而上面的receiverTime时间可以理解为通知单个广播接受者使用的时间。
何时超时,我们放到广播类型的ANR那篇文章中来讲。
4.发送一个延时handler消息,用来监测是否整个广播流程是否超时。
if (! mPendingBroadcastTimeoutMessage) {
setBroadcastTimeoutLocked(timeoutTime);
}
5.5 执行有序广播的发送
好了,经过一系列的操作,我们终于可以开始发送广播了。
1.取出下一个广播接收者。判断其类型,如果是BroadcastFilter类型,说明是动态广播接受者,则执行发送流程。如果是ResolveInfo类型,则说明是静态广播接收者,我们第六章来讲。
final BroadcastOptions brOptions = r.options;
final Object nextReceiver = r.receivers.get(recIdx);
if (nextReceiver instanceof BroadcastFilter) {
//动态有序广播流程
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
return;
}
//静态广播流程
2.执行最终的发送流程。
最终的发送流程方法都在deliverToRegisteredReceiverLocked中,这个方法我们在4.3小节中讲了,这里就不赘述了。
六.静态广播的传播流程
6.1 静态广播的主要流程
静态广播的整个执行流程其实也是在processNextBroadcastLocked方法中,它和无序广播,有序广播是一样的,只不过在这个方法中最后被执行。
上面说5.5中讲到,从BroadcastRecord中的receivers中取出对象,如果是BroadcastFilter类型执行动态有序广播流程,如果是ResolveInfo类型,则会执行静态广播流程了。
这里要注意的是,无论静态广播,还是有序广播,都属于同一个BroadcastRecord对象。也就是说发送一个有序广播,其接收者有可能是静态广播接收者,也有可能是动态广播接收者,而且属于同一批次的接收者。
静态广播主要有如下流程:
1.静态广播的准备工作
2.接收者进程存活情况下通知接收者;
3.接收者进程不存活情况下则拉起接收者进程;
4.权限检查详解。
6.2 静态广播的准备工作
6.2.1构建对象
既然不是BroadcastFilter类型,那么一定是ResolveInfo类型,所以直接转换为ResolveInfo对象。
然后构建ComponentName对象供后面使用。
ResolveInfo info = (ResolveInfo)nextReceiver;
ComponentName component = new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
6.2.2 权限检查
接下来,会执行一系列的权限检查。这一块检查会有很多项目,因为不算事是核心流程,所以我们放到6.5中专门来讲,这里暂且先跳过权且认为权限检查已通过。
boolean skip = false;
//各种判断,如果不通过,就会把skip改为true
if (skip) {
r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
r.receiver = null;
r.curFilter = null;
r.state = BroadcastRecord.IDLE;
r.manifestSkipCount++;
scheduleBroadcastsLocked();
return;
}
6.2.3 准备工作
最后做一些发送前的准备工作,首先更新BroadcastRecord中的一些状态值:
r.manifestCount++;
r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
r.state = BroadcastRecord.APP_RECEIVE;
r.curComponent = component;
r.curReceiver = info.activityInfo;
然后修改对应包名的状态,使其暂时成为不可杀死应用。
// Broadcast is being executed, its package can't be stopped.
try {
AppGlobals.getPackageManager().setPackageStoppedState(
r.curComponent.getPackageName(), false, r.userId);
}
6.3 接收者进程存活情况下通知接收者
如果APP进程还存活,则会通过processCurBroadcastLocked方法执行发送流程,发送广播给对应的进程。
if (app != null && app.getThread() != null && !app.isKilled()) {
processCurBroadcastLocked(r, app);
}
我们再来看一下processCurBroadcastLocked方法,如下:
private final void processCurBroadcastLocked(BroadcastRecord r,
ProcessRecord app) throws RemoteException {
...
//流程1
final IApplicationThread thread = app.getThread();
if (thread == null) {
throw new RemoteException();
}
if (app.isInFullBackup()) {
skipReceiverLocked(r);
return;
}
//流程2
r.receiver = thread.asBinder();
r.curApp = app;
final ProcessReceiverRecord prr = app.mReceivers;
prr.addCurReceiver(r);
app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
mService.updateLruProcessLocked(app, false, null);
...
r.intent.setComponent(r.curComponent);
boolean started = false;
try {
//流程3
thread.scheduleReceiver(new Intent(r.intent), ...);
started = true;
} finally {
...
}
}
主要做了三件事:
1.检查对应进程的binder是否存在,不存在就无法和APP进行通信,自然抛出异常终止流程。
2.更新BroadcastRecord对象的属性值,以及BroadcastRecord中Intent的属性值,这样最终发送到APP侧后,APP可以知道应该启动哪个接收者。
3.通过binder方法scheduleReceiver完成跨进程通信,通知APP一侧。
6.4 接收者进程不存活情况下通知接收者
如果APP进程不存活,则通过两步操作来完成通知接收者的操作:
首先拉起广播接收者所在的进程;
然后在拉起后通知APP一侧。
6.4.1 拉起广播接收者所在的进程
//流程1
r.curApp = mService.startProcessLocked(...)
//流程2
if (r.curApp == null) {
finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
r.state = BroadcastRecord.IDLE;
return;
}
//流程3
mPendingBroadcast = r;
mPendingBroadcastRecvIndex = recIdx;
主要做了三件事:
1.直接通过AMS进行拉起接收者所属进程的操作。
2.如果拉起失败,则直接结束掉当前的广播流程
3.拉起成功,则修改mPendingBroadcast和mPendingBroadcastRecvIndex为当前广播对象的值。这样下一次执行广播流程,就会处理。
6.4.2 PendingBroadcast阻塞和检查
我们回头看一下processNextBroadcastLocked方法,发现在完成无序广播的发送后,有这样一段代码来处理mPendingBroadcast对象。
//流程1
if (mPendingBroadcast != null) {
boolean isDead;
//流程2
if (mPendingBroadcast.curApp.getPid() > 0) {
synchronized (mService.mPidsSelfLocked) {
ProcessRecord proc = mService.mPidsSelfLocked.get(
mPendingBroadcast.curApp.getPid());
isDead = proc == null || proc.mErrorState.isCrashing();
}
} else {
//流程3
final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
isDead = proc == null || !proc.isPendingStart();
}
if (!isDead) {
//流程4
// It's still alive, so keep waiting
return;
} else {
//流程5
mPendingBroadcast.state = BroadcastRecord.IDLE;
mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
mPendingBroadcast = null;
}
}
1.mPendingBroadcast不为空,说明刚刚启动过进程存在静态广播。所以这里推测,APP进程启动后,应该会通知系统侧进行某个流程,置空mPendingBroadcast并完成发送,这个我们后面来讲。
2.pid>0只是代表进程创建成功,但是并不代表进程存活,所以还要再次检查一下。最后更新到isDead这个变量上。
3.如果创建不成功,并不代表当前执行时还未成功,则从进程列表中查询一边,判断进程是否存活更新到isDead上。
4.如果进程存活,则直接返回,继续等待。也就是说,静态广播和有序广播都被阻塞住。
5.如果进程不存活,则直接结束流程并且把相关变量进行重置。
这个流程我们可以得出以下的结论:
1.mPendingBroadcast存在时,有序广播和静态广播是会被阻塞住的。
2.如果mPendingBroadcast所对应的进程启动失败,则会立马进行重置,避免阻塞。
6.4.3 通知APP一侧
接着我们看一下,到底是哪里来处理mPendingBroadcast并完成广播发送的。
这里我们得稍微回顾一下APP的启动流程了,如果对APP启动流程感兴趣,可以看一下下面的这篇文章。
android源码学习- APP启动流程
我们这里稍微讲一下,APP进程启动后,会调用ActivityThread的main方法,main方法中会调用binder方法attachApplication通知系统侧APP进程创建完成,这里就会调用到AMS中的attachApplication方法,经过加锁后调用到attachApplicationLocked方法,看一下这个方法:
private boolean attachApplicationLocked(...) {
//逻辑1
thread.bindApplication(...)
//逻辑2
didSomething |= sendPendingBroadcastsLocked(app);
...
}
1.通知APP区创建Application完成初始化操作。
2.去执行pending的广播的发送。
逻辑2中,会最终调用到BroadcastQueue中的sendPendingBroadcastsLocked方法,我们看下这个方法:
public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
boolean didSomething = false;
//逻辑1
final BroadcastRecord br = mPendingBroadcast;
if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
if (br.curApp != app) {
return false;
}
try {
//逻辑2
mPendingBroadcast = null;
//逻辑3
processCurBroadcastLocked(br, app);
didSomething = true;
} catch (Exception e) {
...
}
}
return didSomething;
}
首先,获取mPendingBroadcast对象(逻辑1);
然后把mPendingBroadcast置为null,这也正好对应了我们上一小节的推测(逻辑2);
最终调用processCurBroadcastLocked方法完成广播的发送,processCurBroadcastLocked方法我们6.3中已经讲过了(逻辑3)。
6.5 静态广播权限检查
上面讲到了,发送静态广播需要对接收者经验很多的权限检查,因为检查条件太多了,所以我们挑几个重要的来讲。
1.检查接收者targetSDK版本的问题,如果不再发送广播的目标范围内,则检查不通过。
ResolveInfo info = (ResolveInfo)nextReceiver;
if (brOptions != null &&
(info.activityInfo.applicationInfo.targetSdkVersion
< brOptions.getMinManifestReceiverApiLevel() ||
info.activityInfo.applicationInfo.targetSdkVersion
> brOptions.getMaxManifestReceiverApiLevel())) {
...
skip = true;
}
2.检查发送进程和接收进程是否存在关联关系。如果两者其一属于系统集成,则被允许。
if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
skip = true;
}
3.检查接收者是否声明了export,以及是否具体接收者组件所声明的权限。也就是说,只有赋予了接收者APP对应的权限,才能够接收。
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
skip = true;
} else if (!skip && info.activityInfo.permission != null) {
if (opCode != AppOpsManager.OP_NONE &...) {
skip = true;
}
}
4.即时应用是不允许发送的
if (!skip && r.callerInstantApp
&& (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
&& r.callingUid != info.activityInfo.applicationInfo.uid) {
Slog.w(TAG, "Instant App Denial: receiving "
+ r.intent
+ " to " + component.flattenToShortString()
+ " requires receiver have visibleToInstantApps set"
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")");
skip = true;
}
5.发送者进程如果出现Crash,也是不允许发送的
if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
skip = true;
}
6.这一块按照我的理解是是否暂停了接收者的应用吧,应用暂停则不可接收(应用暂停时android12.1开始推出的新功能)。
isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
info.activityInfo.packageName,UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
if (!isAvailable) {
skip = true;
}
7.检查APP检查模式。基本上是按照APP配置的targetSDK版本来区分的,高版本检查很严格,低版本松,之前作者就在这里踩过坑。
final int allowed = mService.getAppStartModeLOSP(...);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
skip = true;
}else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
&& !isSignaturePerm(r.requiredPermissions))) {
skip = true;
}
8.关闭应用类型的广播,收到运行状态的限制。
if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
&& !mService.mUserController
.isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),0 )) {
skip = true;
}
9.非系统进程,还要检查接受者是否有发送者所要求的权限
if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
...代码略
skip = true;
}
我们可以看到,静态广播的各种限制,要比动态广播多得多。这是因为静态广播可以直接唤起一个新的APP应用,这个影响面还是很大的。比如我一个APP如果注册了静态广播中的开屏广播,那么每次每次亮屏都会启动我的APP,实现了一个保活,这影响面无疑是很大的,所以一定要做一定的限制。目前普通的APP基本上已经无法通过注册静态广播的方式来使用了。
七.关于BroadCast扩展性问题
1.如果两个应用注册同样action的广播接收者,会怎样?
答:首先要去分有序广播还是无序广播。如果无序广播,那么所有的的广播接收者都会收到通知。如果是有序广播,那么就会按照优先级依次收到通知。
2.广播是否可靠?
答:不可靠。进程如果不存在或者挂掉直接结束了流程,或者发送者进程挂掉也会导致静态广播接收者收不到。
3.动态广播接收者优先级99,静态广播接收者优先级100,哪个会先收到?
答:虽然流程上,动态广播会优先执行。但是由于有序广播,其实每次只处理一个广播对象中广播接收者集合中的一条。所以仍然会按照广播接收者集合中的顺序来执行。相关核心代码如下:
final Object nextReceiver = r.receivers.get(recIdx);
4.使用静态广播时,Application中有耗时操作会有什么问题?
答:这要有一个前提条件,就是APP进城不存在,存在的话就不会走Application流程。
虽然系统侧会连续发送两条任务(创建application和发送广播两条任务)给APP一侧,但是由于都在主线程执行,所以执行顺序上并不会出现问题。
但是由于静态广播创建新进程时是阻塞的,后面的有序广播时不能发送的,因此系统会有ANR超时机制,时间过长是会导致ANR的。
5.发送完广播后发送者进程闪退了,接收者能否收到?
答:参照6.5中静态广播严格的检查,发送者crash了静态广播应该是收不到的,动态可以。
6.前台广播和后台广播有什么区别?
答:从目前代码看来,前台和后台所归属的BroadcastQueue是不同的,所以相互之间不会阻塞。即发送前台广播阻塞时并不会影响后台广播的发送。另外前后台广播和后台广播的时间是不一样的,分别为10S和60S。
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
static final int BROADCAST_BG_TIMEOUT = 60 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
其它流程基本没有区别。
7.发送无序广播,并且注册多个带不同优先级的静态广播接收者,优先级是否会生效?
答:是生效的,上面有介绍。静态广播和有序广播,走的其实是一个流程。所以对于静态广播接收者,哪怕发送的是普通广播类型,一样会按照优先级进行分发。
但是如果此时同样注册有动态广播接收者,那么一定是动态广播接收者先收到这个广播,然后静态广播接收者在按照优先级依次完成接收。
8.abortBroadcast是如何结束掉广播流程的?
答:abortBroadcast方法会修改BroadcastReceiver中的属性值mAbortBroadcast为true。一次有序广播事件APP完成后,都会发送sendFinish方法通知系统侧,我们看一下这个方法。
public void sendFinished(IActivityManager am) {
if (mOrderedHint) {
am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
mAbortBroadcast, mFlags);
} else {
am.finishReceiver(mToken, 0, null, null, false, mFlags);
}
}
可以看到如果是有序广播会把mAbortBroadcast这个值传递给系统侧。AMS收到会,会转交给处理队列BroadcastQueue中的finishReceiverLocked方法,最终更新到广播事件对象BroadcastQueue中的属性值resultAbort上。
if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
r.resultAbort = resultAbort;
}
后续BroadcastQueue队列在处理广播对象时,识别到resultAbort=true就会略过。
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
...
r = null
continue;
}
9.有序和无序广播是怎么区分的?
答:sendOrderedBroadcast和sendBroadcast两个方法,其实最终broadcastIntentWithFeature方法中,就是一个参数不一样,倒数第三个boolean serialized。sendOrderedBroadcast为true,sendBroadcast为false。
10.静态广播的接收者对象是否是同一个?
答:结论有可能和你想象的并不一样。静态广播,每次的广播接收者都是新生成的,所以不在再静态广播的构造方法中做耗时操作。
receiver = packageInfo.getAppFactory().instantiateReceiver(cl, data.info.name, data.intent);
public @NonNull BroadcastReceiver instantiateReceiver(...)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (BroadcastReceiver) cl.loadClass(className).newInstance();
}
八.总结
讲了这么多,每个章节也都包涵很多知识点,所以没有点储备,有可能很难理解。所以最后来做一个总结:
首先,我们按照注册方式的不同,分成动态广播和静态广播。动态广播最终会把广播接收者注册到IntentResolver中,而静态广播没有注册流程,只需要在manifest中声明即可。使用时PackageManager会遍历所有APP的manifest进行查找。
然后,我们在按照发送方式的不同,分成无序广播和有序广播,其中静态广播都属于有序广播类型。无序广播,是发送方发送广播后,所有接收者并行接收的。有序广播,是发送方发送广播后,依次接收到广播事件,优先级高的先收到,甚至可以在接收者中结束掉当前广播流程。
有序广播中,我们在分出来一种特殊类型就是静态广播。如果接收者进程存在,则直接发送给对应的进程,如果进程不存在,则首先会创建APP进程,在APP创建完成通知系统侧后,在进行广播事件的发送。
最后,用一张图来总结一下,方便记录和理解。