android anr 源码,Android源码剖析之ANR前世今生

前言:知其所以,知其所以然

从整个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处理的时候发生超时

这种情况我们平时遇到的时候相对较少,但是我觉得还是有必要介绍一下这种情况。直接给出源码的调用流程。

914df9091a80

ContentProvider-anr调用流程.jpg

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& applicationHandle,

const sp& 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& applicationHandle,

const sp& 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,

const sp& 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

接下来请允许我用一个时序图来表示接下来的调用过程:

914df9091a80

anr调用流程.jpg

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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值