Android觸控事件全過程分析:由產生到Activity.dispatchTouchEvent()

susion發表於2019-02-19

本文會分析觸控事件的產生 -> Activity.dispatchTouchEvent()整個過程。希望對於觸控事件的產生和系統處理過程有一個簡單瞭解即可。

觸控事件的產生 : 觸控事件與中斷

學習過Linux驅動程式編寫的同學可能知道Linux是以中斷的方式處理使用者的輸入事件。觸控事件其實是一種特殊的輸入事件。它的處理方式與輸入事件相同,只不過觸控事件的提供的資訊要稍微複雜一些。

觸控事件產生的大致原理是:使用者對硬體進行操作(觸控式螢幕)會導致這個硬體產生對應的中斷。該硬體的驅動程式會處理這個中斷。不同的硬體驅動程式處理的方式不同,不過最終都是將資料處理後存放進對應的/dev/input/eventX檔案中。所以硬體驅動程式完成了觸控事件的資料收集

/dev/input/eventX中的觸控事件是如何派發到Activity的呢?其實整個過程可以分為兩個部分:一個是native(C++)層的處理、一個是java層的處理。我們先來看一下native層是如何處理的。

系統對觸控事件的處理

native層主要是通過下面3個元件來對觸控事件進行處理的,這3個元件都執行在系統服務中:

  • EventHub : 它的作用是監聽、讀取/dev/input目錄下產生的新事件,並封裝成RawEvent結構體供InputReader使用。
  • InputReader : 通過EventHub/dev/input節點獲取事件資訊,轉換成EventEntry事件加入到InputDispatchermInboundQueue佇列中。
  • InputDispatcher : 從mInboundQueue佇列取出事件,轉換成DispatchEntry事件加入到ConnectionoutboundQueue佇列。然後使用InputChannel分發事件到java層

可以用下面這張圖描述上面3個元件之間的邏輯:

Android觸控事件全過程分析:由產生到Activity.dispatchTouchEvent()

InputChannel

我們可以簡單的把它理解為一個socket, 即可以用來接收資料或者傳送資料。一個Window會對應兩個InputChannel,這兩個InputChannel會相互通訊。一個InputChannel會註冊到InputDispatcher中, 稱為serverChannel(服務端InputChannel)。另一個會保留在應用程式程式的Window中,稱為clientChannel(客戶端InputChannel)

下面來簡要了解一下這兩個InputChannel的建立過程,在Android的UI顯示原理之Surface的建立一文中知道,一個應用程式的WindowWindowManagerService中會對應一個WindowState,WMS在建立WindowState時就會建立這兩個InputChannel,下面分別看一下他們的建立過程。

服務端InputChannel的建立及註冊

WindowManagerService.java

 public int addWindow(Session session...) {
        ...
        WindowState win = new WindowState(this, session, client, token,
            attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
        ...
        final boolean openInputChannels = (outInputChannel != null && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);  
        }
    ...
}

void openInputChannel(InputChannel outInputChannel) { //這個 outInputChannel 其實是應用程式獲取的inputchannel,它其實就是 inputChannels[1];
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(makeInputChannelName()); //通過native建立了兩個InputChannel,實際上是建立了兩個socket
    mInputChannel = inputChannels[0]; // 這裡將服務端的inputChannel儲存在了WindowState中
    mClientChannel = inputChannels[1];
    ....
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle); 
}
複製程式碼

registerInputChannel(..);實際上就是把服務端InputChannel註冊到了InputDispatcher中。上圖中的InputChannel其實就是在建立一個WindowState時註冊的。來看一下InputDispatcher中註冊InputChannel都幹了什麼:

InputDispatcher.cpp

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {

    sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor); //利用 inputChannel 建立了一個 connection,簡單的理解為socket的連結。

    int fd = inputChannel->getFd();
    mConnectionsByFd.add(fd, connection);

    //把這個 inputChannel 的 fd新增到 Looper中
    mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); 

    mLooper->wake();

    return OK;
}
複製程式碼

即利用InputChannel建立了一個Connection(InputDispatcher會通過這個Connection來向InputChannel發射資料),並且把這個InputChannel新增到mLooper中。

那這裡這個mLooper是什麼呢?是UI執行緒的那個Looper嗎?這部分我們後面再看,我們先來看一下客戶端InputChannel的相關過程。

客戶端InputChannel的相關邏輯

客戶端(應用程式)Window是如何通過InputChannel來接收觸控事件的呢?上面WindowState.openInputChannel()方法建立完InputChannel後會走到下面的程式碼:

ViewRootImpl.java

if (mInputChannel != null) { // mInputChannel 即為前面建立的 client inputchannel
    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
}
複製程式碼

這裡的new了一個WindowInputEventReceiver,它繼承自InputEventReceiver,看一下它的初始化過程:

InputEventReceiver.java

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    ...
    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);
    ...
}

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject inputChannelObj, jobject messageQueueObj) {
    ...
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();
    ...
}
複製程式碼

即主要初始化了NativeInputEventReceiver ,它的initialize()呼叫了setFdEvents():

android_view_InputEventReceiver.cpp

void NativeInputEventReceiver::setFdEvents(int events) {
    ...
    int fd = mInputConsumer.getChannel()->getFd(); // 這個fd 就是客戶端的 InputChannel 的 Connection
    ...
    mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
}
複製程式碼

這裡將客戶端的InputChannel的 Connection Fd加入到了Native Looper(下面會分析它)中。看一下addFd:

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    Request request;
    request.fd = fd;
    request.callback = callback;
    request.events = events;
    ...
    mRequests.add(fd, request);
}
複製程式碼

這裡就是利用fd來構造了一個Request注意 :這裡的callback就是NativeInputEventReceiver

OK,到這裡我們就看完了客戶端的InputChannel的初始化。並且還知道 Looper中是持有著客戶端InputChannel服務端InputChannelConnection

那麼就繼續來看一下上面提到的native訊息佇列Native Looper,它有什麼作用。

Android Native 訊息迴圈

我們知道LooperMessageQueue中不斷獲取訊息並處理訊息。其實在MessageQueue建立時還建立了一個native的訊息佇列。InputDispatcher派發的觸控事件就會放到這個訊息佇列中等待執行。先來看一下這個訊息佇列的建立:

//MessageQueue.java
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); 
    ...
    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

//android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();  // 其實就是主執行緒的Looper
    if (mLooper == NULL) {
        mLooper = new Looper(false); 
        Looper::setForThread(mLooper);
    }
}
複製程式碼

即建立了一個NativeMessageQueueLooper在迴圈讀取MessageQueue中的訊息的同時其實也讀取了NativeMessageQueue中的訊息:

Looper.java

public static void loop() {
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        ...
    }
}

Message next() {
    ....
    for (;;) {
        ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        ...
    }
}
複製程式碼

即呼叫到了nativePollOnce()方法。在這個方法中會讀取Server InputChannel傳送的觸控事件(怎麼傳送的後面會講到)。這個方法最終呼叫到Looper.pollInner()

int Looper::pollInner(int timeoutMillis) {
    ...
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  // 阻塞讀取event, 並儲存到eventItems
    ...

    for (int i = 0; i < eventCount; i++) { //依次處理每一個讀取到的event
        int fd = eventItems[i].data.fd; 
        uint32_t epollEvents = eventItems[i].events;

        ... 
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        ...
        pushResponse(events, mRequests.valueAt(requestIndex));
    }
}
複製程式碼

pollInner會呼叫pushResponse來依次處理每一個Event。這裡的mRequests.valueAt(requestIndex)就是前面客戶端的InputChannel註冊時的一些資訊。pushResponse會回撥到NativeInputEventReceiver.handleEvent()

InputDispatcher通過服務端InputChannel傳送觸控事件

上面我們知道了客戶端會通過Looper不斷處理NativeMessageQueue中的訊息,那觸控事件的訊息是如何傳送到NativeMessageQueue的呢?其實觸控原始事件是通過建立好的InputChannel.sendMessage()來傳送的:

status_t InputChannel::sendMessage(const InputMessage* msg) {
    size_t msgLength = msg->size();
    ssize_t nWrite;
    do {
        nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); //向socket中寫入資料
    } while (nWrite == -1 && errno == EINTR);
	...
    return OK;
}
複製程式碼

這個方法是InputDispatcher呼叫的。上面pollInner會因為InputChannel.sendMessage()傳送的資料而被喚醒。進而呼叫request中的NativeInputEventReceiverhandleEvent()方法,引數就是我們接收到的事件資訊與資料。

上面整個過程可以用下圖表示:

Android觸控事件全過程分析:由產生到Activity.dispatchTouchEvent()

其實上面整個過程是利用Socket完成了資料的跨程式通訊(InputDispatcher->NativeMessageQueue)。Socket阻塞/通知機制在這裡是十分高效的。NativeMessageQueue/Looper的主要作用是監聽InputDispatcher服務端InputChannel傳送的觸控資料。然後把這些資料通過NativeInputEventReceiver.handleEvent()回撥到客戶端。

NativeInputEventReceiver.handleEvent()

android_view_NativeInputEventReceiver.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;
    }
	...
    return 1;
}
複製程式碼

即主要通過consumeEvents()來處理這個事件:


status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,...) 
{

    ...
    InputEvent* inputEvent;
    status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);

    jobject inputEventObj;
    ...
    switch (inputEvent->getType()) {
        ...
        case AINPUT_EVENT_TYPE_MOTION: {
            MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent); // MotionEvent的產生
            inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
            break;
        }
    }

    if (inputEventObj) {
        env->CallVoidMethod(receiverObj.get(),
                gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
                displayId);
    }
}

}
複製程式碼

這個方法的主要處理是:

  1. mInputConsumer.consume()會呼叫到mChannel->receiveMessage(&mMsg);,mChannel其實就是客戶端InputChannel,它通過socket接收服務端InputChannel的訊息。這個訊息其實就是觸控事件。
  2. 產生MotionEvent物件inputEventObj,這個物件可以通過jni呼叫
  3. 呼叫jni方法gInputEventReceiverClassInfo.dispatchInputEvent()

其實gInputEventReceiverClassInfo.dispatchInputEvent()最終呼叫到java層InputEventReceiver.dispatchInputEvent(), 這個方法是java層分發觸控事件的開始。

InputEventReceiver的dispatchInputEvent()

InputEventReceiver.java

private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event);
}
複製程式碼

InputEventReceiver是一個抽象類,它在java層的實現是ViewRootImpl.WindowInputEventReceiver,它複寫了onInputEvent():

@Override
public void onInputEvent(InputEvent event) {
    enqueueInputEvent(event, this, 0, true);
}
複製程式碼

enqueueInputEvent()最終會呼叫deliverInputEvent()處理事件:

private void deliverInputEvent(QueuedInputEvent q) {
    ...
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }

    if (stage != null) {
        stage.deliver(q);
    } else {
        finishInputEvent(q);
    }
}
複製程式碼

InputStage可以理解為處理事件過程中的一步,多個InputStage可以組成一個處理流程,他們的組織形式類似於一個連結串列。看一下它的類組成應該就能猜到個大概邏輯:

abstract class InputStage {
    private final InputStage mNext;
    
    ...
    protected void onDeliverToNext(QueuedInputEvent q) {
        if (mNext != null) {
            mNext.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }
    ...
    protected int onProcess(QueuedInputEvent q) {
        return FORWARD;
    }
}
複製程式碼

事件QueuedInputEvent最終會由ViewPostImeInputStage處理,它的onProcess()會呼叫到processPointerEvent:

private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    final View eventTarget = (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ? mCapturingView : mView;

    boolean handled = eventTarget.dispatchPointerEvent(event);
	...
}
複製程式碼

這裡的eventTarget(View)其實就是DecorView,即回撥到了DecorView.dispatchPointerEvent():

View.java

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}
複製程式碼

DecorView.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
複製程式碼

這裡的Window.Callback其實就是Activity:

public class Activity extends ContextThemeWrapper implements Window.Callback,...{
複製程式碼

即回撥到Activity.dispatchTouchEvent()。到這裡就回到的我們常分析Android事件分發機制。這些內容會在下一篇文章來看一下。

本文內容參考自以下文章,感謝這些作者的細緻分析:

Android 觸控事件分發機制(一)從核心到應用 一切的開始

Android 觸控事件分發機制(二)原始事件訊息傳遞與分發的開始

Input系統—事件處理全過程

最後:

歡迎關注我的Android進階計劃看更多幹貨

歡迎關注我的微信公眾號:susion隨心

Android觸控事件全過程分析:由產生到Activity.dispatchTouchEvent()

相關文章