Window系列 (四) — 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()


三、源碼分析

下面從三個方面來分析:

  1. 應用進程如何接收遠程服務回傳的 Touch 事件?
  2. Touch 事件如何傳遞到 Activity 中?
  3. 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. 步驟1:創建一個事件傳遞的通道 (InputChannel)。
  2. 步驟2:創建一個接收遠程服務發送事件的類 (WindowInputEventReceiver)。
  3. 步驟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);
    }
}

小結:

  1. 接收事件類的註冊是在添加Window的過程中(即:ViewRootImpl.setView())。
  2. 接收遠程服務分發的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);
    }
}

小結:

  1. 根據 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;
        }
    }
}

小結:

  1. 從 InputStage 子類的註釋,我們知道 NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage 都不處理 Pointer 事件(手勢事件,Touch事件屬於手勢事件)。
  2. NativePreImeInputStage、ViewPreImeInputStageNativePostImeInputStage、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);
        }
    }
}

五、參考文章

  1. 《Activity是如何接收到touch事件的(窗口與用戶輸入系統)》
  2. 《ViewRootImpl源碼分析事件分發》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章