ANR是如何产生之Service和Broadcast篇
ANR类型
类型 | 超时时间 | 是否有弹窗提示 |
---|---|---|
Activity Timeout | 10s | 有提示 |
BroadcastReceiver Timeout | 10s, 60s | 无感知场景不会提示,如在后台 |
Service Timeout | 20s, 200s | 无感知场景不会提示 |
ContentProvider Timeout | 10s | 无感知场景不会提示 |
InputDispatching Timeout | 5s | 有提示 |
之前的文章,我们分析了Input ANR
是如何产生的,具体可以参考:[ANR] Input ANR是怎么产生的
今天这篇文章,我们来讲讲BroadcastReceiver
和 Service Timeout
ANR分别是怎样产生的。
系统检测ANR的核心原理如下:
- 在流程开始时,启动一个计时器,比如往
MessageQueue
加入一个定时执行的Message
- 如果流程在规定的时间内结束,则关闭计时器,比如移除这个
Message
- 如果流程未按时结束,执行该
Message
,触发ANR
Service Timeout
1. 启动Service时,设置计时器
Service
启动的流程,省略zygote fork
进程的流程,最后会在system_server
进程中调用到ActiveServices
中的realStartServiceLocked
方法。
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
// 发送delay消息(SERVICE_TIMEOUT_MSG)
bumpServiceExecutingLocked(r, execInFg, "create");
try {
// 跨进程调用,调用Service所在进程的ActivityThread中的方法
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
} catch (DeadObjectException e) {
}
}
在这个方法里,主要做了两件事情:
- 发送
SERVICE_TIMEOUT_MSG
,delay
时间为SERVICE_TIME_OUT
- 跨进程调用,调用
Service
所在进程的ActivityThread
中的Service初始化方法
bumpServiceExcutingLocked
就是往MesageQueue
里放一个定时执行的Message
。
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
// 当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程
mAm.mHandler.sendMessageAtTime(msg,
proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}
这个定时执行的Message
的执行时间是,当前时间加上SERVICE_TIME_OUT
。
SERVICE_TIME_OUT
有两种情况:
- 如果是前台
Service
:SERVICE_TIMEOUT = 20s
- 如果是后台
Service
:SERVICE_BACKGROUND_TIMEOUT = 200s
2. Service执行完,移除计时器
在Service
所在进程的ActivityThread
中,执行Service
的初始化方法。
private void handleCreateService(CreateServiceData data) {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
Service service = (Service) cl.loadClass(data.info.name).newInstance();
try {
//创建ContextImpl对象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
//创建Application对象
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//调用服务onCreate()方法
service.onCreate();
// 通知system_server,service启动完成
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (Exception e) {
...
}
}
从上面的方法可以看到,当Service
在进程中初始化完成后,会通过跨进程调用,通知system_server
。
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {
// 移除SERVICE_TIMEOUT_MSG
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
}
3. 如果未按时执行完,执行Message
final class MainHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case SERVICE_TIMEOUT_MSG: {
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
}
}
}
在AMS
的MainHandler
中,会处理SERVICE_TIMEOUT_MSG
,调用ActiveService
的serviceTimeout
方法。
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;
synchronized(mAm) {
if (timeout != null && mAm.mLruProcesses.contains(proc)) {
// 拼接anr信息
anrMessage = "executing service " + timeout.shortName;
}
}
if (anrMessage != null) {
//当存在timeout的service,执行appNotResponding
mAm.appNotResponding(proc, null, null, false, anrMessage);
}
}
在这个方法里,主要完成拼接ANR信息,以"executing service
"开头,同时调用AMS
的appNotResponding
处理后续的dump和弹窗逻辑。
appNotResponding
的处理和采集信息流程,可以参考这篇文章:[ANR] 发生ANR后的信息采集过程
Broadcast Receiver Timeout
广播分为有序广播和无序广播。
对于无序广播,遍历所有的广播接收者并发送,不关心接收者是否处理完广播消息。
有序广播,需要等上一个接收者处理完之后,才能发送给后续的接收者,所以会发生ANR。
1. 发送广播时,设置计时器
发送广播的流程,最后会调用processNextBroadcastLocked
来处理广播。这个方法特别长,我们只截取其中需要的部分。
final void processNextBroadcast(boolean fromMsg) {
synchronized(mService) {
...
// 处理当前有序广播
do {
r = mOrderedBroadcasts.get(0);
// 获取该广播所有的接收者
int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
if (mService.mProcessesReady && r.dispatchTime > 0) {
long now = SystemClock.uptimeMillis();
if ((numReceivers > 0) &&
(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
//当广播处理时间超时,则强制结束这条广播
broadcastTimeoutLocked(false);
}
}
// 当广播分发结束,调用了finishReceiverLocked后,r.receivers为null
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
if (r.resultTo != null) {
//处理广播消息消息
performReceiveLocked(r.callerApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false, r.userId);
r.resultTo = null;
}
// 移除MSG
cancelBroadcastTimeoutLocked();
}
} while (r == null);
r.receiverTime = SystemClock.uptimeMillis();
if (!mPendingBroadcastTimeoutMessage) {
long timeoutTime = r.receiverTime + mTimeoutPeriod;
// 设置广播的超时MSG
setBroadcastTimeoutLocked(timeoutTime);
}
// 处理动态广播
if (nextReceiver instanceof BroadcastFilter) {
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
return;
}
// 省略处理静态广播的流程
}
}
有两种情况会引发BroadcastReceiver的超时:
- 某个广播的总处理时长 > 2 * receiver总个数 + mTimeoutPeriod
- 某个receiver的处理市场超过mTimeoutPeriod
setBroadcastTimeoutLocked的原理就是往Handler里发送一个BROADCAST_TIMEOUT_MSG。
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}
2. 移除计时器
cancelBroadcastTimeoutLocked
的调用时机是,广播结束后,调用AMS
的finishReceiver
方法,然后调用nextReceiver
,最后调用到processNextBroadcastLocked
方法中。
因为这个时候,r.receivers
为null
,所以会执行cancelBroadcastTimeoutLocked
方法。
总结
Service Timeout ANR
发生原理:
- 启动
Service
时,发送一个延时执行的SERVICE_TIMEOUT_MSG
- 如果
Service
在规定的时间内启动,则移除SERVICE_TIMEOUT_MSG
- 如果
Service
没有在规定的时间内启动,则执行SERVICE_TIMEOUT_MSG
注意:以上判断ANR
的逻辑,都在system_server
进程中执行。