圖解Android事件分發機制(深刻底層源碼)

該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,若是能給各位看官帶來一絲啓發或者幫助,那真是極好的。java


前言

在上一篇文章中咱們上了一個小例子來自定義View,文章比較簡單,閱讀量幾乎沒有,有灌水的嫌疑,(實際上沒有,每一篇文章我都是用心在寫)。這一篇文章呢,咱們來看一下Android事件的分發機制。關於這方面的知識大概已經被講爛了。我本人也看了好多關於這方面優質的文章和博客。能夠說是受益不淺,可是但是總以爲沒有掌握徹底。因此我去看了關於底層源碼的一些知識。而後在這裏分享給你們。網絡

當咱們的手指從觸摸到屏幕上的各類View開始到這個點擊事件結束到底經歷了什麼,咱們來詳細分析一下。(Android的輸入系統處理了不少事件,包括按鍵,觸摸,以及外接設備,可是咱們這篇文章只分析咱們最熟悉也是最經常使用的觸摸事件,這裏的描述也許不太精確,可是卻最爲直觀)
咱們先上一個整體流程圖app

注:上圖中綠色線條表示默認的事件處理流程,即咱們沒有作任何處理,事件會按照綠色線條所示的方向由Activity->...ViewGroup..->View->...ViewGroup..->Activity這個U型圖進行傳遞。即一直默認調用super.XXX方法。ide

上圖中黑色線條表示默認Activity->...ViewGroup..->View->...ViewGroup..->Activity這個U型圖的任一節點中(不包括onInterceptTouchEvent)返回了true,事件即結束,再也不向下一節點傳遞。函數

上圖中紅色線條表示一些特殊狀況,尤爲是ViewGroup,ViewGroup.onInterceptTouchEvent表示詢問當前ViewGroup是否須要攔截此事件即要不要處理,爲何要「畫蛇添足」呢,由於ViewGroup.dispatchTouchEvent這個函數的特殊,從上圖可知,該函數返回true,是消費事件,返回false是交由上一級的ViewGroup或者Activity的onTouchEvent。那麼它怎麼向下傳遞事件或者想把事件交給本身的onTouchEvent處理呢,因此ViewGroup多了個onInterceptTouchEvent(View是沒有該函數的),onInterceptTouchEvent起到做用的是分流。onInterceptTouchEvent返回false或者返回super.xxx是向下級View或者ViewGroup傳遞,返回true呢是把事件交給本身的onTouchEvent處理oop

咱們知道了上圖,,可是Activty的事件又是從哪獲得的呢,事件最終返回到Activity的onTouchEvent中又作了什麼呢。。下面咱們來。。。。。源碼分析

  1. 首先從手指觸摸到屏幕開始

咱們知道Android是基於Linux系統的。當輸入設備可用時(這裏的輸入設備包括不少設備,好比觸摸屏和鍵盤是Android最廣泛也是最標準的輸入設備,另外它還包括外接的遊戲手柄、鼠標等),Linux內核會爲輸入設置建立對應的設備節點。當輸入設備不可用時,就把對應的設備節點刪除,這也是若是咱們的屏幕意外摔碎了或者其餘緣由致使觸摸屏幕不可用時觸摸沒有反應的根本緣由。當咱們的輸入設備可用時(咱們這裏只來說解觸摸屏),咱們對觸摸屏進行操做時,Linux就會收到相應的硬件中斷,而後將中斷加工成原始的輸入事件並寫入相應的設備節點中。而咱們的Android 輸入系統所作的事情歸納起來講就是**監控這些設備節點,當某個設備節點有數據可讀時,將數據讀出並進行一系列的翻譯加工,而後在全部的窗口中找到合適的事件接收者,並派發給它。佈局

  1. 手指進行一系列操做(這裏指的是手指的移動,這一步可能沒有)
  2. 手指擡起或者因其餘其餘緣由(忽然間來了個電話之類的)致使事件結束

注:上述第2第3步與第1步裏的處理基本相同,可是須要注意的是Android是串行處理事件的,也就是說按下的動做(ACTION_DOWN|ACTION_POINTER_DOWN)處理完成以前是不會處理後續的ACTION_MOVE|ACTION_POINTER_MOVE和ACTION_UP|ACTION_POINTER_UP事件的。而且後續的ACTION_MOVE|ACTION_POINTER_MOVE和ACTION_UP|ACTION_POINTER_UP事件會根據對ACTION_DOWN|ACTION_POINTER_DOWN事件的不一樣而稍有不一樣。下面咱們先來分析按下的事件ACTION_DOWN|ACTION_POINTER_DOWN的分發。this

下面咱們來詳細分析,請注意,前方高能,請自備紙巾(草稿紙)spa

INotify和Epoll機制

上面咱們說到了Android 輸入系統所作的事情歸納起來講就是監控設備節點,當某個設備節點有數據可讀時,將數據讀出並進行一系列的翻譯加工,而後在全部的窗口中找到合適的事件接收者,並派發給它。那麼它是如何作的呢,,咱們來具體分析一下。Android 的輸入系統InputManagerService(如下簡稱爲IMS)做爲系統服務,它像其餘系統服務同樣在SystemServer進程中建立。

Linux會爲全部可用的輸入設備在/dev/input目錄在創建event0~n或者其餘名稱的設備節點,Android輸入系統會監控這些設備節點,具體是經過INotify和Epoll機制來進行監控。而不是經過一個線程進行輪詢查詢。
咱們先來看一下INotify和Epoll機制(這裏咱們只進行簡單的描述,讀者若是有興趣能夠留言,我單開一篇文章)

INotify機制

INotify是Linux內核提供的一種文件系統變化通知機制。它能夠爲應用程序監控文件系統的變化,如文件的新建,刪除等。

//建立INotify對象,並用描述符inotifyFd 描述它
int inotifyFd = inotify_init();
/*
    添加監聽
    inotify_add_watch函數參數說明
        inotifyFd:上面創建的INotify對象的描述符,當監聽的目錄或文件發生變化時記錄在INotify對象
        「/dev/input」:被監聽的文件或者目錄
        IN_CREATE | IN_DELETE:事件類型
綜合起來下面的代碼表示的意思就是當「/dev/input」下發生IN_CREATE | IN_DELETE(建立或者刪除)時即把這個事件寫入到INotify對象中
*/
int wd = inotify_add_watch(inotifyFd, "/dev/input", IN_CREATE|IN_DELETE )

Epoll機制

在上述INotify機制中咱們知道了咱們只需關心inotifyFd這個描述符就好了,但是事件是隨機發生的,咱們也不會本末倒置的採用輪詢的方式輪詢這個描述符,由於若是這樣作的話會浪費大量系統資源。這時候咱們Linux的另外一個機制就派上用場了,即Epoll機制Epoll機制簡單的說就是使用一次等待來獲取多個描述的可讀或者可寫狀態。這樣咱們沒必要對每個描述符建立獨立的線程進行阻塞讀取,在避免了資源浪費的同時得到較快的相應速度。
至此原始輸入事件已經讀取完畢,Android輸入系統對原始輸入事件進行翻譯加工以及派發的詳細過程很複雜。咱們這裏只分析其中一部分——IMS與窗口。

Avtivity,Window,PhoneWindow,以及ViewRootImpl之間的聯繫

上文中咱們也說到了IMS會在全部的窗口中找到合適的事件接收者。IMS是運行在SystemServer進程中,而咱們的窗口呢,是在咱們的應用進程中。這就引出了咱們在上文中留下的懸念

// ② 初始化mInputChanel。InputChannel是窗口接收來自InputDispatcher的輸入事件的管道。這部份內容咱們將在下一篇介紹。
  if ((mWindowAttributes.inputFeatures
          & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
      mInputChannel = new InputChannel();
  }
  ...


  ...
// ③ 若是mInputChannel不爲空,則建立mInputEventReceiver用於接收輸入事件。
  if (mInputChannel != null) {

      mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
              Looper.myLooper());
  }

讀者看到這裏該疑惑了,這個不是在ViewRootImpl.setView方法中說的嗎,跟如今講的有關係嗎?且聽我娓娓道來。在上幾篇博客中咱們介紹了Avtivity,Window,PhoneWindow,以及ViewRootImpl這些概念之間 到底有什麼關係呢。

咱們從前幾篇中就知道了Activity的啓動流程,Activity對象最早建立,可是Activity的顯示是依靠其內部對象Window mWindow,而Window是個抽象類,因此mWindow指向的其實是Window的實現類PhoneWindow的對象。PhoneWindow做爲顯示的載體,ViewRootImpl的measure、layout以及draw纔是View顯示的動力所在。咱們運行項目,看到了一個MainActivity,咱們點擊MainActivity的某個View(如Button了或者其餘),實際上咱們是點擊了屏幕上的某個點。由IMS對這個原始事件進行翻譯加工並找到咱們的PhoneWindow,並向PhoneWindow派發事件。整個過程可用以下流程圖表示。

注:上面的流程圖中省略了不少細節,意在讓讀者對Android輸入系統有個更總體的把控。
經過上面的流程圖咱們知道,當咱們的PhoneWindow建立完成以後,咱們也在該Window上註冊了InputChannel並與IMS通訊,IMS把事件寫入InputChannel,WindowInputEventReceiver對事件進行處理並最終仍是經過InputChannel反饋給IMS。
下面咱們來稍微介紹下InputChannel和WindowInputEventReceiver。

InputChannel

InputChannel的本質是一對SocketPair(非網絡套接字)。套接字能夠用於網絡通訊,也能夠用於本機內的進程通訊。進程間通訊的一種方式,具體解釋讀者可自行參看《深刻理解Android 卷Ⅲ》》中的5.4.1節。

WindowInputEventReceiver

獲得InputChannel後,便用它建立WindowInputEventReceiver,WindowInputEventReceiver繼承於InputEventReceiver,InputEventReceiver對象能夠接收來自InputChannel的輸入事件,並觸發其onInputEvent方法的回調。咱們這裏的是WindowInputEventReceiver,因此咱們來看一下這個類

final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }
        //重寫了onInputEvent方法,因此當InputChannel有事件時,會觸發
        //WindowInputEventReceiver.onInputEvent(),而其內部直接調用enqueueInputEvent
        @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();
        }
    }

那咱們來看一下enqueueInputEvent

void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    ...
    //① 將InputEvent對應的InputEventReceiver封裝爲一個QueuedInputEvent 
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
    //② 將新建的QueuedInputEvent 追加到mPendingInputEventTail所表示的一個單向鏈表中
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;
   

    if (processImmediately) {
        //③ 若是第三個參數爲true,則直接在當前線程中開始對輸入事件的處理工做
        doProcessInputEvents();
    } else {
        //④ 不然將處理事件的請求發送給主線程的Handler,隨後進行處理
        scheduleProcessInputEvents();
    }
}

咱們來看doProcessInputEvents

void doProcessInputEvents() {
    //遍歷整個輸入事件隊列,並逐一處理
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        if (mPendingInputEventHead == null) {
            mPendingInputEventTail = null;
        }
        q.mNext = null;

        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()方法會將完成單個事件的整個處理流程
        deliverInputEvent(q);
    }

  ...
}

而deliverInputEvent方法進行一系列調用最終會調用咱們的processPointerEvent()方法

private int processPointerEvent(QueuedInputEvent q) {
        
        final MotionEvent event = (MotionEvent)q.mEvent;

        mAttachInfo.mUnbufferedDispatchRequested = false;
        mAttachInfo.mHandlingPointerEvent = true;
        
        // 此時ViewRootImpl會將事件的處理權移交給View樹的根節點,調用dispatchPointerEvent函數  
        boolean handled = mView.dispatchPointerEvent(event);

        maybeUpdatePointerIcon(event);
        maybeUpdateTooltip(event);
        mAttachInfo.mHandlingPointerEvent = false;
        if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
            mUnbufferedInputDispatch = true;
            if (mConsumeBatchedInputScheduled) {
                scheduleConsumeBatchedInputImmediately();
            }
        }
        return handled ? FINISH_HANDLED : FORWARD;
    }

在processPointerEvent咱們看到ViewRootImpl會將事件的處理權移交給View樹的根節點,調用dispatchPointerEvent函數,即mView,而這個mView就是咱們熟知的DecorView
在ActivityThread.handleResumeActivity方法中有以下代碼

//decor即DecorView,l是佈局參數WindowManager.LayoutParams
wm.addView(decor, l);

觸摸事件分發詳解

DecorView

咱們下面即分析DecorView,咱們打開DecorView源碼並無發現dispatchPointerEvent,彆着急,別上火,,那麼這個dispatchPointerEvent確定在DecorView父類裏面了,,咱們打開View源碼,,果真找到了,該函數以下

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        //事件若是是Touch事件,毫無疑問咱們的是啊
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

這個時候咱們要去看View.dispatchTouchEvent嗎??NO!!!!!咱們應該看DecorView.dispatchTouchEvent(DecorView重寫了dispatchTouchEvent)
DecorView.dispatchTouchEvent聲明以下

DecorView.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    /**獲取Window.Callback,Window.Callback是個接口,這裏的mWindow是PhoneWindow,調用*
    *PhoneWindow.getCallback(),可是PhoneWindow並無實現該方法,因此咱們找到了
    *Window.getCallBack()方法。Window.getCallBack()方法返回Callback類型的mCallback
    */
    final Window.Callback cb = mWindow.getCallback();
    /**
    *若是cb不爲空而且window沒有被銷燬 mFeatureId < 0 表示是application的DecorView,
    *好比Activity、Dialog把事件傳給cb,不然把事件傳遞給父類的dispatchTouchEvent
    */
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

Window.java

public final Callback getCallback() {
    return mCallback;
}

Callback.java

public interface Callback {
   ...//省略一部分函數
   
    public boolean dispatchTouchEvent(MotionEvent event);
    
   ...
    
}

咱們來看這個Window.Callback ,既然有getCallback(),那麼應該有setCallback爲mCallback賦值。咱們

咱們在Activity的attach方法中看到以下代碼

Activity.java

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback) {
    ......
    //建立PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    
    ......
    //設置當前Activity爲Window.Callback,那麼毫無疑問,Activity類或者其父類實現了Window.Callback接口
    mWindow.setCallback(this);
    ......
}

咱們來看Activity類的聲明


果真如此。那麼咱們就來看看Activity.dispatchTouchEvent

Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //在這裏咱們又把事件給了PhoneWindow.superDispatchTouchEvent方法根據其返回值,
    //若返回值爲true,那麼dispatchTouchEvent返回true,咱們Activity的onTouchEvent方法沒法獲得執行
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //這裏就是咱們的Activity的onTouchEvent方法
    return onTouchEvent(ev);
}

那咱們要看PhoneWindow.superDispatchTouchEvent

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //兜兜轉轉一大圈,仍是把事件交給咱們的DecorView,
    //DecorView繼承自FrameLayout,FrameLayout呢又繼承自ViewGroup,
    //因此做爲一個ViewGroup,DecorView繼續向其子View派發事件,其流程我在文章的開頭就已經給了
    return mDecor.superDispatchTouchEvent(event);
}

總結:兜兜轉轉一大圈咱們神經都被繞彎了,咱們在這裏總結一下,當咱們觸摸(點擊)屏幕時,Android輸入系統IMS經過對事件的加工處理再合適的Window接收者並經過InputChannel向Window派發加工後的事件,並觸發InputReceiver的onInputEvent的調用,由此產生後面一系列的調用,把事件派發給整個控件樹的根DecorView。而DecorView又上演了一出偷樑換柱的把戲,先把事件交給Activity處理,在Activity中又把事件交還給了咱們的DecorView。自此沿着控件樹自上向下依次派發事件。

咱們總算把ACTION_DOWN的事件分發分析完畢了,ACTION_DOWN事件能夠說是全部觸摸事件的起點。咱們觸摸了屏幕,並引起ACTION_DOWN的事件,而後可能通過一系列的ACTION_MOVE事件,最後是ACTION_UP事件,至ACTION_UP,這整個事件序列算是完成了。咱們前面分析了ACTION_DOWN事件,那麼ACTION_MOV和ACTION_UP呢,ACTION_MOV和ACTION_UP的事件分發與ACTION_DOWN並不徹底相同。爲何這麼說呢,是由於他們很類似,可是稍微有些不一樣。你在執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再獲得執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個事件(如ACTION_DOWN)返回true,纔會收到ACTION_MOVE和ACTION_UP的事件。那麼這句話是什麼意思呢?咱們來看一下不一樣狀況下事件派發圖。

咱們在ViewGroup1中的dispatchTouchEvent中消費事件!

咱們在ViewGroupX中的dispatchTouchEvent中消費事件!

咱們在View中的dispatchTouchEvent中消費事件!

咱們在View中的onTouchEvent中消費事件!


特殊狀況1 :咱們在ViewGroupX中的onTouchEvent中消費事件!

特殊狀況2 :咱們在ViewGroupX中的dispatchTouchEvent中返回false並在ViewGroup1中的onTouchEvent中消費事件!

還有種種狀況我就不畫圖了。。爲何會產生上面的結果呢?咱們仍是來看一下ViewGroup的dispatchTouchEvent源碼把。

ViewGroup dispatchTouchEvent源碼分析

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  ......
   boolean handled = false;
   if (onFilterTouchEventForSecurity(ev)) {
    //表示窗口是否爲模糊窗口(FILTER_TOUCHES_WHEN_OBSCURED),
    //若是是窗口,則表示不但願處理改事件。(如dialog後的窗口)
        if (onFilterTouchEventForSecurity(ev)) {
       final int action = ev.getAction();
       final int actionMasked = action & MotionEvent.ACTION_MASK;
       /** 第①步 從新設置狀態  開始*/
       // 處理初始的按下動做
       if (actionMasked == MotionEvent.ACTION_DOWN) {
           //從新設置狀態等,比較重要的是設置mFirstTouchTarget == null,
           cancelAndClearTouchTargets(ev);
           resetTouchState();
       }
       /** 第①步 從新設置狀態  結束*/
       
        /** 第②步 檢查是否攔截  開始*/
       // 檢查是否攔截
       final boolean intercepted;
       if (actionMasked == MotionEvent.ACTION_DOWN
               || mFirstTouchTarget != null) {//若是是ACTION_DOWN事件或者mFirstTouchTarget != null
            //這裏咱們去問ViewGroup是否容許攔截,若是容許攔截,咱們再去問onInterceptTouchEvent
          ......
       } else {
           //若是不是MotionEvent.ACTION_DOWN事件而且mFirstTouchTarget 爲空,直接攔截
           intercepted = true;
       }
         /** 第②步 檢查是否攔截  結束*/
      ......
      /** 第③步 向子View派發  開始*/
       if (!canceled && !intercepted) {//若是沒有取消而且當前ViewGroup沒有攔截事件

           ......
           if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    //判斷事件類型,若是是ACTION_DOWN或者ACTION_POINTER_DOWN或者ACTION_HOVER_MOVE則進入
                        
                 if (newTouchTarget == null && childrenCount != 0) {
                     ......
                    
                          ......
                                //獲取子View並循環向子View派發事件
                          if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                          //若是當前ViewGroup的子View消費了事件,則進入if體
                          ......
                              //賦值newTouchTarget和mFirstTouchTarget 
                              newTouchTarget = addTouchTarget(child, idBitsToAssign);
                              alreadyDispatchedToNewTouchTarget = true;
                              break;
                          }
        
                }             
            
            ......
              
       }
    }
    /** 第③步 向子View派發  結束*/
    
    /** 第④步 額外的處理  開始*/
   // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
    /**這個判斷十分重要:
    *
    *咱們在上面的過程當中就知道假若咱們沒有攔截即intercepted = false;若是事件是ACTION_DOWN
    *或者ACTION_POINTER_DOWN或者ACTION_HOVER_MOVE咱們會進入循環子View並派發事件的過程,
    *若是子View也不想處理該事件即dispatchTransformedTouchEvent()函數返回了false,那麼此時ViewGroup的mFirstTouchTarget == null
    
    *假若咱們重寫了onInterceptTouchEvent並返回true,那麼intercepted = true即進行攔截,
    *那麼就不會進入咱們的第③步,直接來到第④步,這時當前ViewGroup的mFirstTouchTarget == null
    
    mFirstTouchTarget == null的條件下會調用dispatchTransformedTouchEvent
    */
       
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
       
        TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
    }
    /** 第④步 額外的處理  結束*/
       
     ......
   return handled;
}

咱們從上面的代碼能夠更清晰的瞭解到ACTION_DOWN的派發過程,如今還存疑的就是這個mFirstTouchTarget了,咱們在觸發ACTION_DOWN的時候,ViewGroup會根據事件掩碼actionMask判斷ACTION_DOWN,並重置一些狀態,重置狀態的過程當中就包括把mFirstTouchTarget設爲null,咱們第一次進入第三步時找到合適的子View並向其派發事件,若是子View消費了ACTION_DOWN事件,則調用addTouchTarget進行賦值,咱們來看一下這個函數

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    //在這裏咱們能夠看到mFirstTouchTarget 指向了子View
    mFirstTouchTarget = target;
    return target;
}

有上面的代碼可知mFirstTouchTarget是ViewGroup的一個成員變量,每個ViewGroup都持有這個mFirstTouchTarget。
這個mFirstTouchTarget是個單向鏈表,表示的是當前ViewGroup的子View有沒有消費ACTION_DOWN事件,若是消費了ACTION_DOWN事件,就如上面代碼中第③步的時候描述的同樣給mFirstTouchTarget賦值,若是當前ViewGroup的子View沒有消費ACTION_DOWN事件,即把事件分發給子View的這個dispatchTransformedTouchEvent()函數返回了false,不進入if體,mFirstTouchTarget仍是爲null。

咱們接着來看第④步,結合上圖中的特殊狀況1,咱們在ViewGroupX中的onTouchEvent中消費了事件。那麼對於ViewGroupX來講,它的mFirstTouchTarget==null,由於它的子View並無消費事件,對於ViewGroup1來講它的mFirstTouchTarget != null,由於它的子View ViewGroupX消費了事件,以此類推最後獲得的mFirstTouchTarget 鏈表相似於下圖

因爲ACTION_MOVE|ACTION_UP事件不符合第③步時進入獲取子View並循環派發的條件,當是ACTION_MOVE|ACTION_UP事件會直接來到第④步,判斷當前ViewGroup的mFirstTouchTarget 是否爲空,由上圖可知不爲空,那麼進入第④步else體,在第④步else體內依據下圖的鏈表逐一貫子View派發事件。因此ACTION_MOVE|ACTION_UP事件只派發到ViewGroupX並交由ViewGroupX的onTouchEvent處理,再也不向下派發。

那咱們再來看一下mFirstTouchTarget == null的條件下調用的dispatchTransformedTouchEvent函數
參數分別是ev, canceled, null, TouchTarget.ALL_POINTER_IDS

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

  
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            //child爲空調用父類即View的dispatchTouchEvent
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

   
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;


    if (newPointerIdBits == 0) {
        return false;
    }


    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                 //child爲空調用父類即View的dispatchTouchEvent
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

   
    if (child == null) {
         //child爲空調用父類即View的dispatchTouchEvent
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

上面的函數咱們就不仔細分析了,不過註釋裏寫的很明白,只要流程正常的話,咱們都會調用父類的dispatchTouchEvent

咱們來看一下View的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {

......

if (onFilterTouchEventForSecurity(event)) {
    ......

    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {//這裏判斷有沒有爲View是否可用Enabled而且檢查是否設置了TouchListener,若是設置了,則觸發TouchListener的onTouch
        result = true;
    }

    if (!result && onTouchEvent(event)) {//若是當前View沒有設置listener信息,事件也沒有被滾動條消費這裏回調了咱們的onTouchEvent。因此若是爲當前View設置了TouchListenerb並在TouchListener的onTouch函數中返回了true,那麼,該View的onTouchEvent將沒法獲得回調。
        result = true;
    }
}

......
return result;

}

本篇總結

本篇文章詳細分析了View的事件體系(寫這一篇文章真是不容易啊)。做爲全部觸摸事件的起點ACTION_DOWN|ACTION_POINTER_DOWN來講,Android對其的處理很精細,尤爲是ViewGroup對其的處理。

  1. 首先重置狀態,這是由於一個新的事件序列開始了,重置狀態中比較重要的就是這個mFirstTouchTarget了,mFirstTouchTarget做爲ViewGroup的成員變量記錄當前ViewGroup下的子View是否消費了該ACTION_DOWN|ACTION_POINTER_DOWN事件。這個子View的意思也不只僅是直接子View。假若有這樣一個結構

    <ViewGroup1>

    <ViewGroup2>
        <ViewGroup3>
            <ViewGroup4>
                <View>
                </View>
            </ViewGroup4>
        </ViewGroup3>
    </ViewGroup2>

    </ViewGroup1>

假設是View消費了ACTION_DOWN|ACTION_POINTER_DOWN事件,那麼ViewGroup1的mFirstTouchTarget就是ViewGroup2->ViewGroup3->ViewGroup4->View

  1. 若是ViewGroup子View消費了事件,那麼記錄mFirstTouchTarget,ACTION_DOWN|ACTION_POINTER_DOWN事件結束,若是沒有子View消費此事件,mFirstTouchTarget爲null。後續的ACTION_MOVE|ACTION_UP事件會根據上一步中的mFirstTouchTarget進行分發。若爲null,調用父類的即View的dispatchTouchEvent,該函數內部會先判斷Listener信息,並調用listener的onTouch方法,根據onTouch的返回值決定是否繼續調用當前ViewGroup的onTouchEvent方法;若不爲null,則根據mFirstTouchTarget鏈表進行分發後續的ACTION_MOVE|ACTION_UP事件。

但願讀者能多看幾遍上面的分析。相信你必定會有收穫的


下篇預告

在下一篇文章中咱們將進行實戰項目,也是對咱們前幾篇文章的實際應用。老話說的好,紙上得來終覺淺,絕知此事要躬行。下一篇甚至幾篇咱們就來自定義ViewGroup並重點探討滑動衝突如何解決。滑動衝突解決的基礎是今天這篇的View事件體系


參考博文


此致,敬禮

相關文章
相關標籤/搜索