Android底层事件分发机制-完结篇

前序

书接上文,前面我们讲了下Android输入事件的全貌,从底层硬件产生事件,经过IMS的InputReader加工转换再经过InputDspatcher分发转给WMS中间层,再通过WMS定位到准确的window进行我们熟悉的上层事件处理。

上文我们详细讲解了IMS的初始化流程及底层事件的拦截处理流程,今天我们继续讲解InputReader处理完的事件怎么进行分发的。

InputDspatcher 分发事件

将封装的事件压入到队列中并唤醒 InputDispatchert 线程进行分发处理。

InputDispatcher线程唤醒

kotlin 复制代码
status_t InputDispatcher::start() {
        if (mThread) {
            return ALREADY_EXISTS;
        }
        mThread = std::make_unique<InputThread>(
                "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
        return OK;
    }

和InputReader一样运行在单独的线程,执行 threadLoop 时的回调函数dispatchOnce

scss 复制代码
void InputDispatcher::dispatchOnce() {
        nsecs_t nextWakeupTime = LONG_LONG_MAX;
        { // acquire lock
            std::scoped_lock _l(mLock);
            mDispatcherIsAlive.notify_all();
​
            //注释1
            if (!haveCommandsLocked()) {
                //注释2
                dispatchOnceInnerLocked(&nextWakeupTime);
            }
            // 注释3
            if (runCommandsLockedInterruptible()) {
                nextWakeupTime = LONG_LONG_MIN;
            }
            const nsecs_t nextAnrCheck = processAnrsLocked();
            nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
​
            // We are about to enter an infinitely long sleep, because we have no commands or
            // pending or queued events
            if (nextWakeupTime == LONG_LONG_MAX) {
                mDispatcherEnteredIdle.notify_all();
            }
        } // release lock
        nsecs_t currentTime = now();
        int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
        mLooper->pollOnce(timeoutMillis);
    }

首先判断是否有指令需要执行,如果没有,则调用注释2处的dispatchOnceInnerLocked去根据事件类型进行处理,如果有则优先调用3处的runCommandsLockedInterruptible处理事件, 为了让线程可以立即进入事件处理,将nextWakeupTime 设置为LONG_LONG_MIN,这样线程在指令执行完毕后可以立即被唤醒去处理输入事件。

我们先看下2处的事件处理

php 复制代码
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
        .....
        // 如果 InputDispatcher 被冻结,则不进行派发操作
        if (mDispatchFrozen) {
            if (DEBUG_FOCUS) {
                ALOGD("Dispatch frozen.  Waiting some more.");
            }
            return;
        }
        .......
        // 如果没有待分发的事件,去 mInboundQueue 取出一个事件
        if (!mPendingEvent) {
            //如果没有待分发的事件,就去 mInboundQueue 中取出一个事件
            if (mInboundQueue.empty()) {
                .......
                if (!mPendingEvent) {
                    return;
                }
            } else {
                //不为空,则从头部取出一个事件
                mPendingEvent = mInboundQueue.front();
                mInboundQueue.pop_front();
                traceInboundQueueLengthLocked();
            }
            // Poke user activity for this event.
            if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
                pokeUserActivityLocked(*mPendingEvent);
            }
        }
​
        ALOG_ASSERT(mPendingEvent != nullptr);
        bool done = false;
        DropReason dropReason = DropReason::NOT_DROPPED; //1
        .....
        switch (mPendingEvent->type) {
            case EventEntry::Type::CONFIGURATION_CHANGED:  .....
            case EventEntry::Type::DEVICE_RESET: .....
            case EventEntry::Type::FOCUS:  .....
            case EventEntry::Type::DRAG:  .....
            case EventEntry::Type::KEY:.....
            case EventEntry::Type::MOTION: { //2
                std::shared_ptr<MotionEntry> motionEntry =
                        std::static_pointer_cast<MotionEntry>(mPendingEvent);
                //如果没有及时响应窗口切换操作
                if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
                    dropReason = DropReason::APP_SWITCH;
                }
                //事件过期
                if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
                    dropReason = DropReason::STALE;
                }
                //阻碍其他窗口获取事件
                if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
                    dropReason = DropReason::BLOCKED;
                }
                //3
                done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
                break;
            }
​
            case EventEntry::Type::SENSOR: .....
        }
​
        if (done) {
            if (dropReason != DropReason::NOT_DROPPED) {
                dropInboundEventLocked(*mPendingEvent, dropReason);
            }
            mLastDropReason = dropReason;
            //释放本次事件处理对象
            releasePendingEventLocked();
            //使得 inputDispatcher 能够快速处理下一个分发事件
            *nextWakeupTime = LONG_LONG_MIN;
        }
    }

上面代码主要截取了 Motion 事件相关的源码,主要的事情有下面几点:

如果 InputDispatcher 被冻结,则不进行派发操作,InputDispatcher 有三种状态,分别是正常,冻结,和禁用。

  • 注释1代表了事件被丢弃的原因,默认值是 NOT_DROPPED ,代表不会丢弃
  • 注释2对事件类型进行区分,这里主要是看 Motion 事件。
  • 注释3最终会调用 dispatchMotionLocked 来为这个事件寻找合适的窗口。

InputChannel注册

首先需要一定的WMS知识储备,我们回顾下Window是在ViewRootImpl的setView方法中传入WMS的。

ini 复制代码
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        ...
        InputChannel inputChannel = null;
        if ((mWindowAttributes.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            inputChannel = new InputChannel();
        }
        ...
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(),
                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                mAttachInfo.mOutsets, mInputChannel);
        ...
        if (mInputChannel != null) {
            mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                    Looper.myLooper());
        }
    }

mWindowSession是IWindowSession在app端的代理对象。实际执行的是Session类,最终会调用到WMS的addWindow

java 复制代码
public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int viewVisibility,
                         int displayId, int requestUserId, InsetsState requestedVisibility,
                         InputChannel outInputChannel...) {
​
        ...
        final boolean openInputChannels = (outInputChannel != null
                && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }
        ...
    }

注释1处调用WindowState打开InputChannel通道,继续看

ini 复制代码
void openInputChannel(InputChannel outInputChannel) {
    ...
    String name = getName();
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//1
    mInputChannel = inputChannels[0];
    mClientChannel = inputChannels[1];
    mInputWindowHandle.inputChannel = inputChannels[0];
    if (outInputChannel != null) {
        mClientChannel.transferTo(outInputChannel);
        mClientChannel.dispose();
        mClientChannel = null;
    }
    ...
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//2
}

public static InputChannel[] openInputChannelPair(String name) {
    ...
    return nativeOpenInputChannelPair(name);
}
ini 复制代码
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env...) {
    ...
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
​
​
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(serverChannel));
    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(clientChannel));
    ...
    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;
​
}
ini 复制代码
status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        ...
        return result;
    }
    ...
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

通过以上代码可以看出InputChannel使用的是socket通讯,且WindowState的openInputChannel中会根据名称返回的inputChannels是一个服务端和客户端的输入通道数组 其中:下标0:表示服务端的InputChannel 下标1:表示客户端的InputChannel

上面代码最后会调用mService.mInputManager.registerInputChannel是将wms在服务端的InputChannel注册到IMS中。这样在IMS输入系统就可以给服务端的InputChannel写入数据,在WMS的客户端InputChannel就可以读取数据

而调用mClientChannel.transferTo(outInputChannel)可以将app端的InputChannel和wms的客户端InputChannel关联 这样就可以向客户端InputChannel中写入数据然后通知app端的InputChannel,实际传递给ViewRootImpl对象处理,接着就是View层面的处理了。

继续跟进registerInputChannel会进入native侧

scss 复制代码
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    ...
    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);//1
​
}
​
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
        const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    return mInputManager->getDispatcher()->registerInputChannel(
            inputChannel, inputWindowHandle, monitor);
}
​
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
        ...
        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
        int fd = inputChannel->getFd();
        mConnectionsByFd.add(fd, connection);
        ...
        // registerInputChannel里面传入的monitor是false --> nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
        // 所以这个流程不会将窗口的channel放到mMonitoringChannels里面
        if (monitor) {
            mMonitoringChannels.push(inputChannel);
        }
        ...
}

最后InputChannel会进入native侧通过NativeInputManager调用InputDispatcher并进行注册,这里传入的inputchannel是WMS在服务端InputChannel,在InputDispatcher中创建一个Connection并加入到mConnectionsByFd队列中,key为当前inputChannel的fd。获取的时候也是通过inputChannel的fd去获取。

小结:WMS和输入系统InputDispatcher使用的socket通讯,在View端,WMS端以及IMS端都有一个InputChannel。

确认窗口

scss 复制代码
bool InputDispatcher::dispatchMotionLocked(
       nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
            ...
            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);
            }
            ...
            dispatchEventLocked(currentTime, entry, inputTargets);
            return true;
        }

回到dispatchMotionLocked最后会调用dispatchEventLocked,而且通过findFocusedWindowTargetsLocked方法来确认当前需要传递事件的窗口。

ini 复制代码
int32_t InputDispatcher::findFocusedWindowTargetsLocked(...inputTargets){
    ...
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);
    ...
}
​
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
        int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
    inputTargets.push();
​
    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    InputTarget& target = inputTargets.editTop();
    target.inputChannel = windowInfo->inputChannel;
    target.flags = targetFlags;
    target.xOffset = - windowInfo->frameLeft;
    target.yOffset = - windowInfo->frameTop;
    target.scaleFactor = windowInfo->scaleFactor;
    target.pointerIds = pointerIds;
​
}

windowInfo->inputChannel通道赋值给了target.inputChannel,windowInfo是一个mFocusedWindowHandle对象,是一个包含焦点Window信息的对象。

窗口注册

java 复制代码
public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int viewVisibility,
                         int displayId, int requestUserId, InsetsState requestedVisibility,
                         InputChannel outInputChannel...)
{
​
        ...
        final boolean openInputChannels = (outInputChannel != null
                && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }
        ...
        if (focusChanged) {
                displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                        false /*updateInputWindows*/);
        }
        displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
        ...
​
    }

updateInputWindowsLw经过层层调用最终会走到InputDispatcher::setInputWindows中,

ini 复制代码
void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
    ...
    mWindowHandles = inputWindowHandles;
​
    sp<InputWindowHandle> newFocusedWindowHandle;
    ...
    for (size_t i = 0; i < mWindowHandles.size(); i++) {
        const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
        ...
        if (windowHandle->getInfo()->hasFocus) {
            newFocusedWindowHandle = windowHandle;
        }
        ...
        mFocusedWindowHandle = newFocusedWindowHandle;
    }
    ...

}

我们知道了window相关的信息赋值,上下关联起来了,其中

  • mWindowHandles:代表所有Window的Handler对象
  • mFocusedWindowHandle:表示焦点Window的Handler对象 通过这些代码就让我们IMS中获取到了需要处理的焦点Window。

兜兜转转,我们再回到最终触发事件分发的方法

ini 复制代码
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    ...
    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);
​
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        }
        ...
    }
​
}
​
ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
    ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
    if (connectionIndex >= 0) {
        sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
        if (connection->inputChannel.get() == inputChannel.get()) {
            return connectionIndex;
        }
    }
​
    return -1;
​
}

dispatchEventLocked主要作用:轮询inputTargets,根据inputTarget.inputChannel获取其在mConnectionsByFd中的索引,根据索引获取Connection对象,并调用prepareDispatchCycleLocked进行处理。prepareDispatchCycleLocked方法内部调用了enqueueDispatchEntriesLocked方法。

scss 复制代码
void InputDispatcher::enqueueDispatchEntriesLocked(connection,..){
    // Enqueue dispatch entries for the requested modes.
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,...);//1
    ...
    // If the outbound queue was previously empty, start the dispatch cycle going.
    if (wasEmpty && !connection->outboundQueue.isEmpty()) {//2
        startDispatchCycleLocked(currentTime, connection);//3
    }
}

enqueueDispatchEntryLocked方法中会将输入事件重新封装为一个DispatchEntry并压入connection的outboundQueue队列中。然后判断如果事件不为空,则调用startDispatchCycleLocked循环发送输入事件。

rust 复制代码
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        ...
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
        }
        case EventEntry::TYPE_MOTION: {
        ...
        status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
                motionEntry->deviceId, motionEntry->source, motionEntry->displayId,
                dispatchEntry->resolvedAction, motionEntry->actionButton,
                dispatchEntry->resolvedFlags, motionEntry->edgeFlags,
                motionEntry->metaState, motionEntry->buttonState,
                xOffset, yOffset, motionEntry->xPrecision, motionEntry->yPrecision,
                motionEntry->downTime, motionEntry->eventTime,
                motionEntry->pointerCount, motionEntry->pointerProperties,
                usingCoords);
        ...
        }
        ...
        connection->outboundQueue.dequeue(dispatchEntry);
        connection->waitQueue.enqueueAtTail(dispatchEntry)
    }   
    ...
​
}

startDispatchCycleLocked中通过publishKeyEvent会将事件写入到WMS传递下来的InputChannel通道中。这样WMS端的InputChannel就可以通过socket获取到数据。在发送完毕后会将事件移出outboundQueue队列,并放入到waitQueue等待队列中,等待事件处理完毕后再移出.

那什么时候知道事件处理完毕呢?

在InputDispatcher::registerInputChannel方法里面注册了handleReceiveCallback回调:

arduino 复制代码
status_t InputDispatcher::registerInputChannel(...) {
        ...
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
        ...
}

当app层的事件处理完毕之后就会回调handleReceiveCallback

arduino 复制代码
int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
        InputDispatcher * d = static_cast < InputDispatcher * > (data);
        ...
        for (;;) {
            uint32_t seq;
            bool handled;
            status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
            if (status) {
                break;
            }
            d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
            gotOne = true;
            }
        if (gotOne) {
            d->runCommandsLockedInterruptible();
            if (status == WOULD_BLOCK) {
                return 1;
            }
        }
        ...
        // Unregister the channel.
        d->unregisterInputChannelLocked(connection->inputChannel, notify);
    }

先调用finishDispatchCycleLocked去往mCommandQueue里面加入一个执行doDispatchCycleFinishedLockedInterruptible的命令。

ini 复制代码
void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
        CommandEntry* commandEntry) {
    sp<Connection> connection = commandEntry->connection;
    ...
    if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
        KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
        restartEvent = afterKeyEventLockedInterruptible(connection,
                dispatchEntry, keyEntry, handled);
    } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) {
        MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
        restartEvent = afterMotionEventLockedInterruptible(connection,
                dispatchEntry, motionEntry, handled);
    } else {
        restartEvent = false;
    }
    ...
    DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
    ...
    if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
        connection->waitQueue.dequeue(dispatchEntry);
        ...
    }
}

该方法会将connection->waitQueue出栈,这样整个输入系统的分发过程就闭环了。

简单总结下:

  1. ViewRootImpl在setView方法中创建一个InputChannel通道并在将Window添加给WMS的时候作为参数传递给WMS。
  2. WMS在添加Window的过程中会调用updateInputWindows,这个方法最终会调用到InputDispatcher::setInputWindows中, 并给InputDispatcher的Window队列以及焦点Window赋值,这样IMS就可以找到对应的Window了
  3. 在WMS在添加Window的过程中还会创建一个socketpair通道的InputChannel,其中客户端的socket与app层的InputChannel关联,用于WMS与app通讯 服务端的socket传递给IMS,用于IMS和WMS通讯。
  4. 客户端在接收到输入事件后会根据当前获取焦点Window的InputChannel找到对应的Connection连接,这个Connection用于与WMS进行通讯,内部其实就是使用前面的socket通讯。
  5. 事件分发后将输入事件放入到waitQueue中,等待事件处理完毕后,将事件移出waitQueue

结尾

本文将事件分发的流程从底到上梳理了一下,希望能帮助大家清楚知道硬件产生的事件是怎么从输入系统传到app层进行处理的,欢迎关注我学习硬核技术,感谢支持,欢迎关注、转发、评论。

微信公众号首发:欢迎关注、转发、评论

相关推荐
qq_390161773 分钟前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test32 分钟前
js下载excel示例demo
前端·javascript·excel
AaVictory.41 分钟前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
知孤云出岫1 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web
贩卖纯净水.1 小时前
Chrome调试工具(查看CSS属性)
前端·chrome