天天看點

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

在前面的小節中,我們以按鍵的執行個體,詳細的講解InputStage,該小節多點觸摸屏的InputStage,看看Reader線程是如何處理他的,在這之前先來回顧一下之前的知識。

我們知道一個應用程式APP有多個activity,每個activity對應一個window,在window中存在Decorview,我們可以從其中劃出一塊區域,然後自由創作,比如在上面增加TextView,Button等等。下面是之前我們學習過的理論流程,如果不記得了,可以看看前面的小節:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

為了友善的閱讀源代碼,下面是一個思維導圖:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

從圖中的左下角我們可以找到:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

我們從觸摸屏的ViewPostImeInputStage處開始分析,即輸入法之後的處理。對于觸摸屏,在輸入法之前,他沒有做任何事情,是以我們不用關心。打開源碼檔案ViewRootImpl.java。

如,我們可以看到以下代碼,在輸入法之前:

final class ViewPreImeInputStage extends InputStage {
    protected int onProcess(QueuedInputEvent q) {
       if (q.mEvent instanceof KeyEvent) {
           return processKeyEvent(q);
       }
       return FORWARD;
    }
           

這裡我們可以知道,輸入法之前,如果是觸摸屏事件,直接傳回FORWARD,他隻處理按鍵類事件。是以我們隻關心輸入法之後的處理。假設我們有一個應用程式,其界面如下:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

如果我們點選界面的button1,他是怎麼找到的呢?

1.根據觸點的x,y坐标,遞歸的找到目标控件:先把事件發送給Layout1,看看是否位于其上,如果不是則發送給Layout2,直到其位于Layout3,與Layout3比對之後在發送給其中的button1,但是其不位于button1,就會發送的button2,這樣就找到了這個button。

2.btton如何處理這個事件呢?一般分為兩種,click(點選:松開後處理),touch(觸摸:按下,松開,滑動都可以處理)。

我們利用取巧的辦法,在這兩個函數中,添加棧的列印資訊。這樣我們就能知道其調用過程了。下面是官方文檔的一些介紹:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

從上面可以看到,onClick屬于View,我們打開View.java搜尋onClick,可以找到如下:

public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }
           

可以知道onClick被OnClickListener 調用,OnClickListener 又為一個接口,我們找到其定義:

可知每個View中都存在一個OnClickListener,我們需要去設定mOnClickListener:

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
           

同樣也存在setOnClickListener

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
           

上面是一個相關知識的簡述,下面我們開始編寫APP。

APP編寫

我們在原來的APP_0009_Inputstage工程工程上進行修改,指派重命名為APP_0009_Inputstage-v2,使用AS打開工程,原來MainActivity.java中存在:

class MyButtonListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {
            View decorView = getWindow().getDecorView();
              printViewHierarchy(decorView,0,-1);
        }
     }
           

在我們點選button然後松開的時候就會調用onClick函數,現在我們添加onTouch并且修改onClick:

/*在public class MainActivity extends Activity中添加如下*/
     class MyButtonListener implements View.OnClickListener {
         @Override
         public void onClick(View view) {
+        /*    View decorView = getWindow().getDecorView();
+              printViewHierarchy(decorView,0,-1);
+        */
+            Log.d(TAG,"***MyButtonListener onClick call ****");
+            Log.d(TAG,Log.getStackTraceString(new Throwable()));
         }
     }
     
+    class MyButtonTotchListener implements View.OnTouchListener{
+
+        @Override
+        public boolean onTouch(View view, MotionEvent motionEvent) {
+            Log.d(TAG,"***MyButtonListener onToutch call ****");
+            Log.d(TAG,Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
/*在protected void onCreate(Bundle savedInstanceState中添加*/
         button.setOnClickListener(new MyButtonListener());
+        button.setOnTouchListener(new MyButtonTotchListener());	
           

然後編譯運作APP,點選按鈕,我們就能看到onTouch,與onClick的堆棧資訊l了,如下:

D/LedDemo: ***MyButtonListener onToutch call ****
2019-03-23 10:54:02.305 1582-1582/com.example.administrator.app_0001_leddemp D/LedDemo: java.lang.Throwable
        at com.example.administrator.app_0001_leddemp.MainActivity$MyButtonTotchListener.onTouch(MainActivity.java:76)
        at android.view.View.dispatchTouchEvent(View.java:10019)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:545)
        at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1811)
        at android.app.Activity.dispatchTouchEvent(Activity.java:3091)
        at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:507)
        at android.view.View.dispatchPointerEvent(View.java:10243)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4438)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4306)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3999)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4056)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6247)
        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6221)
        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6182)
        at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6350)
        at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:323)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:6141)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)
2019-03-23 10:54:02.377 1582-1582/com.example.administrator.app_0001_leddemp 



D/LedDemo: ***MyButtonListener onClick call ****
2019-03-23 10:54:02.378 1582-1582/com.example.administrator.app_0001_leddemp D/LedDemo: java.lang.Throwable
        at com.example.administrator.app_0001_leddemp.MainActivity$MyButtonListener.onClick(MainActivity.java:67)
        at android.view.View.performClick(View.java:5637)
        at android.view.View$PerformClick.run(View.java:22445)
        at android.os.Handler.handleCallback(Handler.java:755)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6141)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)

           

我們可以看到:

ViewPostImeInputStage.onProcess(View.java:10243)
	ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4438)
           

下面我們打開ViewRootImpl.java檔案,找到ViewPostImeInputStage,中的:

protected int onProcess(QueuedInputEvent q) {
	processPointerEvent(q);/*對于多點觸摸屏會調用該函數*/
		boolean handled = eventTarget.dispatchPointerEvent(event)		
           

當調用到dispatchPointerEvent時,我們發現其在PhoneWindow.java檔案中沒有被實作,檢視那麼他就會調用父類的dispatchPointerEvent,那我們DecorView的父類FrameLayout,他也沒有實作,繼續找到ViewGroup.java,依舊沒有實作,知道找到View.java,其中實作了dispatchPointerEvent,這也就是說,dispatchPointerEvent最終調用的是View.java中的dispatchPointerEvent:

public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }
           

從堆棧列印:

at android.app.Activity.dispatchTouchEvent(Activity.java:3091)
        at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:545)
        at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:507)
        at android.view.View.dispatchPointerEvent(View.java:10243)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4438)
           

我們也能分析出相同的流程,最終傳遞給我們的Activity,我們進入Activity.java檔案檢視,搜尋dispatchTouchEvent:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
           

其處理流程,就與我們之前總結的:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

的流程是一樣的了,Activity傳遞觸摸屏事件給wind不能處理傳遞給DecorView,最後傳遞我們的焦點,即按鈕上。我上面我們可以看到dispatchTouchEvent函數調用了superDispatchTouchEvent,該實作為:

我們在PhoneWindow.java中找到:

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
           

可以看到直接調用的mDecor(DecorView)中的superDispatchTouchEvent函數,在DecorView.java中我們找到:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
           

其直接調用父類的dispatchTouchEvent方法,他的父類為沒有實作,最終到:

ViewGroup為組View的意思,代表一個View可能放有多個控件:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

在前面的框圖中,DecrView,Layout1,Layout12,Layout3都是ViewGroup。ViewGroup怎麼處理觸摸事件呢?他會判斷輸入事件位于哪個child上面,即ViewGroup内部的控件,然後調用child中的dispatchTouchEvent:

/*ViewGroup.java*/
    public boolean dispatchTouchEvent(MotionEvent ev) {
    	/*根據觸摸點,得到x,y坐标*/
		final float x = ev.getX(actionIndex);
		final float y = ev.getY(actionIndex);
		/*判斷其坐标是否在其内部的child上*/
		isTransformedTouchPointInView(x, y, child, null)
			/*如果找到了則獲得這個child*/
			newTouchTarget = getTouchTarget(child);
			/*發送轉換過的位置資訊給child*/
			dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
               	handled = child.dispatchTouchEvent(event);
           

從:

at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2264)
           

中可以看出,其是一個遞歸的過程。最終會找到我們的button,把輸入事件發送給button,調用button的dispatchTransformedTouchEvent函數,但是我們的按鍵沒有實作該函數,最終調用其父類

View.dispatchTouchEvent(View.java:10019)
           

限制我們看看View.java中的dispatchTouchEvent如何處理這個輸入事件:

public boolean dispatchTouchEvent(MotionEvent event) {
    	/*如果前面我們設定了mOnTouchListener ,這onTouch函數會被調用*/
		if (li != null && li.mOnTouchListener != null&& (mViewFlags &ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {
			/*事情處理完成,不會在外下進行傳遞了*/
			result = true;
		if (!result && onTouchEvent(event)) {
                result = true;

           

如果我們沒有設定mOnTouchListener ,則該函數繼續往下執行onTouchEvent:

if (!result && onTouchEvent(event)) {
	/*對于松開的事件,*/
	case MotionEvent.ACTION_UP:
		post(mPerformClick)
           

其中的post最終會調用到mPerformClick(PerformClick)中的run方法:

private final class PerformClick implements Runnable {
        @Override
        public void run() {
            performClick();
            	/*如果設定了mOnClickListener */
           	    if (li != null && li.mOnClickListener != null) {
		            playSoundEffect(SoundEffectConstants.CLICK);
		            /*調用onClick方法*/
		            li.mOnClickListener.onClick(this);            	
           

是以我們能在列印的堆棧資訊中看到:

at com.example.administrator.app_0001_leddemp.MainActivity$MyButtonListener.onClick(MainActivity.java:67)
        at android.view.View.performClick(View.java:5637)
        at android.view.View$PerformClick.run(View.java:22445)
           

其實中run中被調用的,最終執行我們button中的onClick,詳細的調用過程,大家可以根據堆棧資訊,繼續詳細的分析,下面是一個總結的框圖:

06.輸入系統:第10課第24節_輸入系統_多點觸摸驅動程式_InputStage

繼續閱讀