天天看點

android分發機制---- 源碼解析

android中分發機制在自定義的View中尤為重要,貫穿android Activity – ViewGroup – View的Event傳遞;
為了提高了解,我們分部闡述

1.android分發機制概述

2.圖解

3.android源碼

1.android分發機制

  • 在android中Event是自上而下分發的:Activity —-> ViewGroup —-> View
  • 涉及到3個方法:
    • dispatchTouchEvent—-事件的總排程,貫穿事件分發的主邏輯線
    • onInterceptTouchEvent—- 攔截方法,攔截事件決定後序的分發是否依然向下分發
    • onTouchEvent—- 事件Event真正的消費方法
  • 在android中,首先是從Activity進行分發,Activity通過dispatchTouchEvent分發到DecorView的dispatchTouchEvent中,這裡的DecorView(Activity的根View)就是一個ViewGroup;在ViewGroup中進行事件分發,會有事件攔截onInterceptTouchEvent,onInterceptTouchEvent傳回true,則事件由本層消費,傳回false,繼續向下分發;
  • 注意:
    • Activityh和View隻有dispatchTouchEvent和onTouchEvent;
    • ViewGroup有dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent

以上都是基本常識,下面來看android分發的圖例;(圖解的出現更加有利于對android分發機制的了解)

2.android分發機制圖解

android分發機制---- 源碼解析

從圖中可以看到android的分發就是一個一層一層嵌套的過程

Activity中dispatchTouchEvent分發到ViewGroup,ViewGroup中dispatchTouchEvent分發到View,View樹一層一層的周遊分發,自上而下,然後父層dispatchTouchEvent通過接收下一層dispatchTouchEvent的傳回來決定是否終結分發流程;

有一點拗口,其實可以這樣了解:

android分發是自上而下分發,當然是在不攔截的情況下;如果攔截則交于本層消費;在不攔截的情況下,傳遞到最底層的View來消費,若底層不消費,則傳回到父類的dispatchTouchEvent,由父類消費,同理,若父類沒有消費,則繼續向上傳遞,直到Activity,就是整個分發的頂層,預設處理;

3.源碼解析

3.1首先來看Activity源碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
              //實作屏保功能
              //a. 該方法為空方法
              //b. 當此activity在棧頂時,觸屏點選按home,back,menu鍵等都會觸發此方法
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
           
  • 首先在MotionEvent.ACTION_DOWN的時候會有onUserInteraction();他是一個空方法;主要是實作屏保功能,當activity在棧頂時候,點選home/back/menu均會觸發該方法;
  • 然後來看核心代碼:getWindow().superDispatchTouchEvent(ev) ;提前劇透一下getWindow().superDispatchTouchEvent(ev)就是在調用ViewGroup的分發方法:dispatchTouchEvent(ev),如果ViewGroup中event被消費或者是被其子View消費掉(dispatchTouchEvent—>onTouchEvent),則Activity中dispatchTouchEvent傳回true,事件分發結束;否則Activity調用onTouchEvent(ev),由Activity完成事件消費;
### getWindow()---- return  PhoneWindow
### PhoneWindow
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
### DecorView
     public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
     }
           

因為DecorView繼承自ViewGroup,so這裡的super.dispatchTouchEvent(event)就是ViewGroup的dispatchTouchEvent(event):

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
          //解析一:
          if (actionMasked == MotionEvent.ACTION_DOWN) {
                    //當開始一個新的觸摸手勢時,扔掉所有先前的狀态
                    //架構可能已經删除了前一個階段的UP或取消事件
                    //由于應用程式切換,ANR,或其他一些狀态改變。
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
           }
          //解析二:
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != ;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
    }
           
  • 解析一:
    • 在事件分發過程中首先通過 cancelAndClearTouchTargets(ev) 和resetTouchState()來cancel之前的狀态和事件,
  • 解析二:
    • 在ViewGroup中處理事件攔截:
    • if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)不成立

      事件攔截的标志位intercepted = true;事件直接攔截

      -

      if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)成立

      繼續看disallowIntercept,如果為false,則會傳回

      intercepted = onInterceptTouchEvent(ev)

      為true,直接intercepted = true 攔截;

這裡需要注意一下 intercepted = onInterceptTouchEvent(ev)就是ViewGroup的攔截事件方法;

其次,父 View 會為子View 建立一個 TouchTarget 執行個體加傳入連結表中,連結清單的第一項為 mFirstTouchTarget。後續的 move 和 up 事件會直接交給該 View的dispatchTouchEvent。

在ViewGroup中攔截方法,之後姚根據攔截狀态,進行繼續的向下分發:

仍然是

public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                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;
                }
            }
    }

    //核心代碼
     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) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
    ......
           

ViewGroup中代碼比較多,是以僅粘貼關鍵代碼,在攔截之後會周遊其子View,根據焦點和坐标定位點選的child,然後根據攔截狀态intercepted來進行處理;

這個handled就是在ViewGroup中傳回下一層的分發結果,如果handled傳回true則分發結束;如果傳回false,則事件有ViewGroup來處理;

在這裡dispatchTransformTouchEvent 完成了分發的最後過程:

1. 傳入的 child 不為空,轉化坐标為 child 的坐标系,調用 child.dispatchTouchEvent 向 child 分發事件

2. 傳入的 child 為空,調用 super.dispatchTouchEvent 分發事件到 onTouchEvent 中

由于代碼确實太多我們來簡化模拟一段僞代碼:

public boolean dispatchTouchEvent(MotionEvent e) {
    boolean handled = false;
    if (onInterceptTouchEvent(e)) {
        //如果攔截  則在ViewGroup中消費時間
        handled = onTouchEvent(e);
    } else {
        for (View view: childs) {
            //Event在子View的處理結果
            handled = view.dispatchTouchEvent(e);
            if (handled) {
                break;
            }
        }
        //子View沒有消費event 傳回false ViewGroup自行處理  
        //當然  如果ViewGroup仍然傳回false則繼續向上傳遞
        if (!handled) {
            handled = onTouchEvent(e);
        }
    }   
    return consumed;
}
           

我們繼續看View的源碼:

public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ......
           

這裡需要注意一下在View的dispatchTouchEvent中, View 為 ENABLE 的狀态并且有 mTouchListener,會先調用 onTouch。在 onTouch 傳回 false 時才會繼續調用 onTouchEvent,然後在onTouchEvent中調用performClick,傳回true則事件消費,傳回false則向上傳遞;

到這裡,事件分發整個流程源碼就結束了;由于代碼太多,是以僅粘貼關鍵代碼;

源碼解析完之後,回頭看圖解就會豁然開朗;

夏日炎炎,會有遺漏,期待回報;