前言:知其所以,知其所以然
从整个Android系统来讲,发生的问题主要有3种:
本文介绍的主要内容是:
- 什么是ANR
- 两种类型的ANR触发情况
- InputDispatcher发生超时发生anr的两种子情况
二、ANR问题
ANR:全称是Application Not Respond,就是应用程序无响应,这也是经常会发生的一种问题,任何一个应用程序都有主线程,在Android中,因为UI的操作都在主线程中完成,所以主线程也被成为UI线程,UI线程中会做一些和用户交互相关性非常大的工作,所以UI线程不能有明显的卡顿,一般我们不建议在UI线程中执行较耗时的操作。如果一些粗心的开发者在UI线程中做了一些耗时的操作,会有非常明显的卡顿,严重的情况下还会产生ANR。我们学习Android源码就要知道,这些ANR是如何产生的,并且是如何分发的,如何处理的。
《Android源码剖析之Exception前世今生》文章中介绍了Android中类似问题监控的基本思想,总结起来两个要点:
- 设置监听器
- 在合适的时候回调监听器
因此ANR的监控也是这样处理的。当然要搞清楚这些问题,还是要从源码分析中着手。
Android中有两种情况下会发生ANR:
- ContentProvider处理的时候发生超时
- InputDispatcher发生超时
2.1 ContentProvider处理的时候发生超时
这种情况我们平时遇到的时候相对较少,但是我觉得还是有必要介绍一下这种情况。直接给出源码的调用流程。

ContentProviderClient中提供了setDetectNotResponding(...)函数来帮助监听ContentProvider的超时情况,不过一般情况下都没有ContentProvider会这么设置,反正Android在这里提供给开发者一种思路,ContentProvider是提供超时保护的。
2.2 InputDispatcher发生超时
但是正常情况下还是第2种发生的次数比较多,第1种目前发生的次数比较少,其实原理都是都是一样的。ContentProvider处理的时候发生超时,就是本地操作本地的content provider发生延时。本文主要从InputDispatcher发生超时讲解。
这要从本地的inputFlinger讲起,Android系统处理input时间的就是inputFlinger,每次处理input 事件的时候,要执行到native的一个方法:
frameworks/native/services/inputflinger/InputDispatcher.cpp中的findFocusedWindowTargetsLocked(...)方法,每次处理input的事件的时候都会走到这个函数中。此函数中在开始的时候有一段关键的判断代码:
if (mFocusedWindowHandle == NULL) {
if (mFocusedApplicationHandle != NULL) {
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, NULL, nextWakeupTime,
"Waiting because no window has focus but there is a "
"focused application that may eventually add a window "
"when it finishes starting up.");
goto Unresponsive;
}
ALOGI("Dropping event because there is no focused window or focused application.");
injectionResult = INPUT_EVENT_INJECTION_FAILED;
goto Failed;
}
如果当前没有焦点窗口且没有焦点应用程序,然后放弃事件。继续往下看,执行的handleTargetsNotReadyLocked(...)函数:
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
const EventEntry* entry,
const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle,
nsecs_t* nextWakeupTime, const char* reason) {
//......
nsecs_t timeout;
if (windowHandle != NULL) {
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else if (applicationHandle != NULL) {
timeout = applicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else {
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}
//......
if (currentTime >= mInputTargetWaitTimeoutTime) {
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
// Force poll loop to wake up immediately on next iteration once we get the
// ANR response back from the policy.
*nextWakeupTime = LONG_LONG_MIN;
return INPUT_EVENT_INJECTION_PENDING;
} else {
// Force poll loop to wake up when timeout is due.
if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
*nextWakeupTime = mInputTargetWaitTimeoutTime;
}
return INPUT_EVENT_INJECTION_PENDING;
}
}
这儿关注的点有两个:
- timeout
深入进去看,timeout定义的默认是
const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL;
默认是5s,当然系统本身是可以改动的。- currentTime >= mInputTargetWaitTimeoutTime
这儿的意思是如果超过5s了还没有任何响应,就需要执行相应的错误错误措施了。
执行的函数是:
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
void InputDispatcher::onANRLocked(
nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle,
nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) {
float dispatchLatency = (currentTime - eventTime) * 0.000001f;
float waitDuration = (currentTime - waitStartTime) * 0.000001f;
ALOGI("Application is not responding: %s. "
"It has been %0.1fms since event, %0.1fms since wait started. Reason: %s",
getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
dispatchLatency, waitDuration, reason);
// Capture a record of the InputDispatcher state at the time of the ANR.
time_t t = time(NULL);
struct tm tm;
localtime_r(&t, &tm);
char timestr[64];
strftime(timestr, sizeof(timestr), "%F %T", &tm);
mLastANRState.clear();
mLastANRState.append(INDENT "ANR:\n");
mLastANRState.appendFormat(INDENT2 "Time: %s\n", timestr);
mLastANRState.appendFormat(INDENT2 "Window: %s\n",
getApplicationWindowLabelLocked(applicationHandle, windowHandle).string());
mLastANRState.appendFormat(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency);
mLastANRState.appendFormat(INDENT2 "WaitDuration: %0.1fms\n", waitDuration);
mLastANRState.appendFormat(INDENT2 "Reason: %s\n", reason);
dumpDispatchStateLocked(mLastANRState);
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doNotifyANRLockedInterruptible);
commandEntry->inputApplicationHandle = applicationHandle;
commandEntry->inputWindowHandle = windowHandle;
commandEntry->reason = reason;
}
因为超过规定的时间的没有响应了,此时就走到了anr的流程,这段代码执行的要点如下:
- 设置anr相关的信息
- dumpDispatchStateLocked
dump当前的trace信息- InputDispatcher::doNotifyANRLockedInterruptible
开始进一步调用ANR的处理函数
void InputDispatcher::doNotifyANRLockedInterruptible(
CommandEntry* commandEntry) {
mLock.unlock();
nsecs_t newTimeout = mPolicy->notifyANR(
commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle,
commandEntry->reason);
mLock.lock();
resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
commandEntry->inputWindowHandle != NULL
? commandEntry->inputWindowHandle->getInputChannel() : NULL);
}
这段代码可以看出来,当前发生anr的时候,处理anr回调时需要用lock来保证独占性。下面跳转到 frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
const sp<InputWindowHandle>& inputWindowHandle, const String8& reason) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyANR");
#endif
ATRACE_CALL();
JNIEnv* env = jniEnv();
jobject inputApplicationHandleObj =
getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
jobject inputWindowHandleObj =
getInputWindowHandleObjLocalRef(env, inputWindowHandle);
jstring reasonObj = env->NewStringUTF(reason.string());
jlong newTimeout = env->CallLongMethod(mServiceObj,
gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj,
reasonObj);
if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
newTimeout = 0; // abort dispatch
} else {
assert(newTimeout >= 0);
}
env->DeleteLocalRef(reasonObj);
env->DeleteLocalRef(inputWindowHandleObj);
env->DeleteLocalRef(inputApplicationHandleObj);
return newTimeout;
}
关键的调用是下面这段代码:
jlong newTimeout = env->CallLongMethod(mServiceObj,
gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj,
reasonObj);
这是一个jni调用,调用到Java层了,调用的是frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
接下来请允许我用一个时序图来表示接下来的调用过程:

- InputManagerService->notifyANR
这儿执行的代码如下:
private long notifyANR(InputApplicationHandle inputApplicationHandle,
InputWindowHandle inputWindowHandle, String reason) {
return mWindowManagerCallbacks.notifyANR(
inputApplicationHandle, inputWindowHandle, reason);
}
这个mWindowManagerCallbacks是一个WindowManagerCallbacks接口,这个接口是IMS中实现window管理的,它被InputMonitor实现。
final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
}
- InputMonitor->notifyANR
下面将这个函数的执行的要点总结一下:
1.根据当前的appWindowToken将anr信息保存在WMS和AMS中,两个中分别保存一份。
2.如果当前的appWindowToken存在的话,那么执行keyDispatchingTimedOut
3.如果appWindowToken不存在但是windowState存在的话,那么执行inputDispatchingTimedOut
这里有太多的疑点要解释:
- AMS与WMS是什么关系?
建议这里参考一下我之前的文章《WindowManagerService架构剖析之token分析》,里面有对AMS与WMS关系的描述。
简而言之,Android管理组件的是通过AMS,Android中界面的呈现载体是Activity,Activity管理着Window最终承载着View来显示界面,所以AMS与WMS之间必然有着对应关系,而token就是这种对应关系的纽带。token能有效的在Activity和Window之间建立1对1的关系,每次操作Window,都会通过token来校验当前的Window是否合法,这样保证AMS与WMS在管理Activity与Window的时候不会出现问题。
- windowState是什么?
windowState在WMS中就代表一个window对象,里面存储window相关的信息。
上面的步骤1/2/3表示:
- AMS与WMS中一定要同时保存anr信息,因为AMS中要dump activity信息,WMS中要dump display信息,这两份信息都是分析anr的重要日志。
InputDispatcher发生超时也分为两种情况:
- keyDispatchingTimedOut
- inputDispatchingTimedOut
2.2.1 keyDispatchingTimedOut
if (appWindowToken != null && appWindowToken.appToken != null)
这种情况下走到这个anr情况,表示当前的界面还存在,用户还可以交互,但是很卡。
final boolean abort = controller != null
&& controller.keyDispatchingTimedOut(reason,
(windowState != null) ? windowState.mSession.mPid : -1);
执行到AppWindowContainerController.java
boolean keyDispatchingTimedOut(String reason, int windowPid) {
return mListener != null && mListener.keyDispatchingTimedOut(reason, windowPid);
}
这个mListener是继承WindowContainerListener的,如下:
public interface AppWindowContainerListener extends WindowContainerListener
ActivityRecord.java实现了AppWindowContainerListener接口:
final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener
public boolean keyDispatchingTimedOut(String reason, int windowPid) {
ActivityRecord anrActivity;
ProcessRecord anrApp;
boolean windowFromSameProcessAsActivity;
synchronized (service) {
anrActivity = getWaitingHistoryRecordLocked();
anrApp = app;
windowFromSameProcessAsActivity =
app == null || app.pid == windowPid || windowPid == -1;
}
if (windowFromSameProcessAsActivity) {
return service.inputDispatchingTimedOut(anrApp, anrActivity, this, false, reason);
} else {
// In this case another process added windows using this activity token. So, we call the
// generic service input dispatch timed out method so that the right process is blamed.
return service.inputDispatchingTimedOut(windowPid, false /* aboveSystem */, reason) < 0;
}
}
这段代码执行的要点是:
- getWaitingHistoryRecordLocked
获取当前正在等待的anr的activity,这个可以通过当前activity的状态来判断。- windowFromSameProcessAsActivity为true
说明是当前进程调用了inputDispatchingTimedOut函数- windowFromSameProcessAsActivity为false
在这种情况下,另一个进程使用此活动令牌添加了窗口。所以,我们称之为通用服务输入调度超时方法。
这时候执行
mAppErrors.appNotResponding(proc, activity, parent, aboveSystem, annotation);
2.2.2 inputDispatchingTimedOut
else if (windowState != null)
说明if (appWindowToken != null && appWindowToken.appToken != null) 为false,此时用户界面已经不可以交互了,其实本质上没有什么显著的区别。
说明这时候另外一个进程使用token来添加了窗口,通过服务输入调度超时了。接下来也执行到了:
mAppErrors.appNotResponding(proc, activity, parent, aboveSystem, annotation);
2.3 调用ANR超时窗口
上面两个函数都执行到了mAppErrors.appNotResponding,将当前的进程相关信息传过来,我们会根据具体的情况弹窗ANR窗口。
但是在系统真正弹出ANR窗口之前,还需要执行一系列操作,这样操作,或为重置状态、或为获取更多的信息显示当前的anr具体情况,辅助开发者解决当前发生的问题。
一些的执行过程都在mAppErrors.appNotResponding中,我们分析了代码,得到此函数执行的要点如下:
- 更新AMS中监控的CPU信息
if (ActivityManagerService.MONITOR_CPU_USAGE) {
mService.updateCpuStatsNow();
}
- dump anr的进程信息栈
这个进程包括native进程,把这些栈的信息取出来,放到traces中,便于开发者分析问题。
// For background ANRs, don't pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
File tracesFile = ActivityManagerService.dumpStackTraces(
true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
nativePids);
- 将当前的anr信息通过logcat输出出来
mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
- 弹出anr的弹窗
mService.mUiHandler.sendMessage(msg);
网友评论