前言:
ANR系列文章一共有有若干篇,
遵循这样的一个顺序:
1.先讲ANR的基本概念以及发生后的流程;
2.四种类型的ANR是如何发生的;
3.该如何排查和解决ANR类型问题。
想看整个系列的文章,可以参考该系列文章第一篇,里面会有明确的清单:
ANR系列之一:ANR显示和日志生成原理讲解
本篇是ANR系列文章的第五篇,本文主要讲解service类型的ANR类型是如何发生的。
本文主要讲解内容如下:
1.service类型的ANR在系统侧是如何触发的;
2.service类型的ANR在APP侧的执行流程。
3.什么场景下,可以触发service类型的ANR。
PS:阅读本文前,建议阅读下面的文章,做好知识储备,方便本文的理解。
https://blog.csdn.net/rzleilei/article/details/128452528
一.Service类型ANR如何触发?
1.1 Service类型ANR的触发点
首先,我们看一下service类型的ANR触发点在哪里。
之前讲过,所有类型的ANR,最终都会通知到ANRHelper这个类的appNotResponding方法,service类型的自然也不例外。
所以最终的触发点在ActiveService类的serviceTimeout方法中:
void serviceTimeout(ProcessRecord proc) {
...
anrMessage = "executing service " + timeout.shortInstanceName;
...
if (anrMessage != null) {
mAm.mAnrHelper.appNotResponding(proc, anrMessage);
}
}
1.2 Service类型ANR的触发流程
serviceTimeout的调用流程其实也是很简单的,其核心就是一个延时消息机制。
Active注册一个延时消息,然后继续后面的流程,通知APP去执行对应的流程,APP执行完成后通知回系统,进行对应的延时消息的取消。
如果APP侧因为各种原因超时,没有按照的通知回APP,则就会发生service类型的ANR。
这里超市时间有两个配置,区分前台service还是后台service。
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
前台的超时时间为20S,后台为200S。
1.3 何时触发超时检查流程
整个Service启动流程中,在APP侧其实有多个生命周期的回调,每一个都有可能会超时,所以自然的,整个启动流程中,超时检查的机制并不止一次。
首先,在通知APP侧去创建Service时,会触发第一次的超时检查。
具体方法在realStartServiceLocked中,如下:
private void realStartServiceLocked(...) throws RemoteException {
//第一次超时检查
bumpServiceExecutingLocked(r, execInFg, "create", null /* oomAdjReason */);
//通知APP去创建,创建完成后APP会通知回系统侧取消注册超时消息
thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
app.mState.getReportedProcState());
//第二次超时检查
sendServiceArgsLocked(r, execInFg, true);
}
其次,通知APP侧执行onStartCommand流程时,也会触发一次超时检查。
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
boolean oomAdjusted) throws TransactionTooLargeException {
//第二次超时检查
bumpServiceExecutingLocked(r, execInFg, "start", null /* oomAdjReason */);
//通知到APP一侧
r.app.getThread().scheduleServiceArgs(r, slice);
}
也就是说,系统侧在通知APP侧之前,会提前发送一个延时消息。如果APP正常完成了流程,则会通知回系统侧结束掉这个延时的消息,就不会触发ANR的流程了。
我们接下来,就看下APP侧收到通知后的流程。
二.APP侧如何处理的
2.1 service创建的回调
之前的文章介绍过,APP侧接受创建通知的是ApplicationThread对象中的scheduleCreateService方法,最终交给ActivityThread中的handleCreateService方法来处理。
private void handleCreateService(CreateServiceData data) {
//完成创建service
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
//调用service的attach方法
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
//调用service的onCreate方法
service.onCreate();
//通知回系统侧
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
最终,通过serviceDoneExecuting方法通知回系统侧。serviceDoneExecuting方法具体如何执行取消注册流程的,我们2.3来讲。
2.2 service中onStartCommand生命周期的回调
APP侧最终处理scheduleServiceArgs的方法是ActivityThread中的handleServiceArgs方法:
private void handleServiceArgs(ServiceArgsData data) {
CreateServiceData createData = mServicesData.get(data.token);
Service s = mServices.get(data.token);
if (s != null) {
try {
...
int res;
if (!data.taskRemoved) {
res = s.onStartCommand(data.args, data.flags, data.startId);
} else {
s.onTaskRemoved(data.args);
res = Service.START_TASK_REMOVED_COMPLETE;
}
...
try {
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
"Unable to start service " + s
+ " with " + data.args + ": " + e.toString(), e);
}
}
}
}
我们可以看到,调用了service的onStartCommand方法之后,也调用了serviceDoneExecuting方法进行了回调通知。
2.3 serviceDoneExecuting回调通知
我们看下ActivityManagerService中的serviceDoneExecuting方法:
public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
synchronized(this) {
if (!(token instanceof ServiceRecord)) {
Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
throw new IllegalArgumentException("Invalid service token");
}
mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false);
}
}
完全委托给ActiveService来处理的。
serviceDoneExecutingLocked中的逻辑有些复杂,我这精简一下,只保留最核心的。
就是取消超时类型消息注册的代码,如下:
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing, boolean enqueueOomAdj) {
...
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
...
}
三.Service类型ANR实例
按照上面的分析,我们知道有两种场景都会导致service类型的ANR触发。
分别是:
1.service创建流程
2.service执行onStartCommand生命周期流程。
当然这两步只是流程,具体导致的原因又可能会有很多了。如果service是首次创建的话,那么还会创建Application,Application创建耗时一样会导致Service类型的ANR事件。
我们这里做两个例子,验证下我们的猜测:
例子1:service的onCreate中进行耗时操作
代码如下:
public class ThreadService extends IntentService {
@Override
public void onCreate() {
super.onCreate();
Log.i("ThreadService", "ThreadService onCreate");
try {
Thread.sleep(60_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后通过代码启动:
val intent = Intent(this, ThreadService::class.java)
startService(intent)
这里使用的是startService方法,而不是startForegroundService,如果后台启动的话,就需要休眠200S以上了。
最终实验结果,果然产生了service类型的ANR。
例子2:service的onStartCommand中进行耗时操作
代码如下:
public class ThreadService extends IntentService {
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.i("ThreadService", "onStartCommand");
try {
Thread.sleep(60_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return super.onStartCommand(intent, flags, startId);
}
}
同样启动service,果然也发现出现了Service类型的ANR。