本文會分析觸控事件的產生 -> 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個元件之間的邏輯:
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()
方法,引數就是我們接收到的事件資訊與資料。
上面整個過程可以用下圖表示:
其實上面整個過程是利用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隨心