Touch 事件是如何傳遞到 Activity ?
一、概述
熟悉事件分發的人都知道,事件是從 Activity.dispatchTouchEvent()
開始向下傳遞的,那麼 Activity 中的 Touch 事件又是從哪裏接受到的呢?在這篇文章中,我們來簡單分析一下 Activity 接收 Touch 事件的流程。
版本: Android SDK 29
關聯文章:
1.《Window系列 (一) — WindowManager 詳解》
二、流程分析
對於一塊陌生的代碼,去猜測它的調用流程是困難的,這裏有個小技巧,可以利用 Thread.dumpStack()
來查看當前線程的方法調用棧 (即 MainActivity.dispatchTouchEvent()
方法的調用棧)。
代碼如下所示:
public class MainActivity extends AppCompatActivity {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 使用 Thread.dumpStack() 來查看 MainActivity.dispatchTouchEvent() 方法的調用棧。
Thread.dumpStack();
return super.dispatchTouchEvent(ev);
}
}
Logcat 導出的堆棧如下:
W/System.err: java.lang.Throwable: stack dump
W/System.err: at java.lang.Thread.dumpStack(Thread.java:490)
// 到這裏,事件已經分發到了 Activity.dispatchTouchEvent() 方法。
W/System.err: at com.elson.example.MainActivity.dispatchTouchEvent(MainActivity.java:55)
W/System.err: at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
W/System.err: at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2483)
W/System.err: at android.view.View.dispatchPointerEvent(View.java:8820)
W/System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4750)
W/System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4608)
W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4098)
W/System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4151)
W/System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4117)
W/System.err: at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4254)
W/System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4125)
W/System.err: at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4311)
W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4098)
W/System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4151)
W/System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4117)
W/System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4125)
W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4098)
W/System.err: at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6559)
W/System.err: at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6533)
W/System.err: at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6486)
W/System.err: at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6739)
// 可以看出,這裏是事件接收的開始。
W/System.err: at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
W/System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
W/System.err: at android.os.MessageQueue.next(MessageQueue.java:148)
W/System.err: at android.os.Looper.loop(Looper.java:151)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5775)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at java.lang.reflect.Method.invoke(Method.java:372)
W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:975)
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:770)
通過以上的堆棧信息,我們可以看出 Touch 事件從 遠程服務端的接收 到 傳遞給 Activity 的過程大致如下:
InputEventReceiver.dispatchInputEvent()
–>ViewRootImpl.WindowInputEventReceiver.onInputEvent()
–>ViewRootImpl.deliverInputEvent()
–>ViewRootImpl.InputStage.deliver()
…這裏經過一些了 InputStage 子類的傳遞處理(責任鏈模式)
…
–>ViewRootImpl.ViewPostImeInputStage.processPointerEvent()
–>View.dispatchPointerEvent()
–>PhoneWindow$DecorView.dispatchTouchEvent()
–>WindowCallbackWrapper.dispatchTouchEvent()
–>Activity.dispatchTouchEvent()
三、源碼分析
下面從三個方面來分析:
- 應用進程如何接收遠程服務回傳的 Touch 事件?
- Touch 事件如何傳遞到 Activity 中?
- InputStage 的鏈式調用
1. 應用進程如何接收遠程服務回傳的 Touch 事件?
在 ViewRootImpl.setView()
中,完成了事件分發的監聽操作。
ViewRootImpl
// ViewRootImpl.class
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
// 1.創建一個事件輸入通道(InputChannel)。
mInputChannel = new InputChannel();
}
// ...省略代碼...
if (mInputChannel != null) {
// ...省略代碼...
// 2.WindowInputEventReceiver 用於接收遠程服務發送的事件,此處與mInputChannel進行關聯。
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
}
// ...省略代碼...
// 3.這段代碼在分析 “事件如何傳遞到Activity”時有用。
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
}
}
}
這個方法主要有三步:
- 步驟1:創建一個事件傳遞的通道 (
InputChannel
)。 - 步驟2:創建一個接收遠程服務發送事件的類 (
WindowInputEventReceiver
)。 - 步驟3:創建一系列的 InputStage,並進行串聯。
下面我們來看一下 WindowInputEventReceiver 裏面是如何操作的。
WindowInputEventReceiver (ViewRootImpl 內部類)
// ViewRootImpl.WindowInputEventReceiver.class
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
// 這裏會接收到遠程服務回傳的 Touch 事件。
}
// ...省略代碼...
}
我們看到 InputChannel 被傳遞到了 WindowInputEventReceiver 的父類中,所以我們接着往下看。
InputEventReceiver
// InputEventReceiver.class
public abstract class InputEventReceiver {
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
// ...省略代碼...
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
// 將 InputChannel 傳遞到 Native 層。
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
// 這個方法是被 Native 層調用的,我們可以猜測遠程服務要分發 Touch 事件時,會觸發這個方法來把事件回傳到應用進程。
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
// 這個方法在子類 WindowInputEventReceiver 中被重寫。
onInputEvent(event);
}
}
小結:
- 接收事件類的註冊是在添加Window的過程中(即:
ViewRootImpl.setView()
)。 - 接收遠程服務分發的Touch事件的類爲
WindowInputEventReceiver
。
2. Touch 事件如何傳遞到 Activity 中?
上面我們知道,應用進程中接收 Touch 事件最早是在 WindowInputEventReceiver.onInputEvent()
方法中,下面我們就來分析一下從接收到 Touch 事件到分發到 Activity.dispatchTouchEvent()
的流程。
時序圖:
源碼流程分析:
WindowInputEventReceiver (ViewRootImpl 內部類)
// ViewRootImpl.WindowInputEventReceiver.class
final class WindowInputEventReceiver extends InputEventReceiver {
// ...省略代碼...
@Override
public void onInputEvent(InputEvent event) {
// ...省略代碼...
// 將事件加入隊列
enqueueInputEvent(event, this, 0, true);
}
}
ViewRootImpl
// ViewRootImpl.class
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
// 1. 將參數保包裝成 QueuedInputEvent 事件類。
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// ...省略加入隊列的代碼...
if (processImmediately) {
// 2.處理事件類型。
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
// 從隊列中獲取事件進行處理
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
// ...省略代碼...
mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
// 分發 Touch 事件
deliverInputEvent(q);
}
// ...省略代碼...
}
private void deliverInputEvent(QueuedInputEvent q) {
// ...省略代碼...
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
// 在 ViewRootImpl.setView() 中,我們知道:
// mFirstInputStage指向NativePreImeInputStage類;
// mFirstPostImeInputStage 指向 EarlyPostImeInputStage類。
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
// ...省略代碼...
if (stage != null) {
handleWindowFocusChanged();
// 這裏獲取到 InputStage 子類進行鏈式調用來分發事件(每個子類有不同的作用)
// 根據上面的調用流程,我們知道最終會調用到 ViewPostImeInputStage.Process() 方法。
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
小結:
- 根據 Locat 中顯示的調用流程,我們知道
stage.deliver(q)
調用後,最終會調用到ViewPostImeInputStage.processPointerEvent()
方法。
ViewPostImeInputStage
// ViewPostImeInputStage.class
/**
* Delivers post-ime input events to the view hierarchy.
*/
final class ViewPostImeInputStage extends InputStage {
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
// ...省略代碼...
// mView 是 DecorView,最終調用了 DecorView.dispatchPointerEvent()方法。
boolean handled = mView.dispatchPointerEvent(event);
// ...省略代碼...
return handled ? FINISH_HANDLED : FORWARD;
}
}
View
// View.class
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
// 執行 Touch 事件
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
DecorView
// DecorView.class
public boolean dispatchTouchEvent(MotionEvent ev) {
// cb 指向 Activity,在Activity中實現了Window.Callback接口。
// 具體給 Window 設置 Callback 的地方在 Activity.attach() 方法中,有一行 PhoneWindow.setCallback(this) 代碼。
final Window.Callback cb = mWindow.getCallback();
// 所以這裏最終觸發了 Activity.dispatchTouchEvent() 方法。
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
到這裏,從 WindowInputEventReceiver 接收到 Touch 事件到分發到 Activity 的流程就分析完了。
3. InputStage 的鏈式調用流程
從上面你的調用流程我們可以看到,這中間涉及到 InputStage 及其子類的一系列鏈式調用,下面我們來分析一下這個鏈式調用的流程。
InputStage 是在ViewRootImpl.setView()
方法中創建的。
ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// ...省略代碼...
// 這裏使用了責任鏈,將 InputStage 進行串聯。
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
// Delivers post-ime input events to the view hierarchy.
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
// Delivers post-ime input events to a native activity.
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
// Performs early processing of post-ime input events.
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
// Delivers input events to the ime. Does not support pointer events.
InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix);
// Delivers pre-ime input events to the view hierarchy. Does not support pointer events.
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
// Delivers pre-ime input events to a native activity. Does not support pointer events.
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
}
}
}
小結:
- 從 InputStage 子類的註釋,我們知道
NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage
都不處理 Pointer 事件(手勢事件,Touch事件屬於手勢事件)。 - 從
NativePreImeInputStage、ViewPreImeInputStage
與NativePostImeInputStage、ViewPostImeInputStage
類名可以看出,他們的兩兩配對的。
EarlyPostImeInputStage
// EarlyPostImeInputStage.class
final class EarlyPostImeInputStage extends InputStage {
public EarlyPostImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else if (q.mEvent instanceof MotionEvent) {
// 處理 MotionEvent 事件的。
return processMotionEvent(q);
}
return FORWARD;
}
}
ViewPostImeInputStage
// ViewPostImeInputStage.class
final class ViewPostImeInputStage extends 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); // 核心代碼
}
// ...省略代碼...
}
}
private int processPointerEvent(QueuedInputEvent q) {
// ...省略代碼...
// 核心代碼
boolean handled = mView.dispatchPointerEvent(event);
// ...省略代碼...
return handled ? FINISH_HANDLED : FORWARD;
}
}
InputStage 鏈式調用的流程:
InputStage
// InputStage.class
abstract class InputStage {
private final InputStage mNext;
// 將下個InputStage 與當前InputStage 作關聯。
public InputStage(InputStage next) {
mNext = next;
}
// 開始調用 InputStage.deliver 進行事件分發。
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
// 執行分發邏輯
apply(q, onProcess(q)); //onProcess()默認返回FORWARD
}
}
protected int onProcess(QueuedInputEvent q) {
return FORWARD;
}
// 根據子類重寫 onProcess 方法返回不同的 result 來執行不同的邏輯。
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);
}
}
/**
* Forwards the event to the next stage.
*/
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
/**
* Called when an event is being delivered to the next stage.
*/
protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
if (mNext != null) {
// 然後執行下一個 InputStage 的分發流程。
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
}