Android 广播机制浅析【4】广播 ANR

本分对广播分发的流程中的超时逻辑进行分析。

广播 ANR 只存在于有序广播中,无序广播并不会触发 ANR。其中 ANR 有两处可以触发,接收者处理广播超时和该广播处理超时。

广播分发超时

这个是指分发这个广播总时间超时,其逻辑在 BroadcastQueue#processNextBroadcastLocked 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//如果系统已经启动完成,广播没有设置超时豁免并且已经有接收者处理过该广播
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);
broadcastTimeoutLocked(false); // forcibly finish this broadcast
forceReceive = true;
r.state = BroadcastRecord.IDLE;
}
}

r.dispatchTime 的赋值也是在 processNextBroadcastLocked 中,在第一次分发有序广播给接收者的时候:

1
2
3
4
r.receiverTime = SystemClock.uptimeMillis();
if (recIdx == 0) {
r.dispatchTime = r.receiverTime;
}

这里也可以发现当总耗时超时后会直接出发 ANR。

接收者处理超时

这个就是平时遇到的最多的一种情况,同样是在 BroadcastQueue#processNextBroadcastLocked 中:

1
2
3
4
5
6
7
8
9
10
11
12
if (! mPendingBroadcastTimeoutMessage) {
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
setBroadcastTimeoutLocked(timeoutTime);
}

final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}

通过 mPendingBroadcastTimeoutMessage 来判断是否已经发送了超时消息,这里延迟时间就是该接收者的接受时间加超时时间(后台广播为 10s,前台广播为 60s)。

触发 ANR

如果在规定时间内一个接收者有处理完成,则下一个接收者处理时并不会重新开始计时,而是会在 BROADCAST_TIMEOUT_MSG 消息被处理的时候,即 BroadcastQueue#broadcastTimeoutLocked 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
final void broadcastTimeoutLocked(boolean fromMsg) {
if (fromMsg) {
mPendingBroadcastTimeoutMessage = false; //此时可以再次设置超时
}
long now = SystemClock.uptimeMillis();
BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
if (fromMsg) {
if (!mService.mProcessesReady) return; //如果系统开没启动完成则返回
if (r.timeoutExempt) return; // 如果有设置超时豁免则返回
//重新计算超时时间,如果大于当前时间,说明已经有新的接收者在处理,因此这个超时无效
//重新通过 setBroadcastTimeoutLocked 来发送超时消息
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
if (timeoutTime > now) {
setBroadcastTimeoutLocked(timeoutTime);
return;
}
}

//如果此时广播还处理等待后台服务的状态,则直接处理下一个广播
if (r.state == BroadcastRecord.WAITING_SERVICES) {
Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
? r.curComponent.flattenToShortString() : "(null)"));
r.curComponent = null;
r.state = BroadcastRecord.IDLE;
processNextBroadcast(false);
return;
}
anrMessage = "Broadcast of " + r.intent.toString();
}

if (mPendingBroadcast == r) {
mPendingBroadcast = null;
}
// 结束当前接收者的处理,开始分发给下一个接收者
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
//通过 AMS 来触发 ANR
if (!debugging && anrMessage != null) {
mService.mAnrHelper.appNotResponding(app, anrMessage);
}
}

广播 ANR 分析

如之前的流程,有序广播在接受过程中存在跨进程通信,还需要通过接收者进程的 Handler 来进行分发,并且最主要的是流程还在 AMS 时就已经开始了超时的计时,因此这个过程中只要有一个地方有耗时,即使此时 onReceive() 还没有被调用,也会有 ANR 发生。并且原生逻辑中关键部分的 Log 都是由开关控制,如果不是应用自身的逻辑问题,那么是很难定位具体卡在了哪一个环节,这里也可以借助 systrace 或 perfetto 来辅助分析。