Android开发No Focused Window ANR产生原理解析

 更新时间:2023年07月28日 10:40:12   作者:尹学姐  
这篇文章主要为大家介绍了Android开发No Focused Window ANR产生原理解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

之前我们讲过因为事件没有得到及时处理,引起的ANR问题。但这只是Input Dispatching Timeout中的一种情况,还有一种情况,在我们应用中出现的也很常见,就是No Focused Window ANR,这个又是在哪些情况下产生的呢?

由之前的文章,我们知道,点击事件都是由InputDispatcher来分发的,我们直接来看InputDispatcher的源码。

No Focused Window ANR如何产生

如果是Key事件,或Motion事件,都需要找到焦点窗口取处理,都会调用到findFocusedWindowTargetsLocked()。

// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
                                                        const EventEntry& entry,
                                                        std::vector<InputTarget>& inputTargets,
                                                        nsecs_t* nextWakeupTime) {
    std::string reason;
    int32_t displayId = getTargetDisplayId(entry);
    // mFocusedWindowHandlesByDisplay在setInputWindowsLocked()里赋值
    sp<InputWindowHandle> focusedWindowHandle =
            getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
    // mFocusedApplicationHandlesByDisplay在setFocusedApplication()里赋值
    sp<InputApplicationHandle> focusedApplicationHandle =
            getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
    // focusedWindowHandle和focusedApplicationHandle都为空时表示当前无窗口,该事件会被丢弃,不会执行dispatchEventLocked
    // 一般出现两个都为空的场景,是在窗口切换的过程,此时不处理事件注入
    if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
        return INPUT_EVENT_INJECTION_FAILED;
    }
    // focusedWindowHandle为空但focusedApplicationHandle不为空时开始ANR检查
    if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) {
        // 默认mNoFocusedWindowTimeoutTime没有值,第一次检查ANR会走下面这个流程
        if (!mNoFocusedWindowTimeoutTime.has_value()) {
            // DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5s * HwTimeoutMultiplier();
            // 默认input dispatch timeout时间时5s
            const nsecs_t timeout = focusedApplicationHandle->getDispatchingTimeout(
                    DEFAULT_INPUT_DISPATCHING_TIMEOUT.count());
            // 给mNoFocusedWindowTimeoutTime赋值,触发ANR时会检查这个值是否为空,不为空才触发ANR
            mNoFocusedWindowTimeoutTime = currentTime + timeout;
            // 把当前的focusedApplicationHandle赋值给mAwaitedFocusedApplication,触发ANR时会检查这个值是否为空,不为空才触发ANR
            mAwaitedFocusedApplication = focusedApplicationHandle;
            mAwaitedApplicationDisplayId = displayId;
            *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
            // 返回INPUT_EVENT_INJECTION_PENDING表示dispatchKeyLocked()或者dispatchMotionLocked()为false
            return INPUT_EVENT_INJECTION_PENDING;
        } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
            // Already raised ANR. Drop the event
            return INPUT_EVENT_INJECTION_FAILED;
        } else {
            // Still waiting for the focused window
            return INPUT_EVENT_INJECTION_PENDING;
        }
    }
    // 如果走到这个流程,说明没有ANR,清空mNoFocusedWindowTimeoutTime和mAwaitedFocusedApplication
    resetNoFocusedWindowTimeoutLocked();
    return INPUT_EVENT_INJECTION_SUCCEEDED;
}

主要逻辑:

  • 如果focusedWindowHandle和focusedApplicationHandle都为null,一般发生在窗口切换的时候,返回INPUT_EVENT_INJECTION_FAILED,直接drop事件,不做处理
  • 如果focusedWindowHandle为null,focusedApplicationHandle不为null,返回INPUT_EVENT_INJECTION_PENDING,在nextWakeupTime之后唤醒,检查是否发生ANR
    • mNoFocusedWindowTimeoutTime:记录no focused window timeout的时间
    • mAwaitedFocusedApplication:记录focusedApplicationHandle
    • nextWakeupTime: 下次唤醒pollInner的时间

检查ANR逻辑

// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
nsecs_t InputDispatcher::processAnrsLocked() {
    const nsecs_t currentTime = now();
    nsecs_t nextAnrCheck = LONG_LONG_MAX;
    // 在findFocusedWindowTargetsLocked()中,如果focusedWindowHandle为空,focusedApplicationHandle不为空,以下条件就会满足
    if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
        // mNoFocusedWindowTimeoutTime为检查时间+5s,如果currentTime大于等于mNoFocusedWindowTimeoutTime,表示超时
        if (currentTime >= *mNoFocusedWindowTimeoutTime) {
            // 触发ANR流程,此处触发的ANR类型是xxx does not have a focused window
            processNoFocusedWindowAnrLocked();
            // 清空mAwaitedFocusedApplication,下次就不会再走ANR流程
            mAwaitedFocusedApplication.clear();
            mNoFocusedWindowTimeoutTime = std::nullopt;
            return LONG_LONG_MIN;
        } else {
            // Keep waiting
            const nsecs_t millisRemaining = ns2ms(*mNoFocusedWindowTimeoutTime - currentTime);
            ALOGW("Still no focused window. Will drop the event in %" PRId64 "ms", millisRemaining);
            // 还没有超时,更新检查时间
            nextAnrCheck = *mNoFocusedWindowTimeoutTime;
        }
    }
    ....
    // 如果走到这个流程,ANR类型是xxx is not responding. Waited xxx ms for xxx
    // 这个地方,focusedWindowHandle和focusedApplicationHandle都是不为空的场景
    onAnrLocked(*connection);
    return LONG_LONG_MIN;
}

主要流程:

  • 如果mNoFocusedWindowTimeoutTime有值,且mAwaitedFocusedApplication不为空
    • 超时:调用processNoFocusedWindowAnrLocked触发ANR
    • 未超时:更新检查时间
  • 继续检查input事件是否超时,如果超时,则调用onAnrLocked触发ANR

processNoFocusedAnrLocked的流程

// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::processNoFocusedWindowAnrLocked() {
    // 在触发ANR前,再获取一次当前的focusedApplication
    sp<InputApplicationHandle> focusedApplication =
            getValueByKey(mFocusedApplicationHandlesByDisplay, mAwaitedApplicationDisplayId);
    // 检查触发ANR时的条件是focusedApplication不为空
    // 如果此时focusedApplication为空,或者focusedApplication不等于前一个mAwaitedFocusedApplication表示已经切换application focus,取消触发ANR
    if (focusedApplication == nullptr ||
        focusedApplication->getApplicationToken() !=
                mAwaitedFocusedApplication->getApplicationToken()) {
        return; // The focused application has changed.
    }
    // 在触发ANR前,再获取一次当前的focusedWindowHandle 
    const sp<InputWindowHandle>& focusedWindowHandle =
            getFocusedWindowHandleLocked(mAwaitedApplicationDisplayId);
    // 检查触发ANR时focusedWindowHandle为空,如果此时focusedWindowHandle不为空,取消触发ANR
    if (focusedWindowHandle != nullptr) {
        return; // We now have a focused window. No need for ANR.
    }
    // 通过前面的判断,还是无法拦截,说明该ANR无可避免,最终触发ANR
    // 早期代码没有前面一系列的判断,是直接触发的ANR,会在性能较差的场景下出现误判
    onAnrLocked(mAwaitedFocusedApplication);
}

主要流程:

  • 在这个方法里面,再次检查focusedApplication
    • 如果当前focusedApplication为空,或者和之前记录的mAwaitedFocusedApplication不一致,则说明窗口已经切换,不需要报ANR
  • 再次检查focusedWindow是否未空
    • 如果不为空,则不需要报ANR
  • 检查都通过之后,才会调用onAnrLocked,报no Focused Window ANR

focusedApplication设置流程

// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
void InputDispatcher::setFocusedApplication(
        int32_t displayId, const sp<InputApplicationHandle>& inputApplicationHandle) {
    { // acquire lock
        std::scoped_lock _l(mLock);
        // 获取当前的focusedApplicationHandle
        sp<InputApplicationHandle> oldFocusedApplicationHandle =
                getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
        // 如果当前的focusedApplicationHandle跟触发ANR是的focusedApplicationHandle是一样且
        // 新的focusedApplicationHandle跟旧的不一样,说明focusedApplicationHandle有更新
        // 需要重置ANR计时
        if (oldFocusedApplicationHandle == mAwaitedFocusedApplication &&
            inputApplicationHandle != oldFocusedApplicationHandle) {
            // 重置ANR计时
            resetNoFocusedWindowTimeoutLocked();
        }
        if (inputApplicationHandle != nullptr && inputApplicationHandle->updateInfo()) {
            if (oldFocusedApplicationHandle != inputApplicationHandle) {
                // 赋值新的inputApplicationHandle到mFocusedApplicationHandlesByDisplay,在findFocusedWindowTargetsLocked()时用到
                mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle;
            }
        } else if (oldFocusedApplicationHandle != nullptr) {
            // 如果inputApplicationHandle为空,oldFocusedApplicationHandle不为空,需要清除oldFocusedApplicationHandle
            oldFocusedApplicationHandle.clear();
            // 走到这个流程会出现findFocusedWindowTargetsLocked()中focusedApplicationHandle为空
            mFocusedApplicationHandlesByDisplay.erase(displayId);
        }
    } // release lock
    // Wake up poll loop since it may need to make new input dispatching choices.
    mLooper->wake();
}

主要流程:

  • 如果inputApplicationHandle与oldFocusedApplication,则要重置ANR计时
  • 如果inputApplicationHandle不为空,则更新map中的值
  • 如果inputApplicationHandle为空,则清除oldFocusedApplication

这个方法,是从AMS调过来的,主要流程如下图:

focusedWindow设置流程

// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
// 当VSYNC信号来了之后,会调用到SurfaceFlinger的onMessageInvalidate()方法
// SurfaceFlinger::onMessageInvalidate() 
//   ==> SurfaceFlinger: updateInputFlinger()
//    ==> SurfaceFlinger: updateInputWindowInfo()
//      ==> InputManager::setInputWindows()
//        ==> InputDispatcher::setInputWindows()
//          ==> InputDispatcher::setInputWindowsLocked()
void InputDispatcher::setInputWindowsLocked(
        const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
    // ......
    const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
    // 更新mWindowHandlesByDisplay这个map,然后通过getWindowHandlesLocked()找newFocusedWindowHandle
    updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);
    sp<InputWindowHandle> newFocusedWindowHandle = nullptr;
    bool foundHoveredWindow = false;
    // 在mWindowHandlesByDisplay这个map里面找newFocusedWindowHandle
    for (const sp<InputWindowHandle>& windowHandle : getWindowHandlesLocked(displayId)) {
        // newFocusedWindowHandle要不为空,windowHandle具备focusable和visible属性
        if (!newFocusedWindowHandle && windowHandle->getInfo()->hasFocus &&
            windowHandle->getInfo()->visible) {
            // 给newFocusedWindowHandle赋值,最后这个值存到mFocusedWindowHandlesByDisplay这个map
            newFocusedWindowHandle = windowHandle;
        }
        if (windowHandle == mLastHoverWindowHandle) {
            foundHoveredWindow = true;
        }
    }
    if (!foundHoveredWindow) {
        mLastHoverWindowHandle = nullptr;
    }
    // 在mFocusedWindowHandlesByDisplay这个map里找当前的焦点窗口
    sp<InputWindowHandle> oldFocusedWindowHandle =
            getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
    // 判断oldFocusedWindowHandle是否等于newFocusedWindowHandle,如果相等则不走focus change流程
    if (!haveSameToken(oldFocusedWindowHandle, newFocusedWindowHandle)) {
        // 如果当前的焦点窗口不为空,需要从mFocusedWindowHandlesByDisplay移除掉
        if (oldFocusedWindowHandle != nullptr) {
            sp<InputChannel> focusedInputChannel =
                    getInputChannelLocked(oldFocusedWindowHandle->getToken());
            if (focusedInputChannel != nullptr) {
                CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
                                           "focus left window");
                synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
                // 新建一个FocusEntry加入到mInboundQueue去dispatch
                enqueueFocusEventLocked(*oldFocusedWindowHandle, false /*hasFocus*/);
            }
            // oldFocusedWindowHandle不为空时需要移除旧的
            mFocusedWindowHandlesByDisplay.erase(displayId);
        }
        // 走到这个流程,如果oldFocusedWindowHandle不为空,newFocusedWindowHandle为空,那么在findFocusedWindowTargetsLocked()中的focusedWindowHandle为空
        // 如果newFocusedWindowHandle不为空,更新mFocusedWindowHandlesByDisplay
        if (newFocusedWindowHandle != nullptr) {
            // 更新mFocusedWindowHandlesByDisplay,在findFocusedWindowTargetsLocked()时用到
            mFocusedWindowHandlesByDisplay[displayId] = newFocusedWindowHandle;
            // 新建一个FocusEntry加入到mInboundQueue去dispatch
            enqueueFocusEventLocked(*newFocusedWindowHandle, true /*hasFocus*/);
        }
        if (mFocusedDisplayId == displayId) {
            // 添加focusChanged到mCommandQueue,在dispatchOnce时会执行
            onFocusChangedLocked(oldFocusedWindowHandle, newFocusedWindowHandle);
        }
    }
    // ......
}

这个方法,是从WMS调过来的,主要流程如下图:

ANR可能得原因

设置focusedApplication和focusedWindow中间时间差太长,在这个时间差内发生了ANR

设置focusedApplication发生在,resumeTopActivity,也就是am_set_resumed_activity的时候。

设置focusedWindow发生在,onResume之后,调用WMS的addView添加完窗口之后。

window被设置成了no_focusable,无法响应焦点。

如果误将一个window设置成no_focusable,则窗口无法成为focusedWindow,也可能导致ANR的发生。

以上就是Android开发No Focused Window ANR产生原理解析的详细内容,更多关于Android No Focused Window ANR的资料请关注脚本之家其它相关文章!

相关文章

  • Android自定义View实现球形动态加速球

    Android自定义View实现球形动态加速球

    这篇文章主要为大家详细介绍了Android自定义View实现球形动态加速球,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • Android实现简单的分页效果

    Android实现简单的分页效果

    这篇文章主要为大家详细介绍了Android实现简单的分页效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • Android实现探探图片滑动效果

    Android实现探探图片滑动效果

    这篇文章主要为大家详细介绍了Android实现探探图片滑动效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Android开发中超好用的正则表达式工具类RegexUtil完整实例

    Android开发中超好用的正则表达式工具类RegexUtil完整实例

    这篇文章主要介绍了Android开发中超好用的正则表达式工具类RegexUtil,结合完整实例形式分析了Android正则表达式常见操作技巧,包括针对证件号、银行账号、手机号、邮编等的正则判断相关操作技巧,需要的朋友可以参考下
    2017-11-11
  • Flutter 状态管理scoped model源码解读

    Flutter 状态管理scoped model源码解读

    这篇文章主要为大家介绍了Flutter 状态管理scoped model源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Android入门之Handler的使用教程详解

    Android入门之Handler的使用教程详解

    这篇文章主要为大家详细介绍了Android中Handler机制的使用,文中的示例代码讲解详细,有需要的朋友可以借鉴参考下,希望能够对大家有所帮助,
    2022-11-11
  • Android使用DrawerLayout实现双向侧滑菜单

    Android使用DrawerLayout实现双向侧滑菜单

    这篇文章主要为大家详细介绍了Android使用DrawerLayout实现双向侧滑菜单,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • Android中EditText的drawableRight属性设置点击事件

    Android中EditText的drawableRight属性设置点击事件

    这篇文章主要介绍了Android中EditText的drawableRight属性的图片设置点击事件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • Android自定义控件单位尺寸实现代码

    Android自定义控件单位尺寸实现代码

    这篇文章主要介绍了Android自定义控件单位尺寸实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Android实现自动轮询的RecycleView

    Android实现自动轮询的RecycleView

    这篇文章主要为大家详细介绍了Android实现自动轮询的RecycleView,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10

最新评论