赞
踩
本篇属于ANR系列的第三篇,主要讲解的是broadcast类型的ANR是如何发生和判定的。
本文会讲内容如下:
1.讲解有序广播的传递流程。
2.普通有序广播的ANR机制。
3.静态广播的ANR机制。
4.一些扩展性的问题。
阅读本文前,推荐阅读以下文章,对broadcast的实现流程有一个基本了解后更容易了解广播类型的ANR流程。
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的日志捕获和弹框流程,本文就不扩展了,感兴趣的可以看下面这篇文章。
静态广播的流程中,其发送流程和动态有序广播有一些不同,具体要区分为所属进程是否存活。
如果存活,则传递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。
第二种场景还有一个细分场景,就是静态广播接收者的进程如果不存在,其进程创建的时间也是会被计算到广播流程耗时当中的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。