美文网首页Android开发之散记
Android之input anr机制

Android之input anr机制

作者: 锄禾豆 | 来源:发表于2022-02-03 22:12 被阅读0次

简介

在input服务中,InputReader负责读取数据,InputDispatcher负责分发数据,InputReader和InputDispatcher相互关联的重要纽带为队列mInboundQueue。ANR机制就是在InputDispatcher分发中充当监控。

分析 -- android 10.0
1.启动过程

void InputDispatcher::dispatchOnce() {
    ···
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

因为InputDispatcher跑在一个循环执行的线程中,同时采用了Looper机制,唤醒线程的方式有两种:wake和timeout。通过wake唤醒的方式,一般通过InputReader调用实现,而对于InputDispatcher自身来说,此时自身的timeout机制就发挥很大的作用。ANR就是使用timeout唤醒做的监听

2.核心函数分析

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    ···
    // Ready to start a new event.
    // If we don't already have a pending event, go grab one.
    if (! mPendingEvent) {
        if (mInboundQueue.isEmpty()) {
            ···
        } else {
            // Inbound queue has at least one entry.
            mPendingEvent = mInboundQueue.dequeueAtHead();
            traceInboundQueueLengthLocked();
        }

        ···
        //重置ANR
        resetANRTimeoutsLocked();
    }
    ···

    //以下是触发ANR监听业务
    switch (mPendingEvent->type) {
    ···
    case EventEntry::TYPE_KEY: {
        ···
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }

    case EventEntry::TYPE_MOTION: {
        ···
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }

    ···
    //如果分发正常即done等于true,则重置ANR
    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        //重置ANR
        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

ANR出现的情况,自然我们关注的是交互,进一步说是window:按键事件关注findFocusedWindowTargetsLocked,触摸事件关注findTouchedWindowTargetsLocked。因ANR是通过timeout方式触发线程执行循环,所以需要关注nextWakeupTime

3.重置ANR函数

void InputDispatcher::resetANRTimeoutsLocked() {
    // Reset input target wait timeout.
    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
    mInputTargetWaitApplicationToken.clear();
}

mInputTargetWaitCause为目标input等待的原因,具体有三种 INPUT_TARGET_WAIT_CAUSE_NONE/INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY/INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY

mInputTargetWaitApplicationToken为目标input的令牌token

4.分析nextWakeupTime的变化,以触摸事件为例

bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ···
    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);
    }
    //如果触摸事件异常,则返回false,这样loop马上重启启动循环
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }

    ···
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

以nextWakeupTime为线索跟踪分析
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
        const MotionEntry* entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
        bool* outConflictingPointerActions) {
    ···
    // Ensure all touched foreground windows are ready for new input.
    for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
        if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
            // 如果有异常,则走进handleTargetsNotReadyLocked
            std::string reason = checkWindowReadyForMoreInputLocked(currentTime,
                    touchedWindow.windowHandle, entry, "touched");
            if (!reason.empty()) {
                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                        nullptr, touchedWindow.windowHandle, nextWakeupTime, reason.c_str());
                goto Unresponsive;
            }
        }
    }
    ···

Unresponsive:
    // Reset temporary touch state to ensure we release unnecessary references to input channels.
    mTempTouchState.reset();

    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
    updateDispatchStatistics(currentTime, entry, injectionResult, timeSpentWaitingForApplication);
    ···
    return injectionResult;
}


int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime, const char* reason) {
    //针对不同窗口类型,重置mInputTargetWaitCause/mInputTargetWaitApplicationToken状态
    //前面提到重置ANR条件的应用
    if (applicationHandle == nullptr && windowHandle == nullptr) {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {//system_server进程等
            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
            ···
            mInputTargetWaitApplicationToken.clear();
        }
    } else {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {

            nsecs_t timeout;
            //超时时间来源,默认5秒。例如ProcessRecord/ActivityRecord
            if (windowHandle != nullptr) {
                timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else if (applicationHandle != nullptr) {
                timeout = applicationHandle->getDispatchingTimeout(
                        DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else {
                timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
            }

            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            ···
            mInputTargetWaitApplicationToken.clear();
            ···
        }
    }
    ···
    if (currentTime >= mInputTargetWaitTimeoutTime) {//如果当前时间大于5秒,则发生ANR
        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 {
        //默认nextWakeupTime为最大值
        //重置nextWakeupTime为current+5秒
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

5.anr发生之后的函数流转过程,从onANRLocked开始
1)input服务,从native层来到java层
2)再来到window服务

void InputDispatcher::onANRLocked(
        nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) {
    ···
    CommandEntry* commandEntry = postCommandLocked(
            & InputDispatcher::doNotifyANRLockedInterruptible);
    commandEntry->inputApplicationHandle = applicationHandle;
    commandEntry->inputChannel = windowHandle != nullptr ?
            getInputChannelLocked(windowHandle->getToken()) : nullptr;
    commandEntry->reason = reason;
}
调用doNotifyANRLockedInterruptible


void InputDispatcher::doNotifyANRLockedInterruptible(
        CommandEntry* commandEntry) {
    mLock.unlock();

    nsecs_t newTimeout = mPolicy->notifyANR(
            commandEntry->inputApplicationHandle,
            commandEntry->inputChannel ? commandEntry->inputChannel->getToken() : nullptr,
            commandEntry->reason);

    mLock.lock();
    ···
}
mPolicy实际为NativeInputManager


nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
        const sp<IBinder>& token, const std::string& reason) {
    ···
    jstring reasonObj = env->NewStringUTF(reason.c_str());

    jlong newTimeout = env->CallLongMethod(mServiceObj,
                gServiceClassInfo.notifyANR, tokenObj,
                reasonObj);
    if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
        newTimeout = 0; // abort dispatch
    } else {
        assert(newTimeout >= 0);
    }
    return newTimeout;
}
执行InputManagerService对象的notifyANR


InputManagerService
    // Native callback.
    private long notifyANR(IBinder token, String reason) {
        return mWindowManagerCallbacks.notifyANR(
                token, reason);
    }
mWindowManagerCallbacks为WindowManagerService的mInputManagerCallback对象


InputManagerCallback
    public long notifyANR(IBinder token, String reason) {
        AppWindowToken appWindowToken = null;
        WindowState windowState = null;
        boolean aboveSystem = false;
        synchronized (mService.mGlobalLock) {
            ···
            //input事件发生时的关键日志:
            if (windowState != null) {
                Slog.i(TAG_WM, "Input event dispatching timed out "
                        + "sending to " + windowState.mAttrs.getTitle()
                        + ".  Reason: " + reason);
                ···
            } else if (appWindowToken != null) {
                Slog.i(TAG_WM, "Input event dispatching timed out "
                        + "sending to application " + appWindowToken.stringName
                        + ".  Reason: " + reason);
            } else {
                Slog.i(TAG_WM, "Input event dispatching timed out "
                        + ".  Reason: " + reason);
            }

            ···
        }

        ···

        if (appWindowToken != null && appWindowToken.appToken != null) {
            //按键事件anr流转
            final boolean abort = appWindowToken.keyDispatchingTimedOut(reason,
                    (windowState != null) ? windowState.mSession.mPid : -1);
            if (!abort) {
                // The activity manager declined to abort dispatching.
                // Wait a bit longer and timeout again later.
                return appWindowToken.mInputDispatchingTimeoutNanos;
            }
        } else if (windowState != null) {
            //触摸事件anr流转
            long timeout = mService.mAmInternal.inputDispatchingTimedOut(
                    windowState.mSession.mPid, aboveSystem, reason);
            if (timeout >= 0) {
                // The activity manager declined to abort dispatching.
                // Wait a bit longer and timeout again later.
                return timeout * 1000000L; // nanoseconds
            }
        }
        return 0; // abort dispatching
    }
input 发生anr时的关键日志:
TAG:WindowManager
关键:Input event dispatching timed out xxx. Reason: + reason, 其中xxx取值:
窗口类型: sending to windowState.mAttrs.getTitle()
应用类型: sending to application appWindowToken.stringName
其他类型: 则为空.

reason如下分析

6.发生ANR的原因
1)窗口暂停: Waiting because the [targetType] window is paused.
2)窗口未连接: Waiting because the [targetType] window’s input channel is not registered with the input dispatcher. The window may be in the process of being removed.
3)窗口连接已死亡:Waiting because the [targetType] window’s input connection is [Connection.Status]. The window may be in the process of being removed.
4)窗口连接已满:Waiting because the [targetType] window’s input channel is full. Outbound queue length: [outboundQueue长度]. Wait queue length: [waitQueue长度].
5)按键事件,输出队列或事件等待队列不为空:Waiting to send key event because the [targetType] window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: [outboundQueue长度]. Wait queue length: [waitQueue长度].
6)非按键事件,事件等待队列不为空且头事件分发超时500ms:Waiting to send non-key event because the [targetType] window has not finished processing certain input events that were delivered to it over 500ms ago. Wait queue length: [waitQueue长度]. Wait queue head age: [等待时长].

std::string InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // If the window is paused then keep waiting.
    ···

    // If the window's connection is not registered then keep waiting.
    ···

    // If the connection is dead then keep waiting.
    ···

    // If the connection is backed up then keep waiting.
    ···

    // 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.
        ··· 
    } 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.
        ···
    }
    return "";
}

7.继续分析anr日志记录。以触摸事件为例,inputDispatchingTimedOut
从window服务来到activit服务

ActivityManagerService
        public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
            synchronized (ActivityManagerService.this) {
                return ActivityManagerService.this.inputDispatchingTimedOut(
                        pid, aboveSystem, reason);
            }
        }
        
    long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
        ···

        if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
            return -1;
        }

        return timeout;
    }

    boolean inputDispatchingTimedOut(ProcessRecord proc, String activityShortComponentName,
            ApplicationInfo aInfo, String parentShortComponentName,
            WindowProcessController parentProcess, boolean aboveSystem, String reason) {
        ···
        if (proc != null) {
            ···
            proc.appNotResponding(activityShortComponentName, aInfo,
                    parentShortComponentName, parentProcess, aboveSystem, annotation);
        }

        return true;
    }

proc.appNotResponding是所有发生anr所走的地方,这里暂不分析。结果是把日志记录在/data/anr目录

总结
1.input服务的anr发生所依赖的服务过程:
从input服务的native层来到java层,再流转到window服务,最后来到activity服务记录日志

2.input 发生anr时的关键日志:
TAG:WindowManager
关键:Input event dispatching timed out xxx. Reason: + reason, 其中xxx取值:
窗口类型: sending to windowState.mAttrs.getTitle()
应用类型: sending to application appWindowToken.stringName
其他类型: 则为空.

3.超时默认5秒。
窗口类型和应用类型超时来自ActivityTaskManagerService.KEY_DISPATCHING_TIMEOUT_MS,其他类型超时来自InputDispatcher.DEFAULT_INPUT_DISPATCHING_TIMEOUT

参考学习

http://gityuan.com/2017/01/01/input-anr/

相关文章

网友评论

    本文标题:Android之input anr机制

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