Android 源码分析ANR

来源:互联网 发布:重大资产重组知乎 编辑:程序博客网 时间:2024/05/20 08:27

先普及一下基本知识:

什么是ANR

ANR,是“Application Not Responding”的缩写,即“应用程序无响应”。在Android中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会监测应用程序的响应时间,如果应用程序主线程(即UI线程)在超时时间内对输入事件没有处理完毕,或者对特定操作没有执行完毕,就会出现ANR。对于输入事件没有处理完毕产生的ANRAndroid会显示一个对话框,提示用户当前应用程序没有响应,用户可以选择继续等待或者关闭这个应用程序(也就是杀掉这个应用程序的进程)。


产生ANR的三个必要条件

1.主线程

2.有输入事件

3.处理超时


产生ANR的情况

1.主线程对输入事件5秒没有处理完

2.主线程在执行BroadcastReceiveronReceive函数时10秒内没有执行完毕

3.主线程在执行Service的各个生命周期函数时20秒内没有执行完毕


产生ANR对于Android开发再熟悉不过了,每到它发生的时候,很影响用户体验,是一个很严重的问题,严重到必须要通过关闭整个进程才能让手机正常运行。第二种和第三种情况都不会弹出dialog提示,我们通过底层源码分析第一种情况ANR的产生。


事件传递的源头是InputReader,然后用InputDispatcher发送,我们看如何发送这个事件。

bool InputDispatcher::dispatchMotionLocked(        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {    ......    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;    // Identify targets.    Vector<InputTarget> inputTargets;    bool conflictingPointerActions = false;    int32_t injectionResult;    if (isPointerEvent) {        // Pointer event.  (eg. touchscreen)        injectionResult = findTouchedWindowTargetsLocked(currentTime,                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);    } else {        // Non touch event.  (eg. trackball)        injectionResult = findFocusedWindowTargetsLocked(currentTime,                entry, inputTargets, nextWakeupTime);    }    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {        return false;    }    ......    dispatchEventLocked(currentTime, entry, inputTargets);    return true;}

在dispatchEventLocked发送事件之前,会先去判断这个事件是点击事件(isPointEvent)还是其他事件。继续看findTouchedWindowTargetLocked。

int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,        const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,        bool* outConflictingPointerActions) {    enum InjectionPermission {        INJECTION_PERMISSION_UNKNOWN,        INJECTION_PERMISSION_GRANTED,        INJECTION_PERMISSION_DENIED    };    nsecs_t startTime = now();    ......    // Ensure all touched foreground windows are ready for new input.    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {        const TouchedWindow& touchedWindow = mTempTouchState.windows[i];        if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {            // Check whether the window is ready for more input.            String8 reason = checkWindowReadyForMoreInputLocked(currentTime,                    touchedWindow.windowHandle, entry, "touched");            if (!reason.isEmpty()) {                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,                        NULL, touchedWindow.windowHandle, nextWakeupTime, reason.string());                goto Unresponsive;            }        }    }    ......    return injectionResult;}

这是一个很长的方法,大体是判断这个事件的类型,获取能够处理这个事件的forceground window,如果这个window不能够继续处理事件,就是说这个window的主线程被某些耗时操作占据,我们继续看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) {    if (applicationHandle == NULL && windowHandle == NULL) {        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {#if DEBUG_FOCUS            ALOGD("Waiting for system to become ready for input.  Reason: %s", reason);#endif            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;            mInputTargetWaitStartTime = currentTime;            mInputTargetWaitTimeoutTime = LONG_LONG_MAX;            mInputTargetWaitTimeoutExpired = false;            mInputTargetWaitApplicationHandle.clear();        }    } else {        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {#if DEBUG_FOCUS            ALOGD("Waiting for application to become ready for input: %s.  Reason: %s",                    getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),                    reason);#endif            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;            }            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;            mInputTargetWaitStartTime = currentTime;            mInputTargetWaitTimeoutTime = currentTime + timeout;            mInputTargetWaitTimeoutExpired = false;            mInputTargetWaitApplicationHandle.clear();            if (windowHandle != NULL) {                mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;            }            if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {                mInputTargetWaitApplicationHandle = applicationHandle;            }        }    }    if (mInputTargetWaitTimeoutExpired) {        return INPUT_EVENT_INJECTION_TIMED_OUT;    }    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;    }}

const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec

主线程对输入事件的超时是5s,设置了最后截止时间,如果时间未到进入下一个循环。

如果返回INPUT_EVENT_INJECTION_PENDING,将事件下个loop继续处理。

    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {        return false;    }

回到dispatchMotionEventLocked,如果返回值是INPUT_EVENT_INJECTION_PENDING,就返回false,而不继续调用dispatchEventLocked发送事件。我们看后续处理。

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {    nsecs_t currentTime = now();    ......    case EventEntry::TYPE_MOTION: {        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);        if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {            dropReason = DROP_REASON_APP_SWITCH;        }        if (dropReason == DROP_REASON_NOT_DROPPED                && isStaleEventLocked(currentTime, typedEntry)) {            dropReason = DROP_REASON_STALE;        }        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {            dropReason = DROP_REASON_BLOCKED;        }        done = dispatchMotionLocked(currentTime, typedEntry,                &dropReason, nextWakeupTime);        break;    }    default:        ALOG_ASSERT(false);        break;    }    if (done) {        if (dropReason != DROP_REASON_NOT_DROPPED) {            dropInboundEventLocked(mPendingEvent, dropReason);        }        mLastDropReason = dropReason;        releasePendingEventLocked();        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately    }}

这里done = false,因为dispatchMotionEventLocked返回false,等下一次dispatchOnce再发送一次。

如果时间已过,就在很短的时间内进入下一个事件循环,进入onANRLocked

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

进入doANRlockedInterruptible

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

mPolicy->notifyANR,的实现在com_android_server_input_InputManagerService.cpp这个文件,继续传递到Java世界的InputManagerService.java。而InputManagerService处理的对象正是windowManagerService的对象mInputMonitor。

我们回到InputDispatcher的检查forcegroudWindow是否有效的方法。

String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,        const char* targetType) {    // If the window is paused then keep waiting.    if (windowHandle->getInfo()->paused) {        return String8::format("Waiting because the %s window is paused.", targetType);    }    // If the window's connection is not registered then keep waiting.    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);    }    // If the connection is dead then keep waiting.    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 the connection is backed up then keep waiting.    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());    }    // Ensure that the dispatch queues aren't too far backed up for this event.    if (eventEntry->type == EventEntry::TYPE_KEY) {        // If the event is a key event, then we must wait for all previous events to        // complete before delivering it because previous events may have the        // side-effect of transferring focus to a different window and we want to        // ensure that the following keys are sent to the new window.        //        // Suppose the user touches a button in a window then immediately presses "A".        // If the button causes a pop-up window to appear then we want to ensure that        // the "A" key is delivered to the new pop-up window.  This is because users        // often anticipate pending UI changes when typing on a keyboard.        // To obtain this behavior, we must serialize key events with respect to all        // prior input events.        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 {        // Touch events can always be sent to a window immediately because the user intended        // to touch whatever was visible at the time.  Even if focus changes or a new        // window appears moments later, the touch event was meant to be delivered to        // whatever window happened to be on screen at the time.        //        // Generic motion events, such as trackball or joystick events are a little trickier.        // Like key events, generic motion events are delivered to the focused window.        // Unlike key events, generic motion events don't tend to transfer focus to other        // windows and it is not important for them to be serialized.  So we prefer to deliver        // generic motion events as soon as possible to improve efficiency and reduce lag        // through batching.        //        // The one case where we pause input event delivery is when the wait queue is piling        // up with lots of events because the application is not responding.        // This condition ensures that ANRs are detected reliably.        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();}

checkwindowReadyForMoreInputLocked方法,这个方法之前我们提到过,正常的情况下返回的string应该是空的,让我们看一下非空的情况。

触摸事件可以立即发送到窗口,因为用户预期触摸当时可见的任何东西。即使焦点改变或新窗口出现后,触摸事件的目的是传递到无论什么时候发生在屏幕上的窗口。通用运动事件,如鼠标或操纵杆事件是棘手的。如同key事件,通用motion事件被传递到焦点窗口。不像key事件,一般的motion件不倾向于转移焦点到其他窗口,他们被序列化不重要。所以我们传递通用motion事件,尽快提高效率,通过分批减少滞后

我们暂停输入事件传递是当等待队列事件堆积时,因为应用程序没有响应。这个条件保证ANR检测的可靠。


1 0
原创粉丝点击