Android重学系列 IMS与事件分发(下)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android重学系列 IMS与事件分发(下)相关的知识,希望对你有一定的参考价值。

参考技术A 上一篇文章和大家聊到了IMS在SystemServer进程native层中的原理,本文来聊聊App进程是怎么监听IMS分发出来的输入信号的.

还记得我写过WMS系列文章 WMS在Activity启动中的职责 添加窗体(三) 中,提到了App第一次渲染的时候会通过ViewRootImpl的addWindow方法,在WMS中为当前的Activity中的PhoneWindow添加一个对应的WindowState进行管理。

让我们先看看ViewRootImpl中做了什么。

如果遇到什么问题,欢迎来到本文 https://www.jianshu.com/p/e26bb5fae995 下讨论

文件:/ frameworks / base / core / java / android / view / ViewRootImpl.java

在这个过程中,我们可以把它视作三大部分的逻辑

核心就是第二和第三点。先来看看第二点,Session的addToDisplay最后是调用到了WMS的addWindow中。

文件:/ frameworks / base / services / core / java / com / android / server / wm / WindowManagerService.java

我们把InputChannel相关的逻辑抽离出来:

我们来看看WindowState的openInputChannel方法。

文件:/ frameworks / base / services / core / java / com / android / server / wm / WindowState.java

能看到实际上这个过程诞生了一个很重要的对象InputWindowHandle,输入窗口的句柄。这个句柄最核心的对象就是通过WindowToken获取AppToken的InputApplicationHandle。

能看到这个过程,实际上和上一篇文章十分相似的monitorInput一节中的内容十分相似。

依次执行了如下的逻辑:

这样通过socketpair创建的一对socket对象,注册了一个新的发送端到IMS的native层中,就能被App端的InputChannel监听到。

从这里就可以知道,0号位置的InputChannel对应的socket就是服务端(发送端)。关于如何创建InputChannel,以及如何注册到IMS。这里就不多赘述,请阅读 IMS与事件分发(上) 。

这个对象很简单,他继承于InputEventReceiver。InputEventReceiver对象就是专门监听IMS输入事件的基类。每当IMS发送信号来了就会调用子类的onInputEvent方法,onBatchedInputEventPending。

我们先来看看InputEventReceiver的初始化。

核心实际上就是调用native方法在native层初始化了IMS事件监听器。

文件:/ frameworks / base / core / jni / android_view_InputEventReceiver.cpp

这里只是简单的生成一个NativeInputEventReceiver对象,并调用了NativeInputEventReceiver的initialize方法。为全局的clazz对象新增一个强引用计数。

从NativeInputEventReceiver的申明能看到实际上他是实现了LooperCallback。LooperCallback这个对象,可以阅读 Handler与相关系统调用的剖析(上) ,里面有讲解到LooperCallback实际上就是native层Looper回调后的监听对象,回调的方法就是虚函数handleEvent。

在NativeInputEventReceiver有一个十分重要的对象InputConsumer。当IMS回调了输入事件后,NativeInputEventReceiver使用InputConsumer在native层中进行处理。

构造函数没什么好看的,直接看看initialize初始化的方法。

能看到这里面实际上很简单,就是获取InputConsumer中的InputChannel中的fd,这里fd就是上面初始化好的接收端的InputChannel。因此就是获取主线程的Looper并使用Looper监听客户端的InputChannel。

一旦IMS有信号发送过来则立即回调LooperCallback中的handleEvent。

当输入信号从native层传送过来了,则会开始回调handleEvent方法。关于IMS如果读取输入事件,处理后传输过来,可以阅读我写的 IMS与事件分发(上) 。

大致上可以分为两种情况,分别对象Looper注册的事件类型ALOOPER_EVENT_INPUT和ALOOPER_EVENT_OUTPUT。

很多地方没解析清楚:

在NativeInputEventReceiver中,ALOOPER_EVENT_INPUT代表从驱动读取到的输入事件传递过来;ALOOPER_EVENT_OUTPUT代表此时需要关闭输入事件的监听,而传递过去的后返回的事件处理。

我们先来看看ALOOPER_EVENT_INPUT对应的事件处理。

核心处理方法是consumeEvents。

文件:/ frameworks / native / libs / input / InputTransport.cpp

先从InputChannel的recv系统调用获取socket里面的InputMessage数据。

虽然此时consumeBatches为false,但是result正常情况下不会是WOULD_BLOCK,会先执行consumeBatch批量处理触点事件。

在这个方法中分为两个类型处理:

能看到实际上Batch就是一个InputMessage的集合。每当检测到AMOTION_EVENT_ACTION_MOVE或者AMOTION_EVENT_ACTION_HOVER_MOVE的触点类型,则会添加到mBatches集合中,等待下一次的更新。

当下一次触点触发了回调,在这个outEvent链表不为空的循环前提下,canAddSample判断到当前PointerCount和之前的一致,会把InputMessage不断的添加到Batch的samples集合中。如果出现了不一致则需要consumeSamples进行更新Batch中记录的InputMessage。

这样就能跟踪到了这一批次的触点的轨迹,以及新增的触点。

如果只有单个触点则生成MotionEvent对象赋值给指针返回。

我们来看看InputEventReceiver是通过InputConsumer消费后是怎么触发接下来的逻辑。我们只看单点触发的逻辑。

实际上对应的是:

而onInputEvent这个方法实际上就是对应WindowInputEventReceiver。

可以看到最后回调到了enqueueInputEvent方法中。

能看到整个很久爱都难,就是生成一个obtainQueuedInputEvent对象,添加到mPendingInputEventTail链表的末端,调用scheduleProcessInputEvents方法分发。如果是需要立即响应则调用doProcessInputEvents方法。

能看到此时发送了一个MSG_PROCESS_INPUT_EVENTS一个Asynchronous异步消息。其实就是一个能在同步屏障内优先执行的消息。

核心还是调用了doProcessInputEvents。

Choreographer.mFrameInfo 更新了分发时间后,整个过程最核心的逻辑就是循环遍历mPendingInputEventHead调用deliverInputEvent进行事件的分发QueuedInputEvent。

逻辑分为如下几个步骤:

能看到这里面构建很多InputStage对象。这些对象都是通过责任链设计全部嵌套到一起。

我们简单的看看它的UML图,来区分他们的直接的关系:

先来看看InputStage的deliver

deliver的入口会判断当前QueuedInputEvent的状态。

我们来看看对整个链路从NativePreImeInputStage开始逆推回去,关键还是看apply中的方法。

在所有的InputStage中分为两类,一类是直接继承InputStage,一类是继承AsyncInputStage,我们优先看看AsyncInputStage。

在AsyncInputStage存储了一个QueuedInputEvent链表。当判断到事件打开了FLAG_FINISHED,其在核心方法forward做了如下的事情:

能看到这个过程中很简单,如果QueuedInputEvent持有了InputEventReceiver对象则会InputEventReceiver.finishInputEvent进行native方法的调用,告诉native层销毁了当前的事件。

能看到很简单就是调用InputConsumer的sendFinishedSignal方法发送该输入事件的序列号处理对应在InputDispatcher中事件。

当InputStage需要开始分发事件,就会调用apply方法,而apply中就会调用onProcess方法。每一个子类InputStage的onProcess其实就是意味着这个InputStage做了什么事情。

接下来我们就按照责任链的嵌套顺序来看看InputStage,每一个输入阶段都做了什么。

NativePreImeInputStage实际上就是就是处理InputQueue。

ViewPreImeInputStage 这个InputStage是预处理KeyEvent,把键盘等事件通过DecorView的dispatchKeyEventPreIme进行预处理分发。

ImeInputStage专门处理软键盘

IMS:InputDispatcher的焦点设置

IMS:InputDispatcher的焦点设置

android11-release
IMS:InputDispatcher线程分发事件
ANR InputDispatching TimeOut超时判断


KeyEvent 和 MotionEvent中对应inputTargets


  • dispatchKeyLocked 中 focusedWindowHandle、focusedApplicationHandle判断
  • dispatchMotionLocked 中 findTouchedWindowAtLocked 查找 windowHandle
    在这里插入图片描述

焦点窗口和焦点app设置


findFocusedWindowTargetsLocked
findTouchedWindowTargetsLocked

dispatchKeyLocked 中 focusedWindowHandle、focusedApplicationHandle


findFocusedWindowTargetsLocked

    sp<InputWindowHandle> focusedWindowHandle =
            getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
    sp<InputApplicationHandle> focusedApplicationHandle =
            getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
    // Focus tracking for keys, trackball, etc.
    std::unordered_map<int32_t, sp<InputWindowHandle>> mFocusedWindowHandlesByDisplay
            GUARDED_BY(mLock);

    std::unordered_map<int32_t, TouchState> mTouchStatesByDisplay GUARDED_BY(mLock);

    // Focused applications.
    std::unordered_map<int32_t, sp<InputApplicationHandle>> mFocusedApplicationHandlesByDisplay
            GUARDED_BY(mLock);

mFocusedWindowHandlesByDisplay设置

/**
 * Called from InputManagerService, update window handle list by displayId that can receive input.
 * A window handle contains information about InputChannel, Touch Region, Types, Focused,...
 * If set an empty list, remove all handles from the specific display.
 * For focused handle, check if need to change and send a cancel event to previous one.
 * For removed handle, check if need to send a cancel event if already in touch.
 */
void InputDispatcher::setInputWindowsLocked(
        const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
    if (DEBUG_FOCUS) {
        std::string windowList;
        for (const sp<InputWindowHandle>& iwh : inputWindowHandles) {
            windowList += iwh->getName() + " ";
        }
        ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str());
    }

    // Copy old handles for release if they are no longer present.
    const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);

    updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);

    sp<InputWindowHandle> newFocusedWindowHandle = nullptr;
    bool foundHoveredWindow = false;
    for (const sp<InputWindowHandle>& windowHandle : getWindowHandlesLocked(displayId)) {
        // Set newFocusedWindowHandle to the top most focused window instead of the last one
        if (!newFocusedWindowHandle && windowHandle->getInfo()->hasFocus &&
            windowHandle->getInfo()->visible) {
            newFocusedWindowHandle = windowHandle;
        }
        if (windowHandle == mLastHoverWindowHandle) {
            foundHoveredWindow = true;
        }
    }

    if (!foundHoveredWindow) {
        mLastHoverWindowHandle = nullptr;
    }

    sp<InputWindowHandle> oldFocusedWindowHandle =
            getValueByKey(mFocusedWindowHandlesByDisplay, displayId);

    if (!haveSameToken(oldFocusedWindowHandle, newFocusedWindowHandle)) {
        if (oldFocusedWindowHandle != nullptr) {
            if (DEBUG_FOCUS) {
                ALOGD("Focus left window: %s in display %" PRId32,
                      oldFocusedWindowHandle->getName().c_str(), displayId);
            }
            sp<InputChannel> focusedInputChannel =
                    getInputChannelLocked(oldFocusedWindowHandle->getToken());
            if (focusedInputChannel != nullptr) {
                CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
                                           "focus left window");
                synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
                enqueueFocusEventLocked(*oldFocusedWindowHandle, false /*hasFocus*/);
            }
            mFocusedWindowHandlesByDisplay.erase(displayId);
        }
        if (newFocusedWindowHandle != nullptr) {
            if (DEBUG_FOCUS) {
                ALOGD("Focus entered window: %s in display %" PRId32,
                      newFocusedWindowHandle->getName().c_str(), displayId);
            }
            mFocusedWindowHandlesByDisplay[displayId] = newFocusedWindowHandle;
            enqueueFocusEventLocked(*newFocusedWindowHandle, true /*hasFocus*/);
        }

        if (mFocusedDisplayId == displayId) {
            onFocusChangedLocked(oldFocusedWindowHandle, newFocusedWindowHandle);
        }
    }

    std::unordered_map<int32_t, TouchState>::iterator stateIt =
            mTouchStatesByDisplay.find(displayId);
    if (stateIt != mTouchStatesByDisplay.end()) {
        TouchState& state = stateIt->second;
        for (size_t i = 0; i < state.windows.size();) {
            TouchedWindow& touchedWindow = state.windows[i];
            if (!hasWindowHandleLocked(touchedWindow.windowHandle)) {
                if (DEBUG_FOCUS) {
                    ALOGD("Touched window was removed: %s in display %" PRId32,
                          touchedWindow.windowHandle->getName().c_str(), displayId);
                }
                sp<InputChannel> touchedInputChannel =
                        getInputChannelLocked(touchedWindow.windowHandle->getToken());
                if (touchedInputChannel != nullptr) {
                    CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
                                               "touched window was removed");
                    synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options);
                }
                state.windows.erase(state.windows.begin() + i);
            } else {
                ++i;
            }
        }
    }

    // Release information for windows that are no longer present.
    // This ensures that unused input channels are released promptly.
    // Otherwise, they might stick around until the window handle is destroyed
    // which might not happen until the next GC.
    for (const sp<InputWindowHandle>& oldWindowHandle : oldWindowHandles) {
        if (!hasWindowHandleLocked(oldWindowHandle)) {
            if (DEBUG_FOCUS) {
                ALOGD("Window went away: %s", oldWindowHandle->getName().c_str());
            }
            oldWindowHandle->releaseChannel();
        }
    }
}

mFocusedApplicationHandlesByDisplay设置

void InputDispatcher::setFocusedApplication(
        int32_t displayId, const sp<InputApplicationHandle>& inputApplicationHandle) {
    if (DEBUG_FOCUS) {
        ALOGD("setFocusedApplication displayId=%" PRId32 " %s", displayId,
              inputApplicationHandle ? inputApplicationHandle->getName().c_str() : "<nullptr>");
    }
    { // acquire lock
        std::scoped_lock _l(mLock);

        sp<InputApplicationHandle> oldFocusedApplicationHandle =
                getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);

        if (oldFocusedApplicationHandle == mAwaitedFocusedApplication &&
            inputApplicationHandle != oldFocusedApplicationHandle) {
            resetNoFocusedWindowTimeoutLocked();
        }

        if (inputApplicationHandle != nullptr && inputApplicationHandle->updateInfo()) {
            if (oldFocusedApplicationHandle != inputApplicationHandle) {
                mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle;
            }
        } else if (oldFocusedApplicationHandle != nullptr) {
            oldFocusedApplicationHandle.clear();
            mFocusedApplicationHandlesByDisplay.erase(displayId);
        }
    } // release lock

    // Wake up poll loop since it may need to make new input dispatching choices.
    mLooper->wake();
}

findTouchedWindowTargetsLocked->findTouchedWindowAtLocked 中 windowHandles


其实和上面一样windowHandles 从mWindowHandlesByDisplay获取
在这里插入图片描述

sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
                                                                 int32_t y, TouchState* touchState,
                                                                 bool addOutsideTargets,
                                                                 bool addPortalWindows) {
    if ((addPortalWindows || addOutsideTargets) && touchState == nullptr) {
        LOG_ALWAYS_FATAL(
                "Must provide a valid touch state if adding portal windows or outside targets");
    }
    // Traverse windows from front to back to find touched window.
    const std::vector<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);
    for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
        const InputWindowInfo* windowInfo = windowHandle->getInfo();
        if (windowInfo->displayId == displayId) {
            int32_t flags = windowInfo->layoutParamsFlags;

            if (windowInfo->visible) {
                if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                    bool isTouchModal = (flags &
                                         (InputWindowInfo::FLAG_NOT_FOCUSABLE |
                                          InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        int32_t portalToDisplayId = windowInfo->portalToDisplayId;
                        if (portalToDisplayId != ADISPLAY_ID_NONE &&
                            portalToDisplayId != displayId) {
                            if (addPortalWindows) {
                                // For the monitoring channels of the display.
                                touchState->addPortalWindow(windowHandle);
                            }
                            return findTouchedWindowAtLocked(portalToDisplayId, x, y, touchState,
                                                             addOutsideTargets, addPortalWindows);
                        }
                        // Found window.
                        return windowHandle;
                    }
                }

                if (addOutsideTargets && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
                    touchState->addOrUpdateWindow(windowHandle,
                                                  InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
                                                  BitSet32(0));
                }
            }
        }
    }
    return nullptr;
}

InputMonitor.java桥梁


frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

上面设置窗口是InputDispatcher内部方法,实质上窗口就是WMS设置下来的,通过WMS -> InputMonitor -> InputManagerService -> InputDispatcher桥梁,在updateInputWindowsLw方法中更新。
在这里插入图片描述
InputMonitor.java中InputDispatcher连接点:
在这里插入图片描述
在这里插入图片描述

上面代码明显日志:
setFocusedApplication displayId
setInputWindows displayId
Focus left window:
Focus entered window:

以上是关于Android重学系列 IMS与事件分发(下)的主要内容,如果未能解决你的问题,请参考以下文章

InputDispatcher线程分发事件-Android12

IMS:InputManagerService小结

IMS:Input事件可拦截位置

android-----事件分发机制测试系列

android-----事件分发机制测试系列

Android 事件分发MotionEvent.ACTION_DOWN 按下事件分发流程( Activity | ViewGroup | View )