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

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

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

學習過Linux驅動程序編寫的同窗可能知道Linux是以中斷的方式處理用戶的輸入事件。觸摸事件實際上是一種特殊的輸入事件。它的處理方式與輸入事件相同,只不過觸摸事件的提供的信息要稍微複雜一些。android

觸摸事件產生的大體原理是:用戶對硬件進行操做(觸摸屏)會致使這個硬件產生對應的中斷。該硬件的驅動程序會處理這個中斷。不一樣的硬件驅動程序處理的方式不一樣,不過最終都是將數據處理後存放進對應的/dev/input/eventX文件中。因此硬件驅動程序完成了觸摸事件的數據收集git

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

系統對觸摸事件的處理

native層主要是經過下面3個組件來對觸摸事件進行處理的,這3個組件都運行在系統服務中:bash

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

能夠用下面這張圖描述上面3個組件之間的邏輯:微信

InputChannel

咱們能夠簡單的把它理解爲一個socket, 便可以用來接收數據或者發送數據。一個Window會對應兩個InputChannel,這兩個InputChannel會相互通訊。一個InputChannel會註冊到InputDispatcher中, 稱爲serverChannel(服務端InputChannel)。另外一個會保留在應用程序進程的Window中,稱爲clientChannel(客戶端InputChannel)session

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

服務端InputChannel的建立及註冊

WindowManagerService.javasocket

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都幹了什麼:ide

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()方法,參數就是咱們接收到的事件信息與數據。

上面整個過程能夠用下圖表示:

其實上面整個過程是利用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隨心

相關文章
相關標籤/搜索