[Framework] 深入理解 Android ANR
ANR
对于很多人来说熟悉又陌生,熟悉的是由于应用主线程过于忙碌导致某些重要任务延迟执行然后系统会弹出一个提示框;陌生的是 ANR
产生的流程是什么样的呢?又有哪些场景会产生呢?
这里直接给出结论四大组件中的 Service
、BroadcastReceiver
和 ContentProvider
他们的生命周期会检测 ANR
超时,还有 Input
事件 (屏幕触摸事件和键盘数据事件) 也会检测 ANR
超时,唯独少了我们最熟悉的 Activity
生命周期,哈哈,是否有点颠覆你的认知,有一个很常见的面试题就是问在 Activity#onCreate
的生命周期中调用 Thread.sleep(10000)
是否会导致 ANR
,你是否也回答了 yes 呢?在你读懂了这篇文章后你就知道这道题该怎么回答了。
这里以 Service
Create
流程做一个例子简单描述一下 ANR
是怎么产生的,首先调用 startService()
请求一个 Service
,然后通过 binder
通知 AMS
启动 Service
,AMS
经过各种判断后可以启动,然后就会通过 binder
通知 Service
所在的应用进程创建一个 Service
同时执行 onCreate()
生命周期,关键点来了这时 AMS
会通过 Handler
发送一个延时任务,这个任务就是 ANR
任务,虽然会延时执行,那不是到时间了不是还是会执行?你先别急。在应用进程 Service
生命周期执行完了后就会通过 binder
通知 AMS
,AMS
收到通知后就会移除 ANR
任务。所以当应用进程处理 Service
Create
的任务超过设定的延时后,AMS
中 ANR
任务就会执行,然后就看到了我们熟悉的应用未响应的弹窗。
Service
的 ANR
任务可以类比成以下的故事:绑匪劫持了一个人质,然后让你跑回家拿钱来赎人,超过 2 小时后就撕票。
场景一:收到绑匪的消息后,你就拼命的往家里赶然后拿钱又拼命返回劫匪处然后把钱给劫匪,终于在 2 小时的期限内把钱给了劫匪,然后皆大欢喜人质存活了。
场景二:同样收到消息后往家赶,然后非常不幸半路你脚崴了,拿完钱返回劫匪处时,时间已经超过了 2 小时,然后劫匪撕票了。
绑匪 = AMS
, 你 = 应用进程,场景一正常,场景二触发 ANR
。
Service
、BroadcastReciever
和 ContentProvider
他们处理 ANR
的方式都是类似的,Input
事件和其他组件的处理方式不一样,所以本文以 Service
和 Input
事件来分析 ANR
。
在继续前,建议先了解 binder
、 Handler
和 Input
事件下发流程。
理解 Android 中的 Input 事件
Android Handler 工作原理
Android Binder 工作原理
Service ANR 原理
源码分析基于 Android 9
源应用进程
调用 Context#startService()
方法后最终都会跳转到 ContextImpl#startService()
方法中去,我们也以它作为入口函数:
Java
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
Java
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) {
// ...
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
ActivityManager
只是 AMS
在应用进程的一个 binder
的 Client
,最终会在 AMS#startService()
方法中执行。
AMS system_server 进程
Java
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage, int userId)
throws TransactionTooLargeException {
// ...
synchronized(this) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
requireForeground, callingPackage, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
return res;
}
}
这个 mServices
是 ActiveServices
对象,然后这里调用了它的 startServiceLocked()
方法。
Java
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;
}
Java
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
// ...
String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
// ...
}
Java
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting, boolean permissionsReviewRequired)
throws TransactionTooLargeException {
// ...
if (!isolated) {
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
+ " app=" + app);
if (app != null && app.thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg);
return null;
} catch (TransactionTooLargeException e) {
throw e;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
// If a dead object exception was thrown -- fall through to
// restart the application.
}
} else {
// ...
}
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null && !permissionsReviewRequired) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
hostingType, r.name, false, isolated, false)) == null) {
// ...
return msg;
}
if (isolated) {
r.isolatedProc = app;
}
}
// ...
if (!mPendingServices.contains(r)) {
mPendingServices.add(r);
}
// ...
return null;
}
经过层层调用会走到 ActiveServices#bringUpServiceLocked()
方法,如果 Service
对应的进程已经启动就直接调用 realStartServiceLocked()
,如果进程还没有启动,调用 AMS#startProcessLocked()
启动新的进程,当前要启动的 Service
就存在 mPendingServices
中,等进程启动完成后,继续处理没有完成的 Service
生命流程,这部分代码我就不分析了。
Java
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
// ...
bumpServiceExecutingLocked(r, execInFg, "create");
// ...
boolean created = false;
try {
// ...
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
r.postNotification();
created = true;
} catch (DeadObjectException e) {
// ...
} finally {
// ...
}
// ...
}
bumpServiceExecutingLocked()
就添加了一个 ANR
延迟任务,也就是劫持人质了,然后调用 app.thread.scheduleCreateService()
方法,这个 thread
也是一个 binder
的 Client
,它的 Server
也就是应用进程中的 ApplicationThread
,也就是后续逻辑在应用进程中了。
再看看延迟的 ANR
任务:
Java
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
// ...
long now = SystemClock.uptimeMillis();
if (r.executeNesting == 0) {
// ...
} else if (r.app != null && fg && !r.app.execServicesFg) {
r.app.execServicesFg = true;
if (timeoutNeeded) {
scheduleServiceTimeoutLocked(r.app);
}
}
// ...
}
Java
// ...
// 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;
// ...
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);
}
这里的定时任务会判断是前台 Service
和后台 Service
,前台的超时时间是 20s,后台的超时时间是 200s。
目标应用进程
在前面讲到 AMS
会通过 binder
调用 ApplicationThread#scheduleCreateService()
方法:
Java
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);
}
然后这里会把任务传递到主线程:
Java
// ...
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
private void handleCreateService(CreateServiceData data) {
// ...
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();
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) {
// ...
}
}
在这里看到执行完生命周期后,又通过 binder
调用 AMS#serviceDoneExecuting()
方法。
我们再来看看 AMS#serviceDoneExecuting()
方法中做了什么:
Java
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
void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
boolean inDestroying = mDestroyingServices.contains(r);
if (r != null) {
// ...
final long origId = Binder.clearCallingIdentity();
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
Binder.restoreCallingIdentity(origId);
} else {
Slog.w(TAG, "Done executing unknown service from pid "
+ Binder.getCallingPid());
}
}
Java
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing) {
// ...
if (r.executeNesting <= 0) {
if (r.app != null) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"Nesting at 0 of " + r.shortName);
r.app.execServicesFg = false;
r.app.executingServices.remove(r);
if (r.app.executingServices.size() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
"No more executingServices of " + r.shortName);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
} else if (r.executeFg) {
// ..
}
}
// ...
}
// ...
}
这里就看到移除了 ANR
任务,释放了人质。
Input ANR 原理
在这里我默认大家都熟悉 Input
事件的处理流程,Input
流程的主要源代码逻辑都是 C++
写的,我没有贴源代码,可以参考这位大佬的文章:Input系统---事件处理全过程
InputDispatcher
在获取到处理事件后会通过 Socket
的通信方式发送给对应的 Window
来处理,也就是应用进程所对应的一个 Window
。发送出去后就会把这个事件添加到 waitQueue
队列中,只有等应用进程把这个事件处理完毕了才会通过 Socket
通知 InputDispatcher
,应用进程处理 Input
事件是在应用主线程。InputDispatcher
收到已经完成的事件后,会把对应的 waitQueue
中等待的事件移除。
在之前的讲 Input
原理的文章中提到,InputDispatcher
收到来自 InputReader
发送过来的事件后,回去找对应的 Window
来处理这个事件,在这个过程中它会检查各种 Window
的状态,来判断它是否有能力来处理这个事件。
C++
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
const char* targetType) {
//当窗口暂停的情况,则保持等待
if (windowHandle->getInfo()->paused) {
return String8::format("Waiting because the %s window is paused.", targetType);
}
//当窗口连接未注册,则保持等待
ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
if (connectionIndex < 0) {
return String8::format("Waiting because the %s window's input channel is not "
"registered with the input dispatcher. The window may be in the process "
"of being removed.", targetType);
}
//当窗口连接已死亡,则保持等待
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
if (connection->status != Connection::STATUS_NORMAL) {
return String8::format("Waiting because the %s window's input connection is %s."
"The window may be in the process of being removed.", targetType,
connection->getStatusLabel());
}
// 当窗口连接已满,则保持等待
if (connection->inputPublisherBlocked) {
return String8::format("Waiting because the %s window's input channel is full. "
"Outbound queue length: %d. Wait queue length: %d.",
targetType, connection->outboundQueue.count(), connection->waitQueue.count());
}
if (eventEntry->type == EventEntry::TYPE_KEY) {
// 按键事件,输出队列或事件等待队列不为空
if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
return String8::format("Waiting to send key event because the %s window has not "
"finished processing all of the input events that were previously "
"delivered to it. Outbound queue length: %d. Wait queue length: %d.",
targetType, connection->outboundQueue.count(), connection->waitQueue.count());
}
} else {
// 非按键事件,事件等待队列不为空且头事件分发超时500ms
if (!connection->waitQueue.isEmpty()
&& currentTime >= connection->waitQueue.head->deliveryTime
+ STREAM_AHEAD_EVENT_TIMEOUT) {
return String8::format("Waiting to send non-key event because the %s window has not "
"finished processing certain input events that were delivered to it over "
"%0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms.",
targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
connection->waitQueue.count(),
(currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
}
}
return String8::empty();
}
当上面的方法返回不为空时就表示当前的 Window
不可用,我们以触摸事件 waitQueue
那段逻辑作为我们的分析方向,当 waitQueue
中最旧的事件超过 500ms 时,它就认为目前的 Window
不可用。如果 Window
可用就走上述的事件下发到应用进程的逻辑,如果不可用就跳过这次事件下发,然后还会检查上次事件下发到当前时间的时间间隔,如果这个间隔超过了 5s 那就会触发 ANR
,这个 ANR
消息会通过 jni
调用到 IMS
,然后再到 AMS
,最后执行 ANR
的处理流程。
总结
回到开头问题
为什么在 Activity
的生命周期中阻塞 10s 都不会出现 ANR
? 因为单纯的 Activity
的生命周期中,AMS
本来就没有做 ANR
处理,但是这会造成很大的 ANR
风险,为什么会这么说?因为还有其他四种情况下会导致 ANR
,以 Input
事件为例,当事件到达后,由于主线程被 Activity#onCreate()
中阻塞 10s,然后 Input
任务的 Message
在 MessageQueue
中只能等待,等待 10s 就会导致 Input
事件的 ANR
触发。
应用层如何监听 ANR
在出现 ANR
后会发送一个 SIGQUIT
信号给应用进程,需要通过 C/C++
代码监控,大家自己在去找一下怎么监听信号,有的时候 SIGQUIT
信号也不一定是 ANR
,还需要通过一下代码判断当前进程的状态:
Java
private static boolean checkErrorState() {
try {
Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
if (procs == null) return false;
for (ActivityManager.ProcessErrorStateInfo proc : procs) {
if (proc.pid != android.os.Process.myPid()) continue;
if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
return true;
}
return false;
} catch (Throwable t){
MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
}
return false;
}
在出现 ANR
后,系统会在 data/anr
目录下存放出现的 ANR
记录,里面有非常重要的 ANR
信息。
vbnet
Subject: Input dispatching timed out (6b274b7 com.gmlive.common.xolmedia.demo/com.gmlive.common.xolmedia.demo.MainActivity (server) is not responding. Waited 5004ms for MotionEvent)
--- CriticalEventLog ---
capacity: 20
timestamp_ms: 1697608788397
window_ms: 300000
这就是一个标准的 Input
超时导致的 ANR
信息。后面还会有当时所有进程的信息,我找到我们自己的进程,然后看看当时主线程的堆栈:
php
"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 ucsCount=0 flags=1 obj=0x720110c0 self=0xb4000073589cd380
| sysTid=10108 nice=-10 cgrp=system sched=0/0 handle=0x74a85b44f8
| state=S schedstat=( 879996588 111079630 802 ) utm=77 stm=10 core=4 HZ=100
| stack=0x7fc0b0e000-0x7fc0b10000 stackSize=8188KB
| held mutexes=
at java.lang.Thread.sleep(Native method)
- sleeping on <0x01738b1a> (a java.lang.Object)
at java.lang.Thread.sleep(Thread.java:450)
- locked <0x01738b1a> (a java.lang.Object)
at java.lang.Thread.sleep(Thread.java:355)
at com.gmlive.common.xolmedia.demo.MainActivity.onTouchEvent(MainActivity.kt:13)
at android.app.Activity.dispatchTouchEvent(Activity.java:4302)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:458)
at android.view.View.dispatchPointerEvent(View.java:15309)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6778)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6578)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6034)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6091)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6057)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6222)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6065)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6279)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6038)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6091)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6057)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6065)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6038)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:9206)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:9157)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:9126)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:9329)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:267)
at android.os.MessageQueue.nativePollOnce(Native method)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loopOnce(Looper.java:161)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7918)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
主要是卡在我们的 sleep
方法,这个文件里面还有其他非常重要的信息,包括 binder
状态,内存信息等等。
如果是高版本手机提示没有权限,可以通过 adb bugreport
命令把日志文件下载下来。
ANR 问题定位
其实 ANR
的问题十分复杂,它可能也是由于 AMS
本身处理不过来任务导致,也有可能是 CPU
本身发热降频导致处理任务的能力下降,我们先忽略这些原因只考虑我们自身应用的原因。当我们监听到应用发生 ANR
时,就去找当前主线程的方法栈时就能定位到导致 ANR
的罪魁祸手吗?当我们使用 Thread.sleep()
这种方法来测试时,是可行的,但是测试用的方法总是按照你所预想的结果来跑的,而线上用户实际的运行环境比你测试的代码要复杂非常多,我就举一个例子来反驳上诉的方案。
我这里先默认大家都对 Handler
的工作原理都清楚,当有 Input
事件到来时,主线程的的 MessageQueue
状态如下:
我们假如主线程处理 Input
事件的 Message
为 D
,这时正插入 MessageQueue
尾。我们假如任务 A
、B
、C
都需要执行 2s,那么 Input
处理的事件就需要至少等待 6s 才能执行,也就是在它执行前就会造成 ANR
, 造成 ANR
时对应的 MessageQueue
状态如下:
这个时候你去拿主线程的方法栈信息,就会定位到 Message C
所对应的栈,所以你就认为是 C
所对应的代码有问题?这个就是刻舟求剑了,其实是 A
,B
, C
这三个任务共同导致了这次 ANR
。
ANR
是一个非常复杂的综合性问题,我们只能尽最大的可能减小主线程的负担从而减少 ANR
,和 OOM
一样,我们无法阻止它的发生。