主要参考文章:看完这篇 Android ANR 分析,就可以和面试官装逼了!
补充参考文章:理解Android ANR的触发原理
文中涉及到的源码基于 Android API 25
1、概述
ANR(Application Not Responding),指应用程序无响应。当某些情况下,应用主线程超过预定时间能未能得到有效响应或者响应时间过长,都会造成 ANR。
ANR 机制主体实现在系统层。所有与ANR相关的消息,都会经过系统进程 (system_server) 调度,然后派发到应用进程完成对消息的实际处理。
有以下四种场景会导致 ANR:
(1)Service Timeout: Service 在特定的时间内无法处理完成
(2)BroadcastQueue Timeout:BroadcastReceiver 在特定时间内无法处理完成
(3)ContentProvider Timeout:内容提供者执行超时
(4)inputDispatching Timeout: 按键或触摸事件在特定时间内无响应
发生 ANR 时会调用 AppNotRespondingDialog#show()
方法弹出对话框提示用户,而 AppErrors#appNotResponding()
方法是最终弹出 ANR 对话框的唯一入口。
2、ANR 机制
ANR 机制的实现主要分为两个部分:
1、ANR 监测机制:Android 对于不同的 ANR 类型 (Broadcast, Service, InputEvent) 都有一套监测机制。
2、ANR 报告机制:在监测到 ANR 以后,需要显示 ANR 对话框、输出日志(发生ANR 时的进程函数用栈、CPU使用情况等)。
前面说了AppErrors#appNotResponding()
方法是最终弹出 ANR 对话框的唯一入口,而不同 ANR 场景,最终都是在 AMS 进程中的 ServiceThread 线程中被调用的(只不过不同场景实现调用的手段不同,可能有些是跨进程的,此时是基于 Binder 机制的)。简单的说,就是 ANR 报告机制是在 AMS 进程中的 ServiceThread 线程中被执行的。
但是关于 ANR 监测,则暂时无法下结论,因为本文当前只探究了 Service 超时的监测机制的实现。不过,可以肯定的是,都是基于 Handler 以及消息队列来实现的,即在开始监测前通过 handler 在消息队列中存放一个指定时间的 msg,如果在指定时间范围内没有移除该 msg,则当取出该 msg 处理的时候,就表示要触发 ANR 了。(暂时无法下定论的是 “是不是都是基于 AMS 的 ServiceThread 线程对应的 Handler”
)
2.1 ANR 监测机制
关于 Service 超时监测机制的实现
先说下结论,对于应用程序的 Service ANR 监测,是在 AMS 所在进程的 ServiceThread 中实现的。
Service 的启动流程大致如下:
截图自参考文章
具体的源码细节不深究,简单的说 Service 的启动是基于 AMS 进程与应用进程通信实现的(本质上基于 Binder 机制实现)。
看到 ActiveServices#realStartServiceLocked()
(代码有删减)
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
...
// 主要是为了设置 ANR 超时,可以看出在正式启动 Service 之前开始 ANR 监测
bumpServiceExecutingLocked(r, execInFg, "create");
mAm.updateLruProcessLocked(app, false, null);
mAm.updateOomAdjLocked();
boolean created = false;
try {
...
// 启动 Service,最终会跨进程调用目标应用的 Service#onCreate() 方法
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
r.postNotification();
created = true;
} catch (DeadObjectException e) {
...
} finally {
if (!created) {
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
// 在 serviceDoneExecutingLocked 中会移除在 bumpServiceExecutingLocked() 中设置的
// ActivityManagerService.SERVICE_TIMEOUT_MSG 的 msg
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
...
}
}
if (r.whitelistManager) {
app.whitelistManager = true;
}
// bind 过程中
requestServiceBindingsLocked(r, execInFg);
...
// 调用 Service#onStartCommand(),也是基于 Binder 进行进程通信
sendServiceArgsLocked(r, execInFg, true);
...
}
app.thread.scheduleCreateService()
会基于进程间通信,最终在应用进程主线程中调用 Service#onCreate()
。
而在执行 app.thread.scheduleCreateService()
之前,可以看到,会调用
bumpServiceExecutingLocked(r, execInFg, "create")
而在该方法里面会进一步调用:
scheduleServiceTimeoutLocked(r.app)
其具体实现为:
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
long now = SystemClock.uptimeMillis();
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
// (1) 前台进程中执行 Service,SERVICE_TIMEOUT = 20s;
// (2) 后台进程中执行 Service,SERVICE_BACKGROUND_TIMEOUT = 200s
// mAm.mHandler 为 MainHandler 类型,而 MainHandler 则与 ServiceThread 有关
// 在 ServiceThread 的消息队列中放置一个等待 SERVICE_TIMEOUT/SERVICE_BACKGROUND_TIMEOUT 时间的 msg
// 如果在到时之前没有移除该 msg,则会处理该 msg,从而触发 ANR 机制的处理
// 需要注意的是,这里的 ServiceThread 的消息队列是处于 AMS 进程中的
mAm.mHandler.sendMessageAtTime(msg,
proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}
可以看到,实现 ANR 超时监测的原理,就是在 ServiceThread 的消息队列中添加对应好的 msg,如果在特定时间后该 msg 还在队列中,且被取出处理了,则表示触发了 ANR。
需要注意的是,这里的 ServiceThread 并非指应用的,而是指 AMS 所处进程的一个子线程。 而且因为是在 AMS 的 ServiceThread 中循环遍历 msg,而 AMS 主线程会因跨进程通信阻塞住,所以对于监测 ANR 的 msg 在 ServiceThread 可以正常被取出或者移除,不会因为 AMS 主线程的阻塞而受到影响。
该子线程在 AMS 的构造方法中开启:
public ActivityManagerService(Context systemContext) {
...
mHandlerThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
// mUiHandler 才是对应于主线程的 handler
mUiHandler = new UiHandler();
...
}
因为这一系列操作都是 AMS 中进行的,只不过 app.thread.scheduleCreateService()
是基于 Binder 机制切换到了应用进程中执行的。但是该逻辑是阻塞的,需要等到应用进程中对应的逻辑执行完,这里的 app.thread.scheduleCreateService()
才会相应的执行完,才会执行之后的逻辑。因此应用进程中对应方法执行时间长,也就间接导致 AMS 中对应逻辑的阻塞等待。
这里就基本验证了前面的结论的一部分:对于应用程序的 Service 的 ANR 监测,是在 AMS 所在进程的 ServiceThread 中实现的。而实际上,其他几种 ANR 监测的实现,也同样符合该结论。
回到 Service 的创建流程,Service#onCreate()
最终是在 ActivityThread#handlerCreateService()
中被调用(即在应用进程的主线程中),在调用了 service.onCreate()
生命周期方法之后,即表示 Service 被正常创建,此时就会基于 Binder 机制回调 ActivityManagerService#serviceDoneExecuting()
。
private void handleCreateService(CreateServiceData data) {
...
try {
...
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) {
...
}
}
在 ActivityManagerService#serviceDoneExecuting()
中会进一步调用 ActiveServices#serviceDoneExecutingLocked()
。
看到这里,先暂时停一下,回到之前的 ActiveServices#realStartServiceLocked()
中,可以看到,在 finally 语句块中,也会调用 serviceDoneExecutingLocked()
:
if(!created) {
serviceDoneExecutingLocked(r, inDestroying, inDestroying)
}
该方法会满足条件的情况下移除 AMS ServiceThread 消息队列中对应的 ActivityManagerService.SERVICE_TIMEOUT_MSG
类型的 msg,即在有效时间范围内,如果移除了该 msg,则相当于结束了此次关于 ANR 的超时监测。
需要注意,是要在满足条件的情况下,并不是 onCreate()
完成就直接移除。
之后,关于 requestServiceBindingsLocked()
(对应于 Service#onBind()
)和 sendServiceArgsLocked()
(对应于 Service#onStartCommand()
)也是类似的处理逻辑。都会在生命周期方法开始前且在满足对应条件的情况下通过 bumpServiceExecutingLocked()
方法设置 ANR 监测,在流程结束后,不管成功与否,也会调用 serviceDoneExecutingLocked()
在满足对应条件的情况下移除对应流程的监测 msg。
为什么要强调 “满足对应条件” 呢? 因为对应 Service 的 ANR 监测,其监测的是一系列的生命周期方法,并不是单个生命周期方法单个的监测。
这个也可以通过实例去验证,直接通过 bindService 去绑定一个没没有被创建过的 Service,分别在
onCreate()
和onBind()
方法使主线程休眠,这个时候,当在两个方法中休眠的总时长超过 ANR 设置的时间,但是单个方法分别没有超过的时候,也会触发 ANR。
比如 bindService(BIND_AUTO_CREATE)
,这个时候如果 Service 还没被创建,则会先创建 Service 执行其 onCreate()
方法。这个时候,ANR 监测的就是 onCreate()
与 onBind()
方法。但是如果 bind 的时候 Service 已经创建了,这个时候就只会监测 onBind()
方法。
如果在 AMS ServiceThread 消息队列中轮询处理到了关于 ANR 监测的 msg,则需要进行响应的处理。 处理的逻辑在 AMS#MainHandler
中实现:
final class MainHandler extends Handler {
public MainHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case SERVICE_TIMEOUT_MSG: {
// 如果 mDidDexOpt == true,则延迟 ANR timeouts 时间
if (mDidDexOpt) {
mDidDexOpt = false;
Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG);
nmsg.obj = msg.obj;
mHandler.sendMessageDelayed(nmsg, ActiveServices.SERVICE_TIMEOUT);
return;
}
// 处理此次超时
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
...
}
}
可以看到,在处理的时候会进一步调用 ActiveServices#serviceTimeout()
,并且此时是处于 AMS ServiceThread 中的。
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;
synchronized(mAm) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
final long now = SystemClock.uptimeMillis();
final long maxTime = now -
(proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
ServiceRecord timeout = null;
long nextTime = 0;
// 查找运行超时的 service
for (int i=proc.executingServices.size()-1; i>=0; i--) {
ServiceRecord sr = proc.executingServices.valueAt(i);
if (sr.executingStart < maxTime) {
timeout = sr;
break;
}
if (sr.executingStart > nextTime) {
nextTime = sr.executingStart;
}
}
// 判断执行 Service 超时的进程是否在最近运行进程列表,如果不在,则忽略这个 ANR
if (timeout != null && mAm.mLruProcesses.contains(proc)) {
Slog.w(TAG, "Timeout executing service: " + timeout);
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 1024);
pw.println(timeout);
timeout.dump(pw, " ");
pw.close();
mLastAnrDump = sw.toString();
mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
anrMessage = "executing service " + timeout.shortName;
} else {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
}
}
if (anrMessage != null) {
// 调用 AppErrors#appNotResponding() 来处理 ANR 的后续逻辑
mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
}
}
可以看到,最终会在 ServiceThread 中调用 AppErrors#appNotResponding()
来处理后续逻辑。该部分逻辑就涉及到了 ANR 报告机制 。
对于 “对于应用程序的 Service ANR 监测,是在 AMS 所在进程的 ServiceThread 中实现的
” 这一结论,需要注意,是针对 Service 来说的,而其他类型的 ANR 监测机制,则不与此相同,是另外的实现逻辑。
2.2 ANR 报告机制
ANR 发生以后,正常情况下(Provider 在其进程启动时 publish 过程可能会出现 ANR, 则会直接杀进程以及清理相应信息,而不会弹出 ANR 的对话框)会调用 AppErrors#appNotResponding()
方法,最终的表现形式是:弹出一个对话框,告诉用户当前某个程序无响应;输出一大堆与ANR相关的日志,便于开发者解决问题。
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
...
if (ActivityManagerService.MONITOR_CPU_USAGE) {
// 1. 更新 CPU 使用信息。ANR 的第一次 CPU 信息采样,采样数据会保存在 mProcessStats 这个变量中
mService.updateCpuStatsNow();
}
// Unless configured otherwise, swallow ANRs in background processes & kill the process.
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
boolean isSilentANR;
synchronized (mService) {
...
// In case we come through here for the same app before completing
// this one, mark as anring now so we will bail out.
app.notResponding = true;
// Log the ANR to the event log.
// 2. 记录 ANR 到 EventLog 中
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
// Dump thread traces as quickly as we can, starting with "interesting" processes.
firstPids.add(app.pid);
// Don't dump other PIDs if it's a background ANR
isSilentANR = !showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID;
...
}
// Log the ANR to the main log.
// 3. 输出 ANR 信息到 main log
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(app.pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
}
// 4. 创建CPU tracker对象
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
String[] nativeProcs = NATIVE_STACKS_OF_INTEREST;
// don't dump native PIDs for background ANRs
File tracesFile = null;
// 5. 获取进程 trace 信息,保存到 /data/anr/traces.txt (会先删除老的文件)
if (isSilentANR) {
tracesFile = mService.dumpStackTraces(true, firstPids, null, lastPids,
null);
} else {
tracesFile = mService.dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
nativeProcs);
}
// 6. 记录 CPU 的相关信息
String cpuInfo = null;
if (ActivityManagerService.MONITOR_CPU_USAGE) {
mService.updateCpuStatsNow();
synchronized (mService.mProcessCpuTracker) {
cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
}
info.append(processCpuTracker.printCurrentLoad());
info.append(cpuInfo);
}
info.append(processCpuTracker.printCurrentState(anrTime));
Slog.e(TAG, info.toString());
if (tracesFile == null) {
// There is no trace file, so dump (only) the alleged culprit's threads to the log
Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
}
// 7. 将 traces 文件和 CPU 使用率信息保存到 dropbox,即 data/system/dropbox 目录
mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
...
if (mService.mController != null) {
try {
// 0 == show dialog, 1 = keep waiting, -1 = kill process immediately
// 8. 判断是杀死进程,还是弹出 ANR 对话框
int res = mService.mController.appNotResponding(
app.processName, app.pid, info.toString());
if (res != 0) {
if (res < 0 && app.pid != MY_PID) {
app.kill("anr", true);
} else {
synchronized (mService) {
mService.mServices.scheduleServiceTimeoutLocked(app);
}
}
return;
}
} catch (RemoteException e) {
mService.mController = null;
Watchdog.getInstance().setActivityController(null);
}
}
synchronized (mService) {
mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
if (isSilentANR) {
app.kill("bg anr", true);
return;
}
// Set the app's notResponding state, and look up the errorReportReceiver
makeAppNotRespondingLocked(app,
activity != null ? activity.shortComponentName : null,
annotation != null ? "ANR " + annotation : "ANR",
info.toString());
// Bring up the infamous App Not Responding dialog
// 9. 如果是要显示 ANR 对话框,则利用 mUiHandler(即主线程 handler,前面提到过)
// 弹出 ANR 对话框
Message msg = Message.obtain();
HashMap<String, Object> map = new HashMap<String, Object>();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mService.mUiHandler.sendMessage(msg);
}
}
当发生 ANR 时,主要顺序执行下述逻辑:
- 输出 ANR Reason 信息到 EventLog. 也就是说 ANR 触发的时间点最接近的就是 EventLog 中输出的 am_anr 信息;
- 收集并输出重要进程列表中的各个线程的 traces 信息,该方法较耗时;
- 输出当前各个进程的 CPU 使用情况以及 CPU 负载情况;
- 将 traces 文件和 CPU 使用情况信息保存到 dropbox,即 data/system/dropbox 目录
- 根据进程类型,来决定直接后台杀掉,还是弹框告知用户.
3、ANR 文件相关
当发生 ANR 的时候,正常情况下会在生成一个 traces.txt 的文件,其位于手机系统的 /data/anr/
目录下,该文件只保留最后一次发生 ANR 时的信息(即手机中所有应用发生的发生 ANR 中的最后一次)。
Android 2.2 开始增加了 DropBox 功能, 保留历史上发生的所有 ANR 的 log。/data/system/dropbox
是 DropBox 指定的文件存放位置。
通过 traces.txt,可以在里面找到线程的调用栈信息,定位发生超时的地方等等。
文件部分内容截图。
4、各类型超时时间的定义
各种类型的超时时间的定义:
// ActivityManagerService.java
// 关于 BroadcastReceiver 超时时间的定义
// 前台的 10s,后台的 60s
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
// 关于按键无相应的超时时间定义,正常是 5s,特殊情况是 60s(特殊情况,涉及到测试应用,暂时无法确定)
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
// How long we wait until we timeout on key dispatching during instrumentation.
static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000;
// ActiveServices.java
// 关于 Service 超时的时间定义
// 前台的为 20s,后台的为 200s
// 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, Broadcast, Input 发生 ANR 之后,最终都会调用 appNotResponding()
对于 Provider,在其进程启动时 publish 过程可能会出现 ANR, 则会直接杀进程以及清理相应信息,而不会弹出ANR 的对话框。 appNotRespondingViaProvider() 过程会走 appNotResponding(), 这个就不介绍了,很少使用,由用户自定义超时时间。