美文网首页Android进阶
Android源码剖析之ANR前世今生

Android源码剖析之ANR前世今生

作者: 码上就说 | 来源:发表于2018-08-26 18:00 被阅读220次

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

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

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


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<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
接下来请允许我用一个时序图来表示接下来的调用过程:


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

这里有太多的疑点要解释:

简而言之,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);

相关文章

网友评论

    本文标题:Android源码剖析之ANR前世今生

    本文链接:https://www.haomeiwen.com/subject/cxsoiftx.html