美文网首页
AndroidM源码学习——事件分发

AndroidM源码学习——事件分发

作者: 奥利奥真好吃 | 来源:发表于2018-01-15 22:45 被阅读0次

    Android的事件分发总的来说还是比较复杂的,代码量也很大,这里主要是从整体的流程与结构上对整个事件分发系统做一个初步阐述,一些详细的内容还是需要从每个函数上做具体的理解与分析。


    点击查看大图.png

    该时序图分四个部分,第一部分是按键的读取与分发两个线程是如何进行协调工作的,第二个部分是按键的拦截过程,第三个部分是按键或触屏事件的分发过程,第四个部分描述Activity中关于各类事件的重写是怎样来的。
    首先看第一个部分,
    在InputManager.cpp中创建读取事件的线程和分发事件的线程,这两个线程负责与底层驱动进行联系。

    void InputManager::initialize() {
        mReaderThread = new InputReaderThread(mReader);
        mDispatcherThread = new InputDispatcherThread(mDispatcher);
    }
    

    各自在threadLoop函数中启动线程
    InputReader线程使用getEvents()方法获取input事件,封装为RawEvent事件,之后KeyboardInputMapper::process 对各个不同的input类型进行处理
    目前android实现了多种类型的InputMapper,例如滑盖/翻盖SwitchInputMapper、键盘KeyboardInputMapper、鼠标CursorInputMapper、游戏手柄JoystickInputMapper、轨迹球TrackballInputMapper、多点触屏MultiTouchInputMapper以及单点触屏SingleTouchInputMapper。
    processEventsLocked、processEventsForDeviceLocked处理事件,在此过程中如有设备改变发生,还会调用addDeviceLocked、removeDeviceLocked、handleConfigurationChangedLocked
    KeyboardInputMapper::processKey 这里将驱动扫描码转换为上层键值,并将事件封装成NotifyKeyArgs,之后调用相关事件的监听接口函数 getListener()->notifyKey(&args);
    而后interceptKeyBeforeQueueing方法一直传入java层的PhoneWindowManager

    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
        /* Intercepts a key event immediately before queueing it.
         * The policy can use this method as an opportunity to perform power management functions
         * and early event preprocessing such as updating policy flags.
         *
         * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
         * should be dispatched to applications.
         */
        virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0;
    
        /* Allows the policy a chance to intercept a key before dispatching. */
        virtual nsecs_t interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
                const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
    

    最后调用Looper的wake函数唤醒线程

    第二个部分事件的分发中InputDispatcher::dispatchOnceInnerLocked根据不同的事件类型进行分发处理,这些事件类型有EventEntry::TYPE_KEY、EventEntry::TYPE_MOTION、EventEntry::TYPE_DEVICE_RESET等等,而后InputDispatcher::dispatchKeyLocked判断是否有POLICY_FLAG_PASS_TO_USER并决定是否进行拦截。

        // Give the policy a chance to intercept the key.
        if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
            if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
                CommandEntry* commandEntry = postCommandLocked(
                        & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
                if (mFocusedWindowHandle != NULL) {
                    commandEntry->inputWindowHandle = mFocusedWindowHandle;
                }
                commandEntry->keyEntry = entry;
                entry->refCount += 1;
                return false; // wait for the command to run
            } else {
                entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
            }
        } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
            if (*dropReason == DROP_REASON_NOT_DROPPED) {
                *dropReason = DROP_REASON_POLICY;
            }
        }
    

    最后 int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime);查找当前焦点窗口。

    int32_t InputDispatcher::findFocusedWindowTargetsLocked
     // If there is no currently focused window and no focused application
        // then drop the event.
        if (mFocusedWindowHandle == NULL) {
            if (mFocusedApplicationHandle != NULL) {
                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                        mFocusedApplicationHandle, NULL, nextWakeupTime,
                        "Waiting because no window has focus but there is a "
                        "focused application that may eventually add a window "
                        "when it finishes starting up.");
                goto Unresponsive;
            }
    
            ALOGI("Dropping event because there is no focused window or focused application.");
            injectionResult = INPUT_EVENT_INJECTION_FAILED;
            goto Failed;
        }
    

    如果找不到当前的焦点窗口则放弃当前的事件,如果找到了,对InputTarget进行一次封装。

        // Success!  Output targets.
        injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
        addWindowTargetLocked(mFocusedWindowHandle,
                InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
                inputTargets);
    
        // Done.    
    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;
    }
    

    InputDispatcher::initializeKeyEvent将事件封装为KeyEvent,等同与上层应用的KeyEvent。
    在doInterceptKeyBeforeDispatchingLockedInterruptible函数中将事件传到java层的PhoneWindowManager。

        nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
                &event, entry->policyFlags);
    

    然后使用之前查找焦点窗口后封装的InputTarget获得inputTarget.inputChannel并调用
    prepareDispatchCycleLocked方法

    void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
            EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    #if DEBUG_DISPATCH_CYCLE
        ALOGD("dispatchEventToCurrentInputTargets");
    #endif
    
        ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
    
        pokeUserActivityLocked(eventEntry);
    
        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);
            } else {
    #if DEBUG_FOCUS
                ALOGD("Dropping event delivery to target with channel '%s' because it "
                        "is no longer registered with the input dispatcher.",
                        inputTarget.inputChannel->getName().string());
    #endif
            }
        }
    }
    

    InputDispatcher::prepareDispatchCycleLocked确认连接状态并进行可能的事件分割

    // Skip this event if the connection status is not normal.
        // We don't want to enqueue additional outbound events if the connection is broken.
        if (connection->status != Connection::STATUS_NORMAL) {
    #if DEBUG_DISPATCH_CYCLE
            ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
                    connection->getInputChannelName(), connection->getStatusLabel());
    #endif
            return;
        }
        // Split a motion event if needed.
        if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
            ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);
    
            MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
            if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
                MotionEntry* splitMotionEntry = splitMotionEvent(
                        originalMotionEntry, inputTarget->pointerIds);
                if (!splitMotionEntry) {
                    return; // split event was dropped
                }
    #if DEBUG_FOCUS
                ALOGD("channel '%s' ~ Split motion event.",
                        connection->getInputChannelName());
                logOutboundMotionDetailsLocked("  ", splitMotionEntry);
    #endif
                enqueueDispatchEntriesLocked(currentTime, connection,
                        splitMotionEntry, inputTarget);
                splitMotionEntry->release();
                return;
            }
        }
    

    InputDispatcher::enqueueDispatchEntriesLocked调用enqueueDispatchEntryLocked封装KeyEntry/MotionEntry等

        // This is a new event.
        // Enqueue a new dispatch entry onto the outbound queue for this connection.
        DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
                inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
                inputTarget->scaleFactor);
    
        // Apply target flags and update the connection's input state.
        switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
            KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
            dispatchEntry->resolvedAction = keyEntry->action;
            dispatchEntry->resolvedFlags = keyEntry->flags;
    
            if (!connection->inputState.trackKey(keyEntry,
                    dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
    #if DEBUG_DISPATCH_CYCLE
                ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
                        connection->getInputChannelName());
    #endif
                delete dispatchEntry;
                return; // skip the inconsistent event
            }
            break;
        }
    

    在startDispatchCycleLocked函数向客户端发送事件(最后还是sendMessage)

            // Publish the event.
            status_t status;
            EventEntry* eventEntry = dispatchEntry->eventEntry;
            switch (eventEntry->type) {
            case EventEntry::TYPE_KEY: {
                KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
    
                // Publish the key event.
                status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
                        keyEntry->deviceId, keyEntry->source,
                        dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
                        keyEntry->keyCode, keyEntry->scanCode,
                        keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                        keyEntry->eventTime);
    

    openInputChannelPair创建一对输入通道,一个用于InputDispatch,一个用于应用程序的输入队列。

        /**
         * Creates a new input channel pair.  One channel should be provided to the input
         * dispatcher and the other to the application's input queue.
         * @param name The descriptive (non-unique) name of the channel pair.
         * @return A pair of input channels.  The first channel is designated as the
         * server channel and should be used to publish input events.  The second channel
         * is designated as the client channel and should be used to consume input events.
         */
        public static InputChannel[] openInputChannelPair(String name) {
            if (name == null) {
                throw new IllegalArgumentException("name must not be null");
            }
    
            if (DEBUG) {
                Slog.d(TAG, "Opening input channel pair '" + name + "'");
            }
            return nativeOpenInputChannelPair(name);
        }   
    

    然后给server、client两个通道赋值

    status_t InputChannel::openInputChannelPair(const String8& name,
            sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
        int sockets[2];
        if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
            status_t result = -errno;
            ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
                    name.string(), errno);
            outServerChannel.clear();
            outClientChannel.clear();
            return result;
        }
    
        int bufferSize = SOCKET_BUFFER_SIZE;
        setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
        setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
        setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
        setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    
        String8 serverChannelName = name;
        serverChannelName.append(" (server)");
        outServerChannel = new InputChannel(serverChannelName, sockets[0]);
    
        String8 clientChannelName = name;
        clientChannelName.append(" (client)");
        outClientChannel = new InputChannel(clientChannelName, sockets[1]);
        return OK;
    }
    

    这个channel有什么用呢?这就与Linux的共享内存与管道通信有关了,这里的客户端所对应的肯定是上层应用(读),而服务端对应的是InputDispatch(写),整个事件分发过程大致来说就是InputReader读取到input事件后发送给InputDispatch,InputDispatch接收到该事件后将其写入共享内存并通过管道通知ViewRootImpl中的InputChannel,这样整个事件就完成了从底层到上层应用的传递。

    第三个部分其实是将上层应用与底层的事件分发做了对接,即应用的Looper对Fd文件描述符状态的监听:

    int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data)
    

    添加需要监听的文件描述符并关联LooperCallback,当Fd的状态为input时触发调用LooperCallback中的handleEvent方法,这里是NativeInputEventReceiver

    class NativeInputEventReceiver : public LooperCallback 
    

    Looper::pollInner回调handleEvent

    // Invoke the callback.  Note that the file descriptor may be closed by
    // the callback (and potentially even reused) before the function returns so
    // we need to be a little careful when removing the file descriptor afterwards.
     int callbackResult = response.request.callback->handleEvent(fd, events, data);
     if (callbackResult == 0) {
        removeFd(fd, response.request.seq);
     }
     // Clear the callback reference in the response structure promptly because we
     // will not clear the response vector itself until the next poll.
     response.request.callback.clear();
    

    当通道由于window被移除或者输入完成时移除该回调

    NativeInputEventReceiver::handleEvent
    ···
        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
            // This error typically occurs when the publisher has closed the input channel
            // as part of removing a window or finishing an IME session, in which case
            // the consumer will soon be disposed as well.
            if (kDebugDispatchCycle) {
                ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred.  "
                        "events=0x%x", getInputChannelName(), events);
            }
            return 0; // remove the callback
        }   
    

    如果文件描述符可读则调用consumeEvents,将event转换为java层可识别的格式

    switch (inputEvent->getType()) {
    case AINPUT_EVENT_TYPE_KEY:
        if (kDebugDispatchCycle) {
            ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
        }
        inputEventObj = android_view_KeyEvent_fromNative(env,
                static_cast<KeyEvent*>(inputEvent));
        break;
    
    case AINPUT_EVENT_TYPE_MOTION: {
        if (kDebugDispatchCycle) {
            ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
        }
        MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
        if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
            *outConsumedBatch = true;
        }
        inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
        break;
    }
    

    之后的consume方法中有一个很重要的函数调用receiveMessage,它与前面的sendMessage相对应:

    status_t InputConsumer::consume(InputEventFactoryInterface* factory,
            bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    #if DEBUG_TRANSPORT_ACTIONS
        ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%lld",
                mChannel->getName().string(), consumeBatches ? "true" : "false", frameTime);
    #endif
    
        *outSeq = 0;
        *outEvent = NULL;
    
        // Fetch the next input message.
        // Loop until an event can be returned or no additional events are received.
        while (!*outEvent) {
            if (mMsgDeferred) {
                // mMsg contains a valid input message from the previous call to consume
                // that has not yet been processed.
                mMsgDeferred = false;
            } else {
                // Receive a fresh message.
                status_t result = mChannel->receiveMessage(&mMsg);
                if (result) {
                    // Consume the next batched event unless batches are being held for later.
                    if (consumeBatches || result != WOULD_BLOCK) {
                        result = consumeBatch(factory, frameTime, outSeq, outEvent);
                        if (*outEvent) {
    #if DEBUG_TRANSPORT_ACTIONS
                            ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
                                    mChannel->getName().string(), *outSeq);
    #endif
                            break;
                        }
                    }
                    return result;
                }
            }
    ···
    

    receiveMessage方法内部使用socket的接收函数recv进行接收:

    status_t InputChannel::receiveMessage(InputMessage* msg) {
        ssize_t nRead;
        do {
            nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
        } while (nRead == -1 && errno == EINTR);
    ···
    

    InputEventReceiver为抽象类,onInputEvent的最终实现在ViewRootImpl.java的内部类中

    final class WindowInputEventReceiver extends InputEventReceiver
    

    在doProcessInputEvents方法中判断只要事件队列不为空就一直调用deliverInputEvent;
    在deliverInputEvent方法中使用InputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);判断事件的完整性,例如down与up要成对;
    在processKeyEvent方法中调用shouldDropInputEvent()判断是否丢弃当前事件:

    protected boolean shouldDropInputEvent(QueuedInputEvent q) {
        if (mView == null || !mAdded) {
            Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent);
            return true;
        } else if ((!mAttachInfo.mHasWindowFocus
                && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped
                || (mPausedForTransition && !isBack(q.mEvent))) {
            // This is a focus event and the window doesn't currently have input focus or
            // has stopped. This could be an event that came back from the previous stage
            // but the window has lost focus or stopped in the meantime.
            if (isTerminalInputEvent(q.mEvent)) {
                // Don't drop terminal input events, however mark them as canceled.
                q.mEvent.cancel();
                Slog.w(TAG, "Cancelling event due to no window focus: " + q.mEvent);
                return false;
            }
    
            // Drop non-terminal input events.
            Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
            return true;
        }
        return false;
    }
    

    之后调用view的dispatchKeyEvent等方法将事件传递给UI。
    第四部分的类图则表明了Activity为何可以重写多种事件相关的函数,类与接口的继承关系不再赘述。

    相关文章

      网友评论

          本文标题:AndroidM源码学习——事件分发

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