事件分發機制-InputEvent進入ViewGroup之前的流轉

前言

系列總結回顧:
ViewGroup事件分發總結-TouchTarget
ViewGroup事件分發總結-多點觸摸事件拆分

關於Android觸摸事件分發機制,在日常應用層開發工作中最常接觸的是ViewGroup中的事件派發。當應用窗口接收到系統傳來的Event後,到將Event傳入ViewGroup前還有一段路程,本文就來分析下這個過程。

源碼探究

文中源碼基於 Android 10.0

事件監聽註冊

APP側想要從系統接收觸摸事件,首先需要進行“接收器”的註冊。我們知道Acitivty啓動在onResume之後,會創建ViewRootImpl,然後調用它的setView方法向WindowManagerService添加窗口,而“接收器”的註冊就是在這個階段。

[ViewRootImpl#setView]

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            // ···
            // 創建InputChannel用於輸入事件的傳輸
            mInputChannel = new InputChannel();
            // ···
            // 添加給WindowManagerService
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                    mTempInsets);
            // ···
            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    // 存儲事件的隊列
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                // 初始化並註冊輸入事件接收器
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                        Looper.myLooper());
            }
            // ···
        }
    }
}

該方法中幾個比較關鍵的步驟:

  1. 首先創建InputChannel,它用於系統進程傳輸輸入事件給APP進程。
  2. 接着將InputChannel傳給WindowManagerService,WMS會對InputChannel進行初始化。
  3. 創建用於存儲系統傳來的事件的隊列。
  4. 創建InputEventReceiver,並傳入InputChannel和主線程Looper,在構造函數中進行註冊監聽。

InputChannel的初始化

接下來看其中第二步驟,WindowSession#addToDisplay會調用WindowManagerService的addWindow方法:
[WindowManagerService#addWindow]

public int addWindow(Session session, IWindow client, int seq,
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState) {
    // ···
        final boolean openInputChannels = (outInputChannel != null
                && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            // 打開InputChannel(win是WindowState)
            win.openInputChannel(outInputChannel);
        }
    // ···
}

win的實例是WindowState,一個WindowState表示一個應用端窗口

[WindowState#openInputChannel]

void openInputChannel(InputChannel outInputChannel) {
    if (mInputChannel != null) {
        throw new IllegalStateException("Window already has an input channel.");
    }
    String name = getName();
    // 創建native層InputChannel數組(包含兩個InputChannel)
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
    // 第一個用於輸出事件
    mInputChannel = inputChannels[0];
    // 第二個用於接收事件
    mClientChannel = inputChannels[1];
    // InputWindowHandle作爲具柄提供給InputManagerService,用來尋找目標窗口。
    // IWindow對應一個應用側窗口,將其作爲token。
    mInputWindowHandle.token = mClient.asBinder();
    // outInputChannel即在應用進程創建傳過來的InputChannel
    if (outInputChannel != null) {
        // 將mClientChannel中的實例狀態轉移到outInputChannel中
        mClientChannel.transferTo(outInputChannel);
        // 釋放mClientChannel中的引用
        mClientChannel.dispose();
        mClientChannel = null;
    } else {
        // If the window died visible, we setup a dummy input channel, so that taps
        // can still detected by input monitor channel, and we can relaunch the app.
        // Create dummy event receiver that simply reports all events as handled.
        mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
    }
    // 將mInputChannel和IWindow註冊給InputManagerService保存
    mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
}

該方法中首先調用native方法打開兩個InputChannel,一個用於系統端寫入事件,一個用於應用端讀取事件(當事件處理完畢後,應用端InputChannel會用來寫入反饋,系統端InputChannel用來讀取反饋)。之後將用於發送事件的InputChannel註冊給InputManagerService,將用於接收事件的InputChannel傳回給應用窗口。

打開InputChannel

這裏的打開InputChannel指的是什麼呢?進入native層方法看看:

[android_view_InputChannel#android_view_InputChannel_nativeOpenInputChannelPair]

static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) {
    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
    std::string name = nameChars;
    env->ReleaseStringUTFChars(nameObj, nameChars);

    // serverChannel對應前文中的mInputChannel,clientChannel對應前文中的mClientChannel
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    // 分配serverChannel和clientChannel
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);

    if (result) {
        String8 message;
        message.appendFormat("Could not open input channel pair.  status=%d", result);
        jniThrowRuntimeException(env, message.string());
        return NULL;
    }

    // 創建Java層InputChannel數組
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    if (env->ExceptionCheck()) {
        return NULL;
    }

    // 創建Java層InputChannel,將serverChannel的指針保存在InputChannel.mPtr成員中
    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(serverChannel));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    // 同上
    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(clientChannel));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    // 返回InputChannel數組
    return channelPair;
}

可以看到這裏創建了native層的InputChannel,再將指針保存在Java層InputChannel.mPtr成員中。

看其中的InputChannel::openInputChannelPair方法:
[InputTransport#InputChannel::openInputChannelPair]

status_t InputChannel::openInputChannelPair(const std::string& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    // 創建一對socketpair,保存在sockets中
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        // 錯誤處理 ···
        return result;
    }

    // 設置sockopt讀寫緩存區
    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));

    std::string serverChannelName = name;
    serverChannelName += " (server)";
    // 創建Server端InputChannel,並持有其中一個socketpair
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

    std::string clientChannelName = name;
    clientChannelName += " (client)";
    // 創建Client端InputChannel,並持有其中一個socketpair
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

該方法中創建一對socketpair,這對socketpair中含有兩個相通的文件描述符fd,通過向fd寫數據和讀數據實現數據通信。Server端InputChannel和Client端InputChannel各持有一個socketpair,達到互相之間的雙向通信。

關於socketpair的詳細解釋可以參考《Android中socketpair雙向通信詳解》

到這裏我們知道InputChannel其實是對socketpair的一層封裝。

APP側InputChannel

回到WindowState#openInputChannel方法中,在完成包含socketpair的InputChannel對的創建後,需要將其中一個交給APP側,另一個交給InputManagerService。

通過InputChannel.transferTo方法將內部引用狀態轉移給之前在APP側創建和傳過來的InputChannel,該方法中調用了nativeTransferTo方法:
[android_view_InputChannel.cpp]

static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
        jobject otherObj) {
    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Other object already has a native input channel.");
        return;
    }

    // 通過Java層InputChannel.mPtr保存的地址獲取native層InputChannel對象
    NativeInputChannel* nativeInputChannel =
            android_view_InputChannel_getNativeInputChannel(env, obj);
    // 將nativeInputChannel的地址設置給APP側Java層InputChannel.mPtr
    android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
    // 清空自身的InputChannel.mPtr的值
    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
}

轉移對象就是通過更改InputChannel.mPtr的值來實現。

IMS側InputChannel

當轉移完對象後,就會進行InputManagerService側的註冊,在registerInputChannel方法中又會調用nativeRegisterInputChannel方法,傳入InputChannel。

[com_android_server_input_InputManagerService.cpp]

static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputChannelObj, jint displayId) {
    // 獲取native層InputManagerService
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    // 獲取native層InputChannel
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    // ···
    // 註冊進native層InputManagerService
    status_t status = im->registerInputChannel(env, inputChannel, displayId);
    // ···
}

該方法中取出native層對應的對象,native層InputManagerService的registerInputChannel方法中會把InputChannel註冊給它的InputDispatcher。

接着看InputDispatcher#registerInputChannel方法:
[InputDispatcher.cpp]

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        int32_t displayId) {
    // ···
    { // acquire lock
        std::scoped_lock _l(mLock);
        // ···
        // 創建Connection封裝InputChannel
        sp<Connection> connection = new Connection(inputChannel, false /*monitor*/);

        // 獲取InputChannel中socketpair的文件描述符
        int fd = inputChannel->getFd();
        mConnectionsByFd.add(fd, connection);
        mInputChannelsByToken[inputChannel->getToken()] = inputChannel;

        // Looper監聽fd可讀事件,當InputChannel對端寫入數據時會觸發handleReceiveCallback回調
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock

    // Wake the looper because some connections have changed.
    mLooper->wake();
    return OK;
}

該方法中創建Connection用來封裝InputChannel,並保存在mConnectionsByFd和mInputChannelsByToken集合中。並讓InputDispatcherThread中創建的Looper監聽InputChannel中fd的可讀事件。

Connection中有幾個重要成員:

  • InputPublisher inputPublisher:持有InputChannel,用於操作InputChannel進行寫入事件和讀取反饋。
  • Queue outboundQueue:緩存等待寫入InputChannel進行發送的事件。
  • Queue waitQueue:緩存已發送給應用窗口的事件,用於等待事件完成反饋。

到這裏便完成了InputChannel的初始化,創建了一對InputChannel分別保存在InputManagerService側和應用窗口側,從而實現傳輸鏈路的連接,之後雙方可以通過InputChannel中的socketpair進行雙向通信。

InputEventReceiver的初始化

InputEventReceiver用於APP側接收到數據後響應事件回調。回到ViewRootImpl#setView中,這裏創建其子類WindowInputEventReceiver,並傳入初始化完成的InputChannel和主線程Looper。

在InputEventReceiver的構造函數中,會利用InputChannel和主線程Looper中的MessageQueue進行初始化操作,其中會調用nativeInit方法:

[android_view_InputEventReceiver.cpp]

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
    // 通過InputChannel.mPtr成員獲得native層InputChannel對象
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    // ···

    // 通過MessageQueue.mPtr成員獲得native層MessageQueue對象
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    // ···

    // 創建native層InputEventReceiver,封裝Java層InputEventReceiver、InputChannel、MessageQueue
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    // 初始化,使Looper監聽fd中可讀事件
    status_t status = receiver->initialize();
    // ···

    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
    // 返回native層InputEventReceiver指針
    return reinterpret_cast<jlong>(receiver.get());
}

該方法中主要是創建了native層的InputEventReceiver,並返回其地址給Java層保存。在它的初始化操作中,使用MessageQueue對應的Looper監聽InputChannel中fd的ALOOPER_EVENT_INPUT可讀事件。

小結

輸入事件“接收器”的註冊過程,其實就是創建了一對InputChannel,分別保存在InputManagerService側和應用窗口側,利用其進行雙端數據傳輸。然後又創建WindowInputEventReceiver,用於應用窗口側在接收到事件後,進一步分發處理。

InputChannel

輸入事件接收

這裏不討論InputManagerService中的事件發送流程,僅從觸發事件接收開始分析。

當InputManagerService通過InputChannel的fd寫入事件數據後,應用窗口側的InputChannel就會產生可讀事件,將喚醒應用側Looper(NativeInputEventReceiver初始化時註冊了fd監聽和回調),然後觸發LooperCallback的handleEvent回調(NativeInputEventReceiver繼承LooperCallback)。

[android_view_InputEventReceiver.cpp]

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    // ···
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
    // ···
}

判斷類型如果是ALOOPER_EVENT_INPUT,則調用consumeEvents方法進一步處理。

[android_view_InputEventReceiver.cpp]

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    // ···
    // 循環不斷的處理(如果有事件)
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        // 從InputChannel中讀取一條數據,封裝在InputEvent中
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        // ···
        if (!skipCallbacks) {
            // ···
            jobject inputEventObj;
            // 判斷事件類型,創建對應事件對象
            switch (inputEvent->getType()) {
            case AINPUT_EVENT_TYPE_KEY:
                // 按鍵類型事件
                inputEventObj = android_view_KeyEvent_fromNative(env,
                        static_cast<KeyEvent*>(inputEvent));
                break;

            case AINPUT_EVENT_TYPE_MOTION: {
                // 觸摸類型事件
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }

            default:
                assert(false); // InputConsumer should prevent this from ever happening
                inputEventObj = NULL;
            }

            if (inputEventObj) {
                // 調用Java層方法分發事件對象
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
                if (env->ExceptionCheck()) {
                    ALOGE("Exception dispatching input event.");
                    skipCallbacks = true;
                }
                env->DeleteLocalRef(inputEventObj);
            } else {
                // ···
            }
        }

        if (skipCallbacks) {
            mInputConsumer.sendFinishedSignal(seq, false);
        }
    }
}

可以看到,這裏面循環不斷的從InputChannel中讀取數據封裝成InputEvent對象,並根據事件類型(按鍵、觸摸)創建對應的事件對象,之後調用Java層WindowInputEventReceiver的dispatchInputEvent方法進行事件派發。

小結

事件接收是通過先前註冊的主線程Looper監聽fd來實現,收到事件後封裝成對應類型的觸摸事件,調用Java層InputEventReceiver#dispatchInputEvent方法傳遞事件對象進行派發。

應用窗口事件派發

隊列分發

[InputEventReceiver#dispatchInputEvent]

private void dispatchInputEvent(int seq, InputEvent event) {
    // 保存event序列號,用於事件處理完畢後再通知InputManagerService
    mSeqMap.put(event.getSequenceNumber(), seq);
    // 處理事件
    onInputEvent(event);
}

WindowInputEventReceiver重寫了onInputEvent方法:
[WindowInputEventReceiver#onInputEvent]

public void onInputEvent(InputEvent event) {
    // ···
    // 事件入隊
    enqueueInputEvent(event, this, 0, true);
    // ···
}

這裏調用ViewRootImpl的enqueueInputEvent方法先將事件入隊。

[ViewRootImpl#enqueueInputEvent]

void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    // 從對象緩存池獲取一個QueuedInputEvent,封裝InputEvent
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

    // Always enqueue the input event in order, regardless of its time stamp.
    // We do this because the application or the IME may inject key events
    // in response to touch events and we want to ensure that the injected keys
    // are processed in the order they were received and we cannot trust that
    // the time stamp of injected events are monotonic.
    // 加入隊列尾
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;
    Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
            mPendingInputEventCount);

    // 此時processImmediately爲true,表示立即處理
    if (processImmediately) {
        // 直接開始處理
        doProcessInputEvents();
    } else {
        // 通過handler發送一個MSG_PROCESS_INPUT_EVENTS消息處理
        scheduleProcessInputEvents();
    }
}

加入隊列後就立即開始處理。

接着看doProcessInputEvents方法:
[ViewRootImpl#doProcessInputEvents]

void doProcessInputEvents() {
    // Deliver all pending input events in the queue.
    // 遍歷事件隊列
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        if (mPendingInputEventHead == null) {
            mPendingInputEventTail = null;
        }
        q.mNext = null;

        mPendingInputEventCount -= 1;
        
        // ···

        // 依次分發隊列頭事件
        deliverInputEvent(q);
    }

    // We are done processing all input events that we can process right now
    // so we can clear the pending flag immediately.
    if (mProcessInputEventsScheduled) {
        mProcessInputEventsScheduled = false;
        // 事件全部分發完畢,取消MSG_PROCESS_INPUT_EVENTS消息
        mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
    }
}

該方法中會依次將隊列中的事件全部進行分發。

[ViewRootImpl#deliverInputEvent]

private void deliverInputEvent(QueuedInputEvent q) {
    Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
            q.mEvent.getSequenceNumber());
    // ···

    InputStage stage;
    // ···
    // 獲取InputStage
    stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;

    // ···

    if (stage != null) {
        handleWindowFocusChanged();
        // 由InputStage進行具體的事件分發處理
        stage.deliver(q);
    } else {
        finishInputEvent(q);
    }
}

該方法中獲取InputStage後,由InputStage處理事件分發。

InputStage

InputStage是事件分發責任鏈中的一個基本單元。輸入事件可能會經過多個InputStage,直到有InputStage攔截處理,或者都沒有處理。

InputStage責任鏈的設置也是在ViewRootImpl的setView分發中:
[ViewRootImpl#setView]

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    // ···

    // Set up the input pipeline.
    CharSequence counterSuffix = attrs.getTitle();
    // 綜合處理未分發的事件
    mSyntheticInputStage = new SyntheticInputStage();
    // 分發給view hierarchy,優先級在輸入法之後
    InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
    // 分發給native activity,優先級在輸入法之後
    InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
            "aq:native-post-ime:" + counterSuffix);
    // 優先級在輸入法之後
    InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
    // 分發給輸入法(不支持觸摸事件)
    InputStage imeStage = new ImeInputStage(earlyPostImeStage,
            "aq:ime:" + counterSuffix);
    // 分發給view hierarchy,優先級在輸入法之前(不支持觸摸事件)
    InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
    // 分發給native activity,優先級在輸入法之前(不支持觸摸事件)
    InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
            "aq:native-pre-ime:" + counterSuffix);

    // mFirstInputStage是優先級最高的InputStage
    mFirstInputStage = nativePreImeStage;
    // mFirstPostImeInputStage是輸入法之後優先級最高的InputStage
    mFirstPostImeInputStage = earlyPostImeStage;
}

可以看到通過組合包裝的方式創建了InputStage責任鏈,越早創建的InputStage越後執行。

這裏關心ViewPostImeInputStage,它負責分發事件到視圖樹,看它的deliver分發:
[ViewPostImeInputStage#deliver]

public final void deliver(QueuedInputEvent q) {
    if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
        // 如果標記FLAG_FINISHED則轉到下一個InputStage
        forward(q);
    } else if (shouldDropInputEvent(q)) {
        // 如果需要丟棄則標記FLAG_FINISHED,然後轉給下一個InputStage
        finish(q, false);
    } else {
        // onProcess分發處理事件並返回是否處理的結果,apply會根據結果繼續向下轉發
        apply(q, onProcess(q));
    }
}

直接看onProcess方法:
[ViewPostImeInputStage#onProcess]

protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        // 按鍵事件
        return processKeyEvent(q);
    } else {
        final int source = q.mEvent.getSource();
        if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
            // 觸摸事件
            return processPointerEvent(q);
        } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
            // 軌跡球事件
            return processTrackballEvent(q);
        } else {
            // 其他通用事件(例如外設)
            return processGenericMotionEvent(q);
        }
    }
}

該方法中根據不同事件類型,調用不同方法處理事件。

這裏看觸摸事件的處理,進入processPointerEvent方法:
[ViewPostImeInputStage#processPointerEvent]

private int processPointerEvent(QueuedInputEvent q) {
    // 事件對象轉換成MotionEvent
    final MotionEvent event = (MotionEvent)q.mEvent;
    // ···
    // 調用了View的dispatchPointerEvent方法
    boolean handled = mView.dispatchPointerEvent(event);
    // ···
    return handled ? FINISH_HANDLED : FORWARD;
}

mView就是在ViewRootImpl#setView時傳入的DecorView,這裏調用了它的dispatchPointerEvent方法進行MotionEvent的分發。

DecorView派發過程

當MotionEvent傳給DecorView後,還沒有直接派發給我們設置的視圖樹。

dispatchPointerEvent方法中又調用了dispatchTouchEvent方法:
[DecorView#dispatchTouchEvent]

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 獲取PhoneWindow中的Callback
    final Window.Callback cb = mWindow.getCallback();
    // 正常情況下會執行cb.dispatchTouchEvent(ev) 
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

可以看到DecorView並不會直接把MotionEvent派發給Content View,而是轉發給Window.Callback,這個Callback就是Activity實例(Activity實現了Window.Callback接口,Activity在attach方法中創建PhoneWindow時設置Callback)。

進入Activity:
[Activity#dispatchTouchEvent]

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // 又轉發給PhoneWindow處理
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

該方法中又優先將MotionEvent交由PhoneWindow處理,若沒有處理則再通過onTouchEvent由自身處理。

進入PhoneWindow:
[PhoneWindow#superDispatchTouchEvent]

public boolean superDispatchTouchEvent(MotionEvent event) {
    // 由調用了DecorView
    return mDecor.superDispatchTouchEvent(event);
}

這裏又調用了DecorView,不同的是調用的是superDispatchTouchEvent方法,該方法中會調用super.dispatchTouchEvent,即ViewGroup的dispatchTouchEvent方法。從這裏開始就正式將MotionEvent傳入ViewGroup,開始ViewGroup的事件派發流程。

小結

decorview-inputevent

尾聲

一個觸摸事件在派發給ViewGroup前,還經過了InputManagerService-APP窗口-窗口根視圖的漫長流程,其中事件傳輸的關鍵就是InputChannel和InputEventReceiver。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章