成体系学习 ANR 系列,代码是基于 Android 14,关注 ANR系列合集,公_众_号:代码与生活手记
本篇文章目录:
- 广播处理的大致逻辑
- 广播中不同的时间检测处理机制
- 广播 ANR 埋炸弹
- 广播 ANR 拆炸弹
- ANR 炸弹爆炸
- 广播ANR发生时有用的日志
我们知道广播有多种类型,大致可分为有序广播
,并行广播
,本地广播
等。其中只有有序广播才有 ANR 的逻辑,并行广播没有这个 ANR 的逻辑。且并行广播的处理优先级比有序的高。
而有序广播又分为前台广播 和后台广播 ,这两种发生 ANR 的阈值是不一样的,前台广播 10s,后台广播 60s,如下代码:
java
// 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;
在 AMS 初始化时,会构建两个不同 BroadcastQueue
,分别存储前台和后台广播,如下:
java
mBroadcastQueues[BROADCAST_QUEUE_FG] = new
BroadcastQueueImpl(this, mHandler,"foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
mBroadcastQueues[BROADCAST_QUEUE_BG] = new
BroadcastQueueImpl(this, mHandler,"background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
其中:
mHandler
是MainHandler
类型,里面使用Looper
是 AMS 中创建的名字为"ActivityManager"
的HandlerThread
线程foreConstants
和backConstants
是每个BroadCastReceiver
的超时时间,分别是:前台10s
,后台60s
,具体可以看上面的代码定义
广播处理的大致逻辑
我们知道每次广告的发送是通过调用 Context 的 sendBroadcast
,最终会走到 AMS,一路调用到 BroadcastQueueImpl
(低版本可能有差异)的 processNextBroadcastLocked
方法,这个方法就是处理广播的地方
java
public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
BroadcastRecord r;
// 首先立即处理并行广播,并没有ANR逻辑
while (mParallelBroadcasts.size() > 0) {
r = mParallelBroadcasts.remove(0);
r.dispatchTime = SystemClock.uptimeMillis();
r.dispatchRealTime = SystemClock.elapsedRealtime();
r.dispatchClockTime = System.currentTimeMillis();
r.mIsReceiverAppRunning = true;
final int N = r.receivers.size();
for (int i=0; i<N; i++) {
Object target = r.receivers.get(i);
.....
// 分发处理并行广播
deliverToRegisteredReceiverLocked(r,
(BroadcastFilter) target, false, i);
}
addBroadcastToHistoryLocked(r);
.....
}
boolean looped = false;
// 处理有序广播
do {
final long now = SystemClock.uptimeMillis();
r = mDispatcher.getNextBroadcastLocked(now);
.......
boolean forceReceive = false;
// 该广播所有的接受者总数
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))) {
Slog.w(TAG, "Hung broadcast ["
+ mQueueName + "] discarded after timeout failure:"
+ " now=" + now
+ " dispatchTime=" + r.dispatchTime
+ " startTime=" + r.receiverTime
+ " intent=" + r.intent
+ " numReceivers=" + numReceivers
+ " nextReceiver=" + r.nextReceiver
+ " state=" + r.state);
// 广播发生ANR超时
broadcastTimeoutLocked(false); // forcibly finish this broadcast
forceReceive = true;
r.state = BroadcastRecord.IDLE;
}
}
// 当前的广播的所有接受者处理完成之后执行
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
// Send the final result if requested
if (r.resultTo != null) {
boolean sendResult = true;
........
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
// 取消ANR检测
cancelBroadcastTimeoutLocked();
......
continue;
}
.........
} while (r == null);
// 获取下一个receiver...
int recIdx = r.nextReceiver++;
// 改receiver开始处理的时间,也是ANR计时的起始时间
r.receiverTime = SystemClock.uptimeMillis();
r.scheduledTime[recIdx] = r.receiverTime;
........
// 如果没有启动ANR检测的任务,就启动一个检测任务
if (! mPendingBroadcastTimeoutMessage) {
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
......
setBroadcastTimeoutLocked(timeoutTime);
}
final BroadcastOptions brOptions = r.options;
final Object nextReceiver = r.receivers.get(recIdx);
// 如果是当前的广播接受者是动态注册的
if (nextReceiver instanceof BroadcastFilter) {
......
BroadcastFilter filter = (BroadcastFilter)nextReceiver;
// 执行广播接受者
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
if ((r.curReceiver == null && r.curFilter == null) || !r.ordered) {
r.state = BroadcastRecord.IDLE;
// 执行下一个
scheduleBroadcastsLocked();
}
......
return;
}
// 静态广播
final ResolveInfo info =
(ResolveInfo)nextReceiver;
.......
// 应用进程是否启动,如果启动,则直执行
if (app != null && app.getThread() != null && !app.isKilled()) {
try {
app.addPackage(info.activityInfo.packageName,
info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
maybeAddBackgroundStartPrivileges(app, r);
r.mIsReceiverAppRunning = true;
processCurBroadcastLocked(r, app);
return;
}
......
}
// 如果没有启动,则先启动进程,并把当前的pending起来
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(), r.getHostingRecordTriggerType()),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
......
maybeAddBackgroundStartPrivileges(r.curApp, r);
mPendingBroadcast = r;
mPendingBroadcastRecvIndex = recIdx;
}
总结:
- 先处理
mParallelBroadcasts
里面的并行广播,会遍历所有的并行广播,并一一执行 - 使用一个 do while 的循环处理有序广播,判断这个广播的总耗时有没有超
(2 * mConstants.TIMEOUT * numReceivers)
,如果超时了,则依然会 ANR - 判断当前的广播的所有接受者是否处理完 ,如果处理完,则通过
cancelBroadcastTimeoutLocked
取消没有执行的 ANR 检测任务 - 依次执行当前有序广播的每个
BroadCastReceiver
,如果是动态注册的接收者,则与APP 进程交互执行onReceiver
的逻辑 - 如果是静态接收者,如果 app 进程启动了,则直接和 APP 进程交互执行
BroadCastReceiver
的onReceiver
的逻辑,如果没有启动,则先启动进程,并 pending 当前的广播,等待进程启动后执行
广播发生ANR有两种情况:
- 一种是总时间耗时,超过
(2 * mConstants.TIMEOUT * numReceivers)
- 一种是单个
Receiver
耗时,超过mConstants.TIMEOUT
广播中不同的时间检测处理机制
看上面的代码一直有个问题,我刚开始一直没有看明白:搜了整个代码,发现只有一个地方设置调用了 cancelBroadcastTimeoutLocked
,而且是在一个广播 BroadCastRecord
的所有接受者执行完成后 ,才调用cancelBroadcastTimeoutLocked
取消的。
为什么不和 Service
的一样呢:Service 的检测是启动 每个 Service之前会设置一个 ANR 的检测任务,执行完 Service 之后,就会取消改检测任务?每个 BroadCastReceiver执行之前设置检测,执行完成后取消检测任务呢?
是的,虽然广播和 Service
都是通过埋定时炸蛋的方式检测 ANR,但是关于时间定时检测处理这块确实是不一样的。总结起来就是:
- 是在 第一个
BroadCastReceiver
执行之前设置一个 ANR 定时任务 - 任务不会被取消,而是等待时间到达后,会触发
broadcastTimeoutLocked
方法,检测当前的BroadCastReceiver
的目标 Timeout 时间,如果当前的时间比目标 TimeOut 小,则设置下一个超时时间,如此循环。 - 最后一个
BroadCastReceiver
执行完后,才取消定时任务
我想这里之所以这么处理,是因为每个广播的接受者可以是多个,如果每个BroadCastReceiver
都在执行前开始任务,执行后取消任务,会带来性能上的影响,毕竟大部分是不耗时,不会 ANR 的。
对于定时检测类的任务,相比于先创建定时任务,然后判断如果没有达到阈值取消任务 这种机制来说,类似广播这种处理机制能提高性能,比如字节,钉钉都是利用这个做法获取耗时堆栈的。
广播 ANR 埋炸弹
从上面的逻辑可知,执行 BroadCastReceiver
之前,会判断是否设置过 ANR,也就是是否埋过炸弹,如果没有,则调用 setBroadcastTimeoutLocked
设置 ANR 检测任务,如下:
java
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
// 这里的timeoutTime就是用当前时间+阈值
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}
总结下: 这个逻辑比较简单,就是设置一个延时任务,延时的时间是用 BroadCastReceiver 的接受时间加上阈值,也就是long timeoutTime = r.receiverTime + mConstants.TIMEOUT
广播 ANR 拆炸弹
拆炸弹的逻辑也比较简单,会调用cancelBroadcastTimeoutLocked
取消延时检测任务,如下:
java
final void cancelBroadcastTimeoutLocked() {
if (mPendingBroadcastTimeoutMessage) {
mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
mPendingBroadcastTimeoutMessage = false;
}
}
ANR 炸弹爆炸
如果任务没有取消,都会执行BROADCAST_TIMEOUT_MSG
的消息,这个消息会执行broadcastTimeoutLockedcd
方法
java
final void broadcastTimeoutLocked(boolean fromMsg) {
.......
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastTimeoutLocked()");
try {
long now = SystemClock.uptimeMillis();
BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
if (fromMsg) {
......
// 如果当前的receiver时间大于超时时间,则又开始设置下一个的超时时间
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
if (timeoutTime > now) {
.....
// 又开始设置下一个的超时时间
setBroadcastTimeoutLocked(timeoutTime);
return;
}
}
......
long timeoutDurationMs = now - r.receiverTime;
Slog.w(TAG, "Timeout of broadcast " + r + " - curFilter=" + r.curFilter
+ " curReceiver=" + r.curReceiver + ", started " + timeoutDurationMs
+ "ms ago");
r.receiverTime = now;
.....
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;
}
// 打印time out的日志和event log
Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
logBroadcastReceiverDiscardLocked(r);
......
// 移动到下一个receiver.
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
//触发ANR处理
if (!debugging && app != null) {
mService.appNotResponding(app, timeoutRecord);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
总结下上面的逻辑:
- 判断当前的
active
接受者是否发生了 ANR,如果当前时间大于 ANR 时间,则重新设置下一次 ANR 的时间 - 如果当前时间大于 ANR 时间,则打印
Timeout of broadcast
的日志,并打印event
日志 - 结束当前的
Receiver
,并设置参数移动到一下一个Receiver
- 调用
mService.appNotResponding
处理 ANR 逻辑
广播ANR发生时有用的日志
1、如果广播总耗时超时会发生ANR,会有一句这样的日志:
java
Slog.w(TAG, "Hung broadcast ["
+ mQueueName + "] discarded after timeout failure:"
+ " now=" + now
+ " dispatchTime=" + r.dispatchTime
+ " startTime=" + r.receiverTime
+ " intent=" + r.intent
+ " numReceivers=" + numReceivers
+ " nextReceiver=" + r.nextReceiver
+ " state=" + r.state);
2、单个广播耗时的关键日志
java
Slog.w(TAG, "Timeout of broadcast " + r + " - curFilter=" + r.curFilter
+ " curReceiver=" + r.curReceiver
+ ", started " + timeoutDurationMs+ "ms ago");
3、打印当前超时的广播实例
java
Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
4、输出event日志
scss
am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
我们可以结合这些日志,可以得出Receiver的开始的时间,发生ANR的时间,那个Receiver对象发生anr