從接觸Android開發以來,貌似Android的事件就一直伴随着我。從一開始的setOnclickListener到後來的setOnTouchListener以及各種手勢的事件,關于Android的事件傳遞機制,我覺得很多人都看了不止一遍了。借着這次大總結,我覺得有必要對這部分進行一下總結了。
之前寫了關于
View的測量、布局和繪制的過程,在繪制完成後,界面的元素就已經展示出來了。光有花裡胡哨的頁面對于一個完整的App是不夠的,因為我們需要的不僅僅是頁面展示,還包括了頁面互動,不是還有個職位叫UE(互動設計師)嘛。
說起互動,那麼可以說的就很多了。比如我點選這個按鈕會怎樣?長按又如何?這裡面有很多東西需要深入探究,我們需要做到知其然并且知其是以然,這裡我就假裝一次小白(咳咳,現在是大白。。)一點點分析Android的觸摸事件。
作為小白,我隻知道在View是所有子View的爸爸(這不廢話嘛),是以我肯定知道觸摸事件在View中一定有實作。翻開源碼找一找,果不其然:
public boolean dispatchTouchEvent(MotionEvent event) {
......
// 具體實作暫時不看
}
找到關于觸摸事件的分發了,那麼問題來了:這個事件分發是在何時調用的呢?我們知道View是一個基類,如果手機接收到觸摸事件時肯定直接或間接調用的是基類的
dispatchTouchEvent
方法,是以需要查找下有那個類調用了
dispatchTouchEvent
方法。不查不知道,一查吓一跳,N多個類都有調用這個
dispatchTouchEvent
方法。定睛一看,原來都是View的子類(虛驚一場)。既然外面沒有,那就找View目前有沒有方法調用吧。别說,還真有一個——
dispatchPointerEvent
:
// 代碼簡單,我喜歡。。
public final boolean dispatchPointerEvent(MotionEvent event) {
// 判斷是不是觸摸事件
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
這個方法代碼很少,我這種小白也是能夠了解的:通過判斷目前的事件是不是觸摸事件,如果是的話則需要将觸摸事件分發。好了,現在知道了dispatchPointerEvent方法調用了dispatchTouchEvent方法。是以現在我們需要找到誰調用了dispatchPointerEvent方法。

搜尋結果
可以從搜尋結果中看到,這裡就ViewRootImpl調用了這個方法。從這裡我就可以知道,觸摸事件的分發肯定是通過ViewRootImpl進行分發的。那麼,先去看下這個方法:
private int processPointerEvent(QueuedInputEvent q) {
// 擷取輸入事件,并将其轉換成MotionEvent
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
final View eventTarget =
(event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
mCapturingView : mView;
mAttachInfo.mHandlingPointerEvent = true;
// 調用dispatchPointerEvent去處理這次事件
boolean handled = eventTarget.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
// 如果被處理傳回FINISH_HANDLED ,否則傳回轉發狀态
return handled ? FINISH_HANDLED : FORWARD;
}
可以看到這個方法是ViewRootImpl的内部類ViewPostImeInputStage的方法,并且這個方法被onProcess調用,接下來需要看下
ViewPostImeInputStage
這個類到底有什麼作用:
ViewPostImeInputStage類
看注釋的意思是:将後期輸入事件傳遞給視圖層次結構。那麼就可以了解為輸入事件的傳遞了。那麼,先看下這個類是何時建立并在何處調用了
onProcess
方法的。
建立
可以看到,這個建立過程是在ViewRootImpl的setView方法中,在之前寫的
Activity顯示到Window的過程中講到setView是在Activity顯示的時候調用的方法,通過WindowManagerGlobal的addView方法調用了ViewRootImpl的setView方法。在這個方法裡面建立了ViewPostImeInputStage對象,在這裡也可以看到一個很有趣的現象:前面建立好的對象又當作參數傳入了下一個建立的對象,是以這邊需要看下這裡面到底有什麼玄機。這裡我們看下這些類的父類InputStage:
abstract class InputStage {
private final InputStage mNext;
protected static final int FORWARD = 0;
protected static final int FINISH_HANDLED = 1;
protected static final int FINISH_NOT_HANDLED = 2;
// 構造方法裡面傳入了InputStage作為下一個将要轉發的InputStage
/**
* Creates an input stage.
* @param next The next stage to which events should be forwarded.
*/
public InputStage(InputStage next) {
mNext = next;
}
/**
* Delivers an event to be processed.
* 提供要被處理的事件。
*/
public final void deliver(QueuedInputEvent q) {
// 如果事件的flag是完成了,則轉發事件
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
//如果需要丢棄這個事件
} else if (shouldDropInputEvent(q)) {
// 完成,但是傳入的是false
finish(q, false);
} else {
// 應用處理,裡面調用了onProcess即在過程中執行,當初次進入時應該會進入這個方法
apply(q, onProcess(q));
}
}
/**
* Marks the the input event as finished then forwards it to the next stage.
*/
protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
if (handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
}
forward(q);
}
/**
* Forwards the event to the next stage.
* 轉發事件到下一個階段
*/
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
/**
* Applies a result code from {@link #onProcess} to the specified event.
*/
protected void apply(QueuedInputEvent q, int result) {
if (result == FORWARD) {
forward(q);
} else if (result == FINISH_HANDLED) {
finish(q, true);
} else if (result == FINISH_NOT_HANDLED) {
finish(q, false);
} else {
throw new IllegalArgumentException("Invalid result: " + result);
}
}
/**
* Called when an event is ready to be processed.
* 傳回這個處理事件的代碼
* 如FORWARD(轉發)FINISH_HANDLED(完成,已經處理)FINISH_NOT_HANDLED(完成,沒有處理)
* 這個方法具體實作應當由其子類實作
* @return A result code indicating how the event was handled.
*/
protected int onProcess(QueuedInputEvent q) {
return FORWARD;
}
/**
* Called when an event is being delivered to the next stage.
* 傳遞到下一個InputState
*/
protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
// 如果下一個不為空,則調用deliver去處理;否則,完成這次輸入事件的處理
if (mNext != null) {
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
protected boolean shouldDropInputEvent(QueuedInputEvent q) {
// 如果目前傳入的View為空或者沒有被添加,此時應該抛棄調輸入事件
if (mView == null || !mAdded) {
Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
return true;
} else if ((!mAttachInfo.mHasWindowFocus
&& !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped
|| (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
|| (mPausedForTransition && !isBack(q.mEvent))) {
// This is a focus event and the window doesn't currently have input focus or
// has stopped. This could be an event that came back from the previous stage
// but the window has lost focus or stopped in the meantime.
// 這是一個焦點事件,視窗目前沒有輸入焦點或已經停止。
// 這可能是一個事件,從前一個階段回來,但視窗失去了重點或停止在此期間。
if (isTerminalInputEvent(q.mEvent)) {
// Don't drop terminal input events, however mark them as canceled.
// 不要丢棄終端輸入事件,但将它們标記為已取消。
q.mEvent.cancel();
Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent);
return false;
}
// Drop non-terminal input events.
Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent);
return true;
}
return false;
}
void dump(String prefix, PrintWriter writer) {
if (mNext != null) {
mNext.dump(prefix, writer);
}
}
private boolean isBack(InputEvent event) {
if (event instanceof KeyEvent) {
return ((KeyEvent) event).getKeyCode() == KeyEvent.KEYCODE_BACK;
} else {
return false;
}
}
}
從上面的代碼可以看出,InputStage是單向連結清單結構,從上到下依次處理,并将根據處理結果指派給事件然後轉發到下一個狀态處理。當全部處理完成後,會調用
finishInputEvent
這個方法去完成這次輸入事件的處理。現在可以解釋剛才的那個現象了:我們傳入的Stage作為接收上層Stage的事件轉發,并且根據狀态去處理。上面的注釋挺齊全的,這裡需要注意:
- deliver方法傳遞事件是會根據QueuedInputEvent的mFlags 屬性來判斷是forward還是finish或者apply,這個屬性除了在一開始生成的時候指派,其他修改的地方就是在finish方法中
-
onProcess在InputStage中隻是傳回了一個FORWARD狀态碼,其子類會根據自身處理傳回相應的狀态碼
看完
後,我們需要看下ViewPostImeInputStage的onProcess方法:InputStage
@Override
protected int onProcess(QueuedInputEvent q) {
// 按鍵事件處理
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
// 擷取事件的源頭
final int source = q.mEvent.getSource();
// 觸摸
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
// 安卓軌迹球類似與滑鼠等輸入
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
// 其他的輸入
return processGenericMotionEvent(q);
}
}
}
關于
processPointerEvent
我們在上面已經寫過,這裡不贅述。
好了,現在知道輸入事件是在各種InputStage中處理的,那麼到底是哪裡調用了InputStage的哪個方法呢?還是跟着代碼看一看吧:
調用位置
看下代碼:
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
InputStage stage;
// 如果true,則使用最後一個InputStage
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
// 根據shouldSkipIme來判斷使用哪個InputStage
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
// 調用deliver方法
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
// 根據mFlags是否為FLAG_UNHANDLED來判斷
public boolean shouldSendToSynthesizer() {
if ((mFlags & FLAG_UNHANDLED) != 0) {
return true;
}
return false;
}
下面需要查找哪裡調用了
deliverInputEvent
方法(方法調用較多):
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
// 從頭部得到目前的事件,頭部指向next
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
// 事件數量-1
mPendingInputEventCount -= 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
long eventTime = q.mEvent.getEventTimeNano();
long oldestEventTime = eventTime;
if (q.mEvent instanceof MotionEvent) {
MotionEvent me = (MotionEvent)q.mEvent;
if (me.getHistorySize() > 0) {
oldestEventTime = me.getHistoricalEventTimeNano(0);
}
}
mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
// 傳遞事件
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
// 給輸入事件排序
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
// 生成輸入事件,這個通過池來實作
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
// 始終按順序排列輸入事件,而不管其時間戳。
// 我們這樣做是因為應用程式或IME可能會響應觸摸事件而注入關鍵事件,
// 并且我們希望確定注入的鍵以接收到的順序進行處理,并且我們不能相信注入事件的時間戳是單調的。
// 主要意思是說還是需要排序,下面是排序過程(後面會有圖解釋下)
QueuedInputEvent last = mPendingInputEventTail;
// 設定最後一個事件等于事件的尾部
if (last == null) {
// 如果為空則頭部和尾部都等于這個事件
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
// 否則的話讓事件的尾部的next等于目前這個事件,并将mPendingInputEventTail指向最後一個事件
last.mNext = q;
mPendingInputEventTail = q;
}
// 資料數量+1
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
// 如果是立即執行(這裡隻看這個)
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
// 從名字上可以看出這是一個關于輸入事件的接收者
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
@Override
public void onBatchedInputEventPending() {
if (mUnbufferedInputDispatch) {
super.onBatchedInputEventPending();
} else {
scheduleConsumeBatchedInput();
}
}
@Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
}
}
InputEventReceiver.java
// 看注釋可以知道這個方法被native層調用,是以我覺得到這裡就應該算是java層的起點了
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
排序圖
上面是排序圖(靈魂畫家。。),通過上面的代碼可以看到,我們的事件是native層調用InputEventReceiver中的dispatchInputEvent方法進行傳遞的,在傳遞過程中需要對事件進行排序處理,即先來先處理。
好了,到了這裡基本上就是我們找到的整個觸摸事件的“根源”了。當然,這裡肯定不是最初的起點,但是起點可能太深了,需要一點點挖掘,量力而為。
整個過程已經完成了,我覺得有必要從後面到前面在梳理一遍:
流程圖
到此為止,事件從java層的事件産生到傳遞的整個流程就已經全部展示完成,下面會分析一個嚼爛了的點——Android事件的分發。
順便貼一張debug下方法調用圖,整個過程如下面所示:
關于為什麼貼這張圖?因為今天再寫下面的一篇文章的時候,搜到了一些關于這方面的知識。結果,有人連整個分發過程還不清楚。。不想多說。