avatar

目录
Android中ANR的触发机制-Service篇

个人博客

http://www.milovetingting.cn

Android中ANR的触发机制-Service篇

概述

ANR,即Application Not Responding,应用程序不响应。在Android系统中,对于事件的处理,都需要在一定的时间内完成,如果处理超时的话,就会触发ANR,弹出不响应的界面,让用户选择等待或是立即结束应用。ANR机制的简单流程:在事件发给应用处理前,会先发一个延时消息到系统的Looper中,如果应用在规定的时间内执行完成,则会移除掉延时消息。如果没有在规定时间内执行完,就会在处理延时消息中,触发ANR。

ANR主要场景:

  1. Service

  2. BroadcastReceiver

  3. ContentProvider

  4. Input:包括输入和触摸

触发机制分析

下面对Service进行源码分析,源码为Android9.0。

首先从Service的启动来分析。这里只分析startService的模式,bindService模式暂时不分析。

附一张时序图

Service的ANR机制

不管是通过Activity的startService还是非Activity的Context中的startService,最终都是调用ContextWrapper的startService方法:

java
1
2
3
4
5
//ContextWrapper
@Override
public ComponentName startService(Intent service) {
return mBase.startService(service);
}

mBase对应的具体Context类为ContextImpl。ContextImpl的startService方法:

java
1
2
3
4
5
6
//ContextImpl
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}

startService方法调用startServiceCommon方法

java
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
//ContextImpl
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
} else if (cn.getPackageName().equals("?")) {
throw new IllegalStateException(
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

在这个方法里,跨进程调用AMS的startService方法

java
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
//AMS
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage, int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}

if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}

if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
synchronized(this) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
//调用ActiveServices的startServiceLocked方法
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
requireForeground, callingPackage, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
return res;
}
}

AMS中调用ActiveServices的startServiceLocked方法:

java
1
2
3
4
5
6
7
8
//ActiveServices
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
throws TransactionTooLargeException {
//...
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
return cmp;
}

这个方法又调用了startServiceInnerLocked方法:

java
1
2
3
4
5
6
7
8
9
10
//ActiveServices
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
//...
String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
if (error != null) {
return new ComponentName("!!", error);
}
//...
}

这个方法调用bringUpServiceLocked方法:

java
1
2
3
4
5
6
7
8
//ActiveServices
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting, boolean permissionsReviewRequired)
throws TransactionTooLargeException {
//...
realStartServiceLocked(r, app, execInFg);
//...
}

这个方法里调用realStartServiceLocked方法:

java
1
2
3
4
5
6
7
8
9
10
11
//ActiveServices
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
//...
bumpServiceExecutingLocked(r, execInFg, "create");
//...
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
//...
}

这里先调用了bumpServiceExecutingLocked方法用来设置超时消息:

java
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
43
44
45
46
47
48
49
//ActiveServices
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
+ why + " of " + r.shortName);

// For b/34123235: Services within the system server won't start until SystemServer
// does Looper.loop(), so we shouldn't try to start/bind to them too early in the boot
// process. However, since there's a little point of showing the ANR dialog in that case,
// let's suppress the timeout until PHASE_THIRD_PARTY_APPS_CAN_START.
//
// (Note there are multiple services start at PHASE_THIRD_PARTY_APPS_CAN_START too,
// which technically could also trigger this timeout if there's a system server
// that takes a long time to handle PHASE_THIRD_PARTY_APPS_CAN_START, but that shouldn't
// happen.)
boolean timeoutNeeded = true;
if ((mAm.mBootPhase < SystemService.PHASE_THIRD_PARTY_APPS_CAN_START)
&& (r.app != null) && (r.app.pid == android.os.Process.myPid())) {

Slog.w(TAG, "Too early to start/bind service in system_server: Phase=" + mAm.mBootPhase
+ " " + r.getComponentName());
timeoutNeeded = false;
}

long now = SystemClock.uptimeMillis();
if (r.executeNesting == 0) {
r.executeFg = fg;
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
}
if (r.app != null) {
r.app.executingServices.add(r);
r.app.execServicesFg |= fg;
if (timeoutNeeded && r.app.executingServices.size() == 1) {
scheduleServiceTimeoutLocked(r.app);
}
}
} else if (r.app != null && fg && !r.app.execServicesFg) {
r.app.execServicesFg = true;
if (timeoutNeeded) {
scheduleServiceTimeoutLocked(r.app);
}
}
r.executeFg |= fg;
r.executeNesting++;
r.executingStart = now;
}

这个方法中调用scheduleServiceTimeoutLocked方法:

java
1
2
3
4
5
6
7
8
9
10
11
//ActiveServices
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}

调用ActivityManagerService的MainHandler发送一个SERVICE_TIMEOUT消息,这里具体根据是否为前台消息发送的消息不同。

前台服务超时时间:20s,后台服务超时时间:200s

java
1
2
3
4
5
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;

// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

设置超时消息就先看到这里,接着看启动Service

调用ProcessRecord中的IApplicationThread类型的thread属性的scheduleCreateService方法,即调用到了ActivityThread的内部类ApplicationThread的scheduleCreateService方法:

java
1
2
3
4
5
6
7
8
9
10
11
//ActivityThread$ApplicationThrad
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;

sendMessage(H.CREATE_SERVICE, s);
}

这个方法里调用了ActivityThread的sendMessage方法:

java
1
2
3
4
//ActivityThread
void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}

最终通过Handler发送了一个消息出去

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Handler
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+ ": " + arg1 + " / " + obj);
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}

在mH的handleMessage中回调处理:

java
1
2
3
4
5
6
//ActivityThread$H.handleMessage
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
java
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
43
44
45
46
47
48
//ActivityThread
private void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();

LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}

try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);

Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
//完成Service的创建,并回调onCreate方法
service.onCreate();
mServices.put(data.token, service);
try {
//通知移除延时消息
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}

如果在规定的时间内完成处理,则会调用AMS的serviceDoneExecuting:

java
1
2
3
4
5
6
7
8
9
10
//AMS
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);
}
}

这个方法调用ActiveServices中的serviceDoneExecutingLocked方法:

java
1
2
3
4
5
6
//ActiveServices
void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
//...
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
//...
}

这个方法会调用另一个重载方法:

java
1
2
3
4
5
6
7
//ActiveServices
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing) {
//...
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
//...
}

在这个方法里移除了前面的延时消息,就不会触发ANR。

如果没有及时移除这个消息,那么将会在ActivityManagerService的MainHandler中触发:

MainHandler收到消息的处理:

java
1
2
3
4
5
6
7
8
9
10
11
//ActivityManagerService$MainHandler.handleMessage
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
//...
case SERVICE_TIMEOUT_MSG: {
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
//...
}
}

这个方法调用ActiveServices中的serviceTimeout方法:

java
1
2
3
4
5
6
void serviceTimeout(ProcessRecord proc) {
//...
if (anrMessage != null) {
mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
}
}

在这里弹出了不响应的界面。

文章作者: milovetingting
文章链接: http://www.milovetingting.cn/2020/03/16/Android/Android%E4%B8%ADANR%E7%9A%84%E8%A7%A6%E5%8F%91%E6%9C%BA%E5%88%B6-Service%E7%AF%87/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 milovetingting

评论