Android觸控事件全過程分析:由產生到Activity.dispatchTouchEvent()
本文會分析 觸控事件的產生 -> 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
事件加入到InputDispatcher
的mInboundQueue
佇列中。 - InputDispatcher : 從
mInboundQueue
佇列取出事件,轉換成DispatchEntry
事件加入到Connection
的outboundQueue
佇列。然後使用InputChannel
分發事件到java層
。
可以用下面這張圖描述上面3個元件之間的邏輯:

EventHub_InputReader_InputDispatcher.png
InputChannel
我們可以簡單的把它理解為一個 socket
, 即可以用來接收資料或者傳送資料。一個 Window
會對應兩個 InputChannel
,這兩個 InputChannel
會相互通訊。一個 InputChannel
會註冊到 InputDispatcher
中, 稱為 serverChannel(服務端InputChannel)
。另一個會保留在應用程式程序的 Window
中,稱為 clientChannel(客戶端InputChannel)
。
下面來簡要了解一下這兩個 InputChannel
的建立過程,在 Android的UI顯示原理之Surface的建立 一文中知道,一個應用程式的 Window
在 WindowManagerService
中會對應一個 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
和 服務端InputChannel
的 Connection
。
那麼就繼續來看一下上面提到的 native訊息佇列
與 Native Looper
,它有什麼作用。
Android Native 訊息迴圈
我們知道 Looper
從 MessageQueue
中不斷獲取訊息並處理訊息。其實在 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); } }
即建立了一個 NativeMessageQueue
。 Looper
在迴圈讀取 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中的 NativeInputEventReceiver
的 handleEvent()
方法,引數就是我們接收到的事件資訊與資料。
上面整個過程可以用下圖表示:

觸控事件InputChannel的通訊.png
其實上面整個過程是利用 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); } } }
這個方法的主要處理是:
-
mInputConsumer.consume()
會呼叫到mChannel->receiveMessage(&mMsg);
,mChannel
其實就是客戶端InputChannel
,它通過socket
接收服務端InputChannel
的訊息。這個訊息其實就是觸控事件。 - 產生
MotionEvent
物件inputEventObj
,這個物件可以通過jni
呼叫 - 呼叫
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 觸控事件分發機制(二)原始事件訊息傳遞與分發的開始
最後:
歡迎關注我的 Android進階計劃 看更多幹貨
歡迎關注我的微信公眾號:susion隨心

微信公眾號.jpeg