本文會分析觸摸事件的產生 -> Activity.dispatchTouchEvent()整個過程。但願對於觸摸事件的產生和系統處理過程有一個簡單瞭解便可。java
學習過Linux
驅動程序編寫的同窗可能知道Linux
是以中斷的方式處理用戶的輸入事件。觸摸事件實際上是一種特殊的輸入事件。它的處理方式與輸入事件相同,只不過觸摸事件的提供的信息要稍微複雜一些。android
觸摸事件產生的大體原理是:用戶對硬件進行操做(觸摸屏)會致使這個硬件產生對應的中斷。該硬件的驅動程序會處理這個中斷。不一樣的硬件驅動程序處理的方式不一樣,不過最終都是將數據處理後存放進對應的/dev/input/eventX
文件中。因此硬件驅動程序完成了觸摸事件的數據收集git
那/dev/input/eventX
中的觸摸事件是如何派發到Activity
的呢?其實整個過程能夠分爲兩個部分:一個是native(C++)層
的處理、一個是java層
的處理。咱們先來看一下native層
是如何處理的。github
在native層
主要是經過下面3個組件來對觸摸事件進行處理的,這3個組件都運行在系統服務中:bash
/dev/input
目錄下產生的新事件,並封裝成RawEvent
結構體供InputReader
使用。EventHub
從/dev/input
節點獲取事件信息,轉換成EventEntry
事件加入到InputDispatcher
的mInboundQueue
隊列中。mInboundQueue
隊列取出事件,轉換成DispatchEntry
事件加入到Connection
的outboundQueue
隊列。而後使用InputChannel
分發事件到java層
。能夠用下面這張圖描述上面3個組件之間的邏輯:微信
咱們能夠簡單的把它理解爲一個socket
, 便可以用來接收數據或者發送數據。一個Window
會對應兩個InputChannel
,這兩個InputChannel
會相互通訊。一個InputChannel
會註冊到InputDispatcher
中, 稱爲serverChannel(服務端InputChannel)
。另外一個會保留在應用程序進程的Window
中,稱爲clientChannel(客戶端InputChannel)
。session
下面來簡要了解一下這兩個InputChannel
的建立過程,在Android的UI顯示原理之Surface的建立一文中知道,一個應用程序的Window
在WindowManagerService
中會對應一個WindowState
,WMS
在建立WindowState
時就會建立這兩個InputChannel
,下面分別看一下他們的建立過程。app
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
的相關過程。
客戶端(應用程序)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
,它有什麼做用。
咱們知道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()
。
上面咱們知道了客戶端會經過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()
回調到客戶端。
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.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隨心