本篇属于ANR系列的第三篇,主要讲解的是broadcast类型的ANR是如何发生和判定的。
本文会讲内容如下:
1.讲解有序广播的传递流程。
2.普通有序广播的ANR机制。
3.静态广播的ANR机制。
4.一些扩展性的问题。
阅读本文前,推荐阅读以下文章,对broadcast的实现流程有一个基本了解后更容易了解广播类型的ANR流程。
android四大组件之四-BroadCast实现原理分析
PS:本文基于android13的源码进行讲解。
为了方便后续的阅读,所以我们先介绍下文本中涉及到的一些核心类以及其中的成员对象。
BroadcastQueue正如其名,负责广播事件的具体发送任务。
BroadcastQueue一共有三种类型,有的是版本是四种类型,分别为:前台广播队列,后台广播队列,离线广播队列(有的版本把离线广播也分为前后台)。因为离线广播我们很少用,这里主要介绍前两个。
我们首先看一下两个队列的初始化操作:
mFgBroadcastQueue = new BroadcastQueue(this, mHandler,"foreground", foreConstants, false);
mBgBroadcastQueue = new BroadcastQueue(this, mHandler,"background", backConstants, true);
都属于BroadcastQueue对象,只是传入参数不一样。name和constants不一样。
我们再来看一下foreConstants和backConstants的初始化代码:
final BroadcastConstants foreConstants = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS);
foreConstants.TIMEOUT = BROADCAST_FG_TIMEOUT;
final BroadcastConstants backConstants = new BroadcastConstants(
Settings.Global.BROADCAST_BG_CONSTANTS);
backConstants.TIMEOUT = BROADCAST_BG_TIMEOUT;
可以看到,区别只是TIMEOUT的值不一样,分别为10S和60S,也就能解释为什么前台广播和后台广播的ANR超时时间为什么不一致了。
static final int BROADCAST_FG_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
static final int BROADCAST_BG_TIMEOUT = 60 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
之前有介绍,每个广播发送事件,对应的是一个BroadcastRecord对象,一个发送事件可能对应多个接收者。
BroadcastRecord对象中有几个关键属性,涉及到整个广播流程中ANR的判断,这里介绍一下:
dispatchTime:无论无序还是有序广播,都会使用这个属性值。其对应的是一个完成的广播事件流程中,开始处理该事件的时间,这个时间就等于处理第0个接收者的时间。
dispatchClockTime:和dispatchTime类似,也是开始记录整个流程开始的时候。
receiverTime:开始处理每个接收者的时间。有序广播发送给每个广播接收者之前,都会更新该时间。
ordered:标记是否是有序广播,true代表有序广播。
receiver:广播接收者集合,包含动态广播和静态广播两种类型。
timeoutExempt:标记当前广播对象是否受超时影响,true代表不受超时影响。只有ACTION_PRE_BOOT_COMPLETED类型为true。
delivery:数组类型,记录通知每一个广播接收者的结果状态。
duration:数组类型,记录通知每一个广播接收者流程所花费时间。
为了方便某些对广播没有了解的读者,所以本章先粗略的介绍下有序广播的整个流程。因为前4个流程在另外一篇文章中,已经介绍的很详细了,所以这里就不做过多的阐述。这里会对APP侧的回调着重进行介绍,因为这一块涉及到后面的ANR判定流程。
主要流程如下:
1.动态广播的注册,这个我们就不扩展去讲了;
2.查找广播接收者;
3.BroadcastQueue开启发送流程;
4.广播的发送
5.APP侧处理动态广播并进行回调
APP发送广播后,会传递到系统侧。这时候系统侧会分别从IntentResolver和PackageManager中分别查找动态广播和静态广播接收者。
AMS中,是由BroadcastQueue负责整个发送的流程。
调用该类的scheduleBroadcastsLocked方法开启整个广播的发送流程,通过handler切换到主线程,最终调用到processNextBroadcastLocked方法,执行广播发送流程
无序广播的发送,处理的对象是一个完整的广播事件,一个流程中,所有接收者都会收到广播通知。
而有序广播和静态广播,处理对象的是一个接收者,一次流程中只会有一个接收者收到广播通知。
再processNextBroadcastLocked方法中,首先会完成无序广播的发送,然后执行有序广播的发送,最后执行静态广播的发送。
APP侧收到广播事件的方法是InnerReceiver(该类位于LoadedApk)中的performReceive方法,然后交给LoadedApk.ReceiverDispatcher。如果该对象为空,则直接发送广播完成通知。
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser);
} else {
...
mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
}
}
主要对应的是performReceive方法,代码如下:
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
final Args args = new Args(intent, resultCode, data, extras, ordered,
sticky, sendingUser);
...
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执行一个runnable任务,这个任务我们后面。这里我们可以看到,如果任务注册失败,则也会通知系统侧广播流程完成。
该流程对应的主要是Args中的getRunnable方法中返回的runnable对象。
public final Runnable getRunnable() {
return () -> {
...
try {
ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl); intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent),
mContext.getAttributionSource());
setExtrasClassLoader(cl);
//流程1
receiver.setPendingResult(this);
//流程2
receiver.onReceive(mContext, intent);
} catch (Exception e) {
...
}
//流程3
if (receiver.getPendingResult() != null) {
finish();
}
};
}
1.设置pendingResult为自身,后面会有判断。
2.把广播事件传递给广播接收者,也就是这个方法的调用,通知到BroadcastReceiver中的onReceive方法。
3.流程1中设置了pendingResult为自身,则此处不为空,执行finish方法。
finish方法中,判断如果是有序广播并且已经注册了,则也会调用sendFinished方法通知系统侧。
该流程对应的就是sendFinished方法,代码如下:
public void sendFinished(IActivityManager am) {
if (mOrderedHint) {
am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
mAbortBroadcast, mFlags);
} else {
am.finishReceiver(mToken, 0, null, null, false, mFlags);
}
}
我们看到,其实就是就是通过binder方法finishReceiver通知系统侧,此时代码APP侧广播已经处理完成了。
主要可以分为以下几步流程:
1.发送前的准备工作;
2.开启超时检测;
3.把广播事件发送给广播接收者;
4.超时机制判断;
5.ANR相关流程。
动态有序广播的进入到发送流程后,发送之前,主要做了以下几件事:
首先,根据当前时间,选择等待发送的BroadcastRecord对象,相关代码如下:
BroadcastRecord r;
r = mDispatcher.getNextBroadcastLocked(now);
然后,记录当前广播接收者在集合中的位置,并且更新下一个广播接收者位置,相关代码如下:
int recIdx = r.nextReceiver++;
接着更新本次发送给单个广播接收者的开始时间,相关代码如下:
r.receiverTime = SystemClock.uptimeMillis();
如果是处理第0位的广播接收者,说明该条BroadcastRecord是首次被处理,则还要更新其dispatchTime和dispatchClockTime值。
用mPendingBroadcastTimeoutMessage来进行判断,如果已经开启了超时检测的流程,则跳过,否则,开启超时检测的流程。
if (! mPendingBroadcastTimeoutMessage) {
//流程1
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
setBroadcastTimeoutLocked(timeoutTime);
}
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
//流程2
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
//流程3
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}
1.以刚刚计算的receiverTime时间+超时时间,得到任务执行时间timeoutTime,其代表超时检查任务执行时间。mConstants.TIMEOUT时间2.1中有介绍,前后台广播分别为10S和60S。
2.获取BROADCAST_TIMEOUT_MSG类型的消息,其对应超时检查任务broadcastTimeoutLocked方法。
3.发送定时执行的handler消息。
这里稍微总结一下,就是说等待时间mConstants.TIMEOUT时间后,超时检查任务会被执行,除非该消息被取消。超时检查任务,我们3.5中来讲。
流程中会调用deliverToRegisteredReceiverLocked方法,该方法中做了一些安全检查。
然后调用到performReceiveLocked去执行发送任务,最终通过binder方法scheduleRegisteredReceiver通知到APP一侧。这里要注意,参数中有传递binder的引用对象IIntentReceiver。
接下来的流程我们1.5中已经介绍了,APP接收到之后会通知到广播接收者,处理完成之后会回调AMS的方法finishReceiver进行通知。
AMS中finishReceiver中收到调用后,会做如下的工作:
BroadcastRecord r;
BroadcastQueue queue;
...
//流程1
if (isOnOffloadQueue(flags)) {
queue = mOffloadBroadcastQueue;
} else {
queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0
? mFgBroadcastQueue : mBgBroadcastQueue;
}
r = queue.getMatchingOrderedReceiver(who);
}
//流程2
doNext = r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, true);
//流程3
if (doNext) {
r.queue.processNextBroadcastLocked(...);
}
1.找到所匹配的BroadcastQueue;
2.调用BroadcastQueue的finishReceiverLocked方法进行单次广播流程的收尾工作;
3.如果还存在未完成的任务,则调用processNextBroadcastLocked方法继续开启广播流程。
3.4.2中我们看下如何进行收收尾的,3.4.3中我们看下processNextBroadcastLocked又更新了哪些属性。
这个流程中,主要是在BroadcastQueue中的finishReceiverLocked方法中执行的。
final long elapsed = finishTime - r.receiverTime;
//流程1
r.state = BroadcastRecord.IDLE;
//流程2
if (r.nextReceiver > 0) {
r.duration[r.nextReceiver - 1] = elapsed;
}
//流程3
r.receiver = null;
r.intent.setComponent(null);
...
r.curFilter = null;
r.curReceiver = null;
r.curApp = null;
mPendingBroadcast = null;
//流程4
if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
r.resultAbort = resultAbort;
} else {
r.resultAbort = false;
}
//流程5
return state == BroadcastRecord.APP_RECEIVE
|| state == BroadcastRecord.CALL_DONE_RECEIVE;
1.修改BroadcastRecord的状态;
2.记录通知单个广播接收者的流程所花费的时间;
3.重置BroadcastRecord中的相关属性;
4.如果接收到广播终止信号,并且该广播对象允许终止的话,则修改BroadcastRecord中resultAbort为true。
5.返回状态值。如果BroadcastRecord对象原来处于执行状态,则继续返回true继续后续广播事件处理。
3.4.3 后续流程
一般情况下,BroadcastRecord的状态都是非空闲的,所以会继续执行processNextBroadcastLocked流程,等于又会重新执行3.1的流程。
但是这时候,和3.1流程中有两点区别:
1.查找待执行的BroadcastRecord方法getNextBroadcastLocked中,返回的一定是当前对象。
public BroadcastRecord getNextBroadcastLocked(final long now) {
if (mCurrentBroadcast != null) {
return mCurrentBroadcast;
}
...
}
2.因为不属于首次发送,所以BroadcastRecord中的receiverTime会被更新,但是dispatchTime不会。
这两个区别,后面超时判断中会使用到。
把mPendingBroadcastTimeoutMessage改为true,允许下一次的超时机制触发。
long now = SystemClock.uptimeMillis();
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
if (timeoutTime > now) {
setBroadcastTimeoutLocked(timeoutTime);
return;
}
首先获取当前时间,和超时时间比较,这里分两种情况:
如果已经处理完了这个消息,那么因为3.4.3中说的更新了receiverTime的缘故,所以当前时间一定是大于超时时间的,会执行setBroadcastTimeoutLocked方法开启下一轮的超时检测。
如果未处理完了这个消息,那么这里的时间一定是小于等于超时时间的,也就是说超时了,就需要进入到3.7中的ANR的流程了。
3.5中的判断,依赖于handler机制。但是存在这样一个场景,如果系统侧的主进程卡住或者因为其它异常情况导致信号丢失,则会导致ANR的检测机制失效,这样自然是不行的,所以系统做了一定的补偿机制。
在processNextBroadcastLocked方法中处理有序广播的流程中,找到了广播对象BroadcastRecord后,会有如下的一个判断:
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;
}
}
我们上面有讲到dispatchTime代表整个广播流程的开始时间,这里这里判断整个广播流程所花费的时间,是否大于2*单次超时时长*广播接收者数量。也就是说如果有4个接受者,那么这里的超时时间就是80秒了(前台广播类型)。
网上有一种说法,说广播的总时长不能超过2倍单次超时时间,这种说法应该是对这个方法误读。
r.receiverTime = now;
if (!debugging) {
r.anrCount++;
}
修改BroadcastRecord的属性值receiverTime为当前时间。
如果不处于debug状态,ANR次数+1(PS:这里的debug指的是系统进程debug状态)。
首先,获取当前导致超时的广播接收者;
然后判断接受者类型,根据类型不同,用不同方式取其所属进程。
ProcessRecord app = null;
Object curReceiver;
if (r.nextReceiver > 0) {
curReceiver = r.receivers.get(r.nextReceiver-1);
r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
} else {
curReceiver = r.curReceiver;
}
if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
...
app = mService.mPidsSelfLocked.get(...);
} else {
app = r.curApp;
}
//流程1
String anrMessage = null;
if (app != null) {
anrMessage = "Broadcast of " + r.intent.toString();
}
//流程2
if (mPendingBroadcast == r) {
mPendingBroadcast = null;
}
//流程3
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
//流程4
scheduleBroadcastsLocked();
//流程5
if (!debugging && anrMessage != null) {
mService.mAnrHelper.appNotResponding(app, anrMessage);
}
1.如果app进程不为空,则给anrMessage赋值。
2.待处理的广播对象也置空,这里针对的其实主要是静态广播对象的超时,后面会讲到。
3.结束掉当前的广播流程,既然已经超时了,那么也没必要继续等下去了,直接结束当前流程。
4.开启下一轮广播发送流程,虽然超时了,但是不代表广播任务执行完了呀,所以还得继续。
5.开启ANR的日志捕获和弹框流程,本文就不扩展了,感兴趣的可以看下面这篇文章。
ANR系列之一:ANR显示和日志生成原理讲解
静态广播的流程中,其发送流程和动态有序广播有一些不同,具体要区分为所属进程是否存活。
如果存活,则传递intent给APP一侧,由APP一侧创建和通知接收者;
如果不存活,则首先需要创建APP进程,进程创建后回调系统时,在执行发送广播的操作。
而接收APP的回调以及超时判断的流程,则基本上是一致的。
所以,按照作者的理解,把静态广播流程中的ANR判定原理,主要分为以下几个流程,其中因为2和5的流程和动态有序广播完全一致,所以这里就不再讲了。
1.发送前的准备工作;
2.开启超时检测
3.进程存活时,把广播事件发送给广播接收者;
4.进程不存活时,把广播事件发送给广播接收者;
5.超时机制判断;
6.ANR相关流程。
这个流程和3.2中是一致的,就不重复讲了。
静态广播发送的代码,也在processNextBroadcastLocked方法中。
只不过和动态广播不同的时,这里的nextReceiver对象的类型为ResolveInfo。
ResolveInfo info = (ResolveInfo)nextReceiver;
ComponentName component = new ComponentName(info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
...各种安全检查
r.manifestCount++;
r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
r.state = BroadcastRecord.APP_RECEIVE;
r.curComponent = component;
r.curReceiver = info.activityInfo;
一次完成的广播流程中,是允许包涵静态和动态两种接收者的。所以,无论动态还是静态,都会在发送前更新delivery中对应位置接收者的状态。
然后更新广播对象的状态为BroadcastRecord.APP_RECEIVE,以及curComponent和curReceiver。
如果进程存活,则流程比较简单,直接通过processCurBroadcastLocked方法通知APP。
if (app != null && app.getThread() != null && !app.isKilled()) {
processCurBroadcastLocked(r, app);
}
该方法中,最终回通过binder方法scheduleReceiver完成向APP的传递,这个方法和动态广播是不一样的。
但是接收者的处理流程是一样的,处理完广播事件后,都会通过binder方法finishReceiver完成向系统侧的通知,后续的收尾流程也是一样的
如果进程不存活,则会想通过AMS的方法创建进程,并且设置mPendingBroadcast和mPendingBroadcastRecvIndex,对当前的广播对象以及执行到第几个进行记录。
APP进程创建后,进行回调通知,AMS收到回调后,检查几个BroadcastQueue队列,如果有未处理完的任务,则继续执行。
private boolean attachApplicationLocked(...){
didSomething |= sendPendingBroadcastsLocked(app);
}
boolean sendPendingBroadcastsLocked(ProcessRecord app) {
boolean didSomething = false;
for (BroadcastQueue queue : mBroadcastQueues) {
didSomething |= queue.sendPendingBroadcastsLocked(app);
}
return didSomething;
}
而sendPendingBroadcastsLocked方法中,就会判断上面设置的mPendingBroadcast是否为空,如果不为空,则首先把mPendingBroadcast置空,并且和4.3一样通过processCurBroadcastLocked方法执行广播发送任务。
静态广播的超时机制和有序广播是一样,都是判断当前执行通知广播接收者的时间,是否大于超时时间。
不管走的是创建进程的流程,还是进程存活的流程,其时间计算方式都是一致的,就是说创建进程的时间,也会被计算到单次通知广播接收者的所花费的时间当中。
和动态有序广播一样,有序如果已经处理完广播接收者事件,因为更新了receiverTime的缘故,所以当前时间一定是大于超时时间的,会执行setBroadcastTimeoutLocked方法开启下一轮的超时检测。
如果未处理完了这个广播接收者事件,那么这里的时间一定是小于等于超时时间的,也就是说超时了,就需要进入到ANR的流程了。
未处理完广播事件的话,mPendingBroadcast不为空,因为超时了所以也没必要继续等待了,所以就会把mPendingBroadcast置空。这是和动态广播的区别,因为动态广播不会用到mPendingBroadcast。
和动态广播是一致的,不重复讲了。
1.发送一个广播,A进程中接收,广播接收者A中的onReceive方法中sleep100秒,是否一定会触发ANR?如果是或者不是?原因是什么?如果我们把广播改为有序广播呢?
答:只有有序广播才会ANR,如果第一种情况如果是无序广播,自然不会ANR。
第二个答案是一般情况下是会的,因为广播接收者A中阻塞,导致AMS无法按时收到广播完成的信号,从而会引起ANR。除非进程A的主线程因为某种异常原因退出,不过这种情况下,onReceive方法自然也不会走到。
2.一个广播发送后,总时长有限制吗?
答:首先,网上有一种说法,说广播的总时长不能超过2倍单次超时时间。
实际上如3.6中的描述。除非系统侧发生异常,否则实际上是不存在总时间的限制的。因为总时长的限制是(单个超时*2*接收者数量),如果总时长都超时了,那么单个流程肯定早就超时了。
并且作者本人进行了实际的验证,并没有发生ANR。
3.哪些场景会触发广播的ANR流程?
答:应该只有两种场景,如果细分的话就是三种。
第一种场景:系统侧主线程阻塞了活着CPU跑死,如3.6种的描述。实际上这种情况很少发生;
第二种场景:有序广播有N个接收者(N>=1),单个接收者处理广播时超时,则会发生ANR。值得注意的是,哪怕只有一个接收者,也会出现ANR。
第二种场景还有一个细分场景,就是静态广播接收者的进程如果不存在,其进程创建的时间也是会被计算到广播流程耗时当中的。