ANR系列之三:broadcast类型ANR产生原理讲解

前言:

本篇属于ANR系列的第三篇,主要讲解的是broadcast类型的ANR是如何发生和判定的。

本文会讲内容如下:

1.讲解有序广播的传递流程。

2.普通有序广播的ANR机制。

3.静态广播的ANR机制。

4.一些扩展性的问题。

阅读本文前,推荐阅读以下文章,对broadcast的实现流程有一个基本了解后更容易了解广播类型的ANR流程。

android四大组件之四-BroadCast实现原理分析

PS:本文基于android13的源码进行讲解。

一.核心对象类介绍

为了方便后续的阅读,所以我们先介绍下文本中涉及到的一些核心类以及其中的成员对象。

1.1 BroadcastQueue介绍

1.1.1 BroadcastQueue作用

BroadcastQueue正如其名,负责广播事件的具体发送任务。

BroadcastQueue一共有三种类型,有的是版本是四种类型,分别为:前台广播队列,后台广播队列,离线广播队列(有的版本把离线广播也分为前后台)。因为离线广播我们很少用,这里主要介绍前两个。

1.1.2 前后台广播超时配置

我们首先看一下两个队列的初始化操作:

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;

1.2 BroadcastRecord介绍

之前有介绍,每个广播发送事件,对应的是一个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侧处理动态广播并进行回调

2.1 动态广播的注册

2.2 查找广播接收者

APP发送广播后,会传递到系统侧。这时候系统侧会分别从IntentResolver和PackageManager中分别查找动态广播和静态广播接收者。

2.3 BroadcastQueue开启发送流程

AMS中,是由BroadcastQueue负责整个发送的流程。

调用该类的scheduleBroadcastsLocked方法开启整个广播的发送流程,通过handler切换到主线程,最终调用到processNextBroadcastLocked方法,执行广播发送流程

2.4 广播的发送

无序广播的发送,处理的对象是一个完整的广播事件,一个流程中,所有接收者都会收到广播通知。

而有序广播和静态广播,处理对象的是一个接收者,一次流程中只会有一个接收者收到广播通知。

再processNextBroadcastLocked方法中,首先会完成无序广播的发送,然后执行有序广播的发送,最后执行静态广播的发送。

2.5 APP侧处理动态广播并进行回调

2.5.1 APP侧收到广播通知

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());
    }
}

2.5.2 切换到主线程,执行传递任务

主要对应的是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任务,这个任务我们后面。这里我们可以看到,如果任务注册失败,则也会通知系统侧广播流程完成。

2.5.3 通知到广播接收者

该流程对应的主要是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方法通知系统侧。

2.5.4 APP侧通知系统侧广播流程完成

该流程对应的就是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侧广播已经处理完成了。

三.动态有序广播的ANR判定原理

流程简介

主要可以分为以下几步流程:

1.发送前的准备工作;

2.开启超时检测;

3.把广播事件发送给广播接收者;

4.超时机制判断;

5.ANR相关流程。

3.1 发送前的准备工作

动态有序广播的进入到发送流程后,发送之前,主要做了以下几件事:

首先,根据当前时间,选择等待发送的BroadcastRecord对象,相关代码如下:

BroadcastRecord r;
r = mDispatcher.getNextBroadcastLocked(now);

然后,记录当前广播接收者在集合中的位置,并且更新下一个广播接收者位置,相关代码如下:

 int recIdx = r.nextReceiver++;

接着更新本次发送给单个广播接收者的开始时间,相关代码如下:

r.receiverTime = SystemClock.uptimeMillis();

如果是处理第0位的广播接收者,说明该条BroadcastRecord是首次被处理,则还要更新其dispatchTime和dispatchClockTime值。

3.2 开启超时检测

用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中来讲。

3.3 发送广播事件

流程中会调用deliverToRegisteredReceiverLocked方法,该方法中做了一些安全检查。

然后调用到performReceiveLocked去执行发送任务,最终通过binder方法scheduleRegisteredReceiver通知到APP一侧。这里要注意,参数中有传递binder的引用对象IIntentReceiver。

接下来的流程我们1.5中已经介绍了,APP接收到之后会通知到广播接收者,处理完成之后会回调AMS的方法finishReceiver进行通知。

3.4 处理回调事件

3.4.1 AMS中的处理逻辑

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又更新了哪些属性。

3.4.2 广播收尾工作

这个流程中,主要是在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不会。

这两个区别,后面超时判断中会使用到。

3.5 单次超时机制判断

3.5.1 重置mPendingBroadcastTimeoutMessage属性

把mPendingBroadcastTimeoutMessage改为true,允许下一次的超时机制触发。

3.5.2 超时判断

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.6 超时检测补偿机制

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倍单次超时时间,这种说法应该是对这个方法误读。

3.7 ANR相关流程

3.7.1 更新BroadcastRecord属性值

r.receiverTime = now;
if (!debugging) {
    r.anrCount++;
}

修改BroadcastRecord的属性值receiverTime为当前时间。

如果不处于debug状态,ANR次数+1(PS:这里的debug指的是系统进程debug状态)。

3.7.2 获取ANR进程

首先,获取当前导致超时的广播接收者;

然后判断接受者类型,根据类型不同,用不同方式取其所属进程。

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;
}

3.7.3 开启ANR流程

        //流程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显示和日志生成原理讲解

四.静态广播的ANR判定原理

流程简介

静态广播的流程中,其发送流程和动态有序广播有一些不同,具体要区分为所属进程是否存活。

如果存活,则传递intent给APP一侧,由APP一侧创建和通知接收者;

如果不存活,则首先需要创建APP进程,进程创建后回调系统时,在执行发送广播的操作。

而接收APP的回调以及超时判断的流程,则基本上是一致的。

所以,按照作者的理解,把静态广播流程中的ANR判定原理,主要分为以下几个流程,其中因为2和5的流程和动态有序广播完全一致,所以这里就不再讲了。

1.发送前的准备工作;

2.开启超时检测

3.进程存活时,把广播事件发送给广播接收者;

4.进程不存活时,把广播事件发送给广播接收者;

5.超时机制判断;

6.ANR相关流程。

4.1 开启超时检测

这个流程和3.2中是一致的,就不重复讲了。

4.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。

4.3 进程存活时,把广播事件发送给广播接收者

如果进程存活,则流程比较简单,直接通过processCurBroadcastLocked方法通知APP。

if (app != null && app.getThread() != null && !app.isKilled()) {
    processCurBroadcastLocked(r, app);
}

该方法中,最终回通过binder方法scheduleReceiver完成向APP的传递,这个方法和动态广播是不一样的。

但是接收者的处理流程是一样的,处理完广播事件后,都会通过binder方法finishReceiver完成向系统侧的通知,后续的收尾流程也是一样的

4.4 进程不存活时,把广播事件发送给广播接收者

如果进程不存活,则会想通过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方法执行广播发送任务。

4.5 超时机制判断

静态广播的超时机制和有序广播是一样,都是判断当前执行通知广播接收者的时间,是否大于超时时间。

不管走的是创建进程的流程,还是进程存活的流程,其时间计算方式都是一致的,就是说创建进程的时间,也会被计算到单次通知广播接收者的所花费的时间当中。

和动态有序广播一样,有序如果已经处理完广播接收者事件,因为更新了receiverTime的缘故,所以当前时间一定是大于超时时间的,会执行setBroadcastTimeoutLocked方法开启下一轮的超时检测。

如果未处理完了这个广播接收者事件,那么这里的时间一定是小于等于超时时间的,也就是说超时了,就需要进入到ANR的流程了。

未处理完广播事件的话,mPendingBroadcast不为空,因为超时了所以也没必要继续等待了,所以就会把mPendingBroadcast置空。这是和动态广播的区别,因为动态广播不会用到mPendingBroadcast。

4.6 ANR相关流程

和动态广播是一致的,不重复讲了。

五.扩展问题

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。

第二种场景还有一个细分场景,就是静态广播接收者的进程如果不存在,其进程创建的时间也是会被计算到广播流程耗时当中的。

你可能感兴趣的:(安卓-源码分析,1024程序员节)