在前面的小節中,我們以按鍵的執行個體,詳細的講解InputStage,該小節多點觸摸屏的InputStage,看看Reader線程是如何處理他的,在這之前先來回顧一下之前的知識。
我們知道一個應用程式APP有多個activity,每個activity對應一個window,在window中存在Decorview,我們可以從其中劃出一塊區域,然後自由創作,比如在上面增加TextView,Button等等。下面是之前我們學習過的理論流程,如果不記得了,可以看看前面的小節:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSP9EVTyMmeNhXQ61EM4wmYwhGWhxGZzwEMW1mY1RzRapnTtxkb5ckYplTeMZTTINGMShUYfRHelRHLwEzX39GZhh2css2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xyayFWbyVGdhd3LcV2Zh1Wa9M3clN2byBXLzN3btg3Pn5GcucTN2MTMwkDMyIzMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
為了友善的閱讀源代碼,下面是一個思維導圖:
從圖中的左下角我們可以找到:
我們從觸摸屏的ViewPostImeInputStage處開始分析,即輸入法之後的處理。對于觸摸屏,在輸入法之前,他沒有做任何事情,是以我們不用關心。打開源碼檔案ViewRootImpl.java。
如,我們可以看到以下代碼,在輸入法之前:
final class ViewPreImeInputStage extends InputStage {
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
return FORWARD;
}
這裡我們可以知道,輸入法之前,如果是觸摸屏事件,直接傳回FORWARD,他隻處理按鍵類事件。是以我們隻關心輸入法之後的處理。假設我們有一個應用程式,其界面如下:
如果我們點選界面的button1,他是怎麼找到的呢?
1.根據觸點的x,y坐标,遞歸的找到目标控件:先把事件發送給Layout1,看看是否位于其上,如果不是則發送給Layout2,直到其位于Layout3,與Layout3比對之後在發送給其中的button1,但是其不位于button1,就會發送的button2,這樣就找到了這個button。
2.btton如何處理這個事件呢?一般分為兩種,click(點選:松開後處理),touch(觸摸:按下,松開,滑動都可以處理)。
我們利用取巧的辦法,在這兩個函數中,添加棧的列印資訊。這樣我們就能知道其調用過程了。下面是官方文檔的一些介紹:
從上面可以看到,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);
}
其處理流程,就與我們之前總結的:
的流程是一樣的了,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可能放有多個控件:
在前面的框圖中,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,詳細的調用過程,大家可以根據堆棧資訊,繼續詳細的分析,下面是一個總結的框圖: