1. 程式人生 > >Android事件分發機制原始碼分析之Activity篇

Android事件分發機制原始碼分析之Activity篇

在之前的事件分發分析中,曾提及到View的事件是由ViewGroup分發的,然而ViewGroup的事件我們只是稍微帶過是由Activity分發的。而我們知道,事件產生於使用者按下螢幕的一瞬間,事件生成後,經過一系列的過程來到我們的Activity層,那麼事件是怎樣從Activity傳遞到根ViewGroup的呢?

注:建議先閱讀 Android事件分發機制原始碼分析之View篇 Android事件分發機制原始碼分析之ViewGroup篇

例項程式碼

我們依舊用回之前用過的程式碼。對MainActivity稍作修改。

public class MainActivity
extends Activity {
private static final String TAG = "MainActivity"; private CustomButton mbtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mbtn = (CustomButton) findViewById(R.id.button); } @Override
public boolean dispatchTouchEvent(MotionEvent ev) { Log.i(TAG, "dispatchTouchEvent" + ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public void onUserInteraction() { Log.i(TAG, "onUserInteraction"); super.onUserInteraction(); } @Override
public boolean onTouchEvent(MotionEvent event) { Log.i(TAG, "onTouchEvent" + event.getAction()); return super.onTouchEvent(event); } }

我們看一下當點選CustomButton的列印情況:

11-01 15:47:52.738 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent0
11-01 15:47:52.738 30414-30414/com.example.pc.myapplication I/MainActivity: onUserInteraction
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomLayout: dispatchTouchEvent0
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomLayout: onInterceptTouchEvent0
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomButton: dispatchTouchEvent0
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomButton: onTouchEvent0
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomLayout: dispatchTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomLayout: onInterceptTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomButton: dispatchTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomButton: onTouchEvent1

我們再看一下點選非CustomButton範圍的列印情況:

11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/MainActivity: onUserInteraction
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/CustomLayout: dispatchTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/CustomLayout: onInterceptTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/CustomLayout: onTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent0
11-01 15:54:17.051 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent2
11-01 15:54:17.051 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent2
11-01 15:54:17.059 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent2
11-01 15:54:17.060 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent2
11-01 15:54:17.060 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent1
11-01 15:54:17.060 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent1

從結果現象中,我們假設它的ACTION_DOWN事件首先觸發dispatchTouchEvent,然後觸發onUserInteraction,再次onTouchEvent,接著的ACTION_UP事件觸發dispatchTouchEvent後觸發了onTouchEvent,也就是說ACTION_UP事件時不會觸發onUserInteraction。

下面我們去看看原始碼的情況。

原始碼分析。原始碼參考Android API 23 Platform

我們依舊先看一下Activity的dispatchTouchEvent()。

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

好像程式碼比想象中要少一點。首先在第一個條件判斷中,我們可以看到只有是ACTION_DOWN的動作才會去執行onUserInteraction(),驗證了上面列印臺在ACTION_UP動作中沒有執行onUserInteraction()。另外當我們點開onUserInteraction()時,發現它是一個空方法:

    /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     */
    public void onUserInteraction() {
    }

從註釋中我們可以瞭解到,當此activity在棧頂時,觸屏點選按home,back,menu鍵等都會觸發此方法。下拉statubar、旋轉螢幕、鎖屏不會觸發此方法。所以它會用在屏保應用上,因為當你觸屏機器 就會立馬觸發一個事件,而這個事件又不太明確是什麼,正好屏保滿足此需求。

繼續往下看。到了第二個條件判斷,先是getWindow()返回的是Window物件,物件mWindow在Activity類中是

mWindow = new PhoneWindow(this);

這個例項化的。而Window是一個抽象類,所以superDispatchTouchEvent()相當於是PhoneWindow的其中一個方法。我們看一下Window中的superDispatchTouchEvent():

    /**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

從註釋我們看到,不需要實現該方法。我們去看一下PhoneWindow裡看下Window抽象方法的實現吧,如下:

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

你會發現在PhoneWindow的superDispatchTouchEvent方法裡又直接返回了另一個mDecor物件的superDispatchTouchEvent方法,mDecor是啥?繼續分析。這參考工匠若水的分析。

在PhoneWindow類裡發現,mDecor是DecorView類的例項,同時DecorView是PhoneWindow的內部類。而我們發現:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

這裡實現的類名稱中說是root view,我們可以通過Android App開發技巧中關於UI佈局優化使用的SDK工具Hierarchy Viewer(用虛擬機器)來驗證一下。
這裡寫圖片描述

在上面的圖看到我們的xml佈局放置在FrameLayout的佈局中。(從其他的Demo也是統一的效果)。那就說明,我們的xml佈局是載入在一個FrameLayout中。

這裡我們繼續分析一下,上面PhoneWindow的superDispatchTouchEvent直接返回了DecorView的superDispatchTouchEvent,而DecorView又是繼承FrameLayout,FrameLayout又是繼承ViewGroup的。我們看一下DecorView類的superDispatchTouchEvent方法:

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

那麼我們可以理清了嗎?方法調來調去,在getWindow().superDispatchTouchEvent(ev)這句中其實本質執行的是一個ViewGroup的dispatchTouchEvent()方法。(這個ViewGroup是Activity特有的root view,也就是id為content的FrameLayout佈局),ViewGroup的dispatchTouchEvent()的內容就是我們前兩篇分析的內容。

然後我們繼續往下看Activity的onTouchEvent()方法。

    /**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

我們看一下條件判斷裡面的mWindow.shouldCloseOnTouch(this, event),我們去Window看一下shouldCloseOnTouch():

    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
    }

這裡判斷mCloseOnTouchOutside變數的值及是否為ACTION_DOWN事件,同時判斷event的x、y座標是不是超出Bounds,然後檢查FrameLayout的content的id的DecorView是否為空。其實沒啥太重要的,這只是對於處理window邊界之外的Touch事件有判斷價值而已。一般都是返回false。也就是說onTouchEvent()一般也是返回false。

那麼我們Activity的事件分發基本分析完。總結一下:
我們參照著流程圖來總結:
這裡寫圖片描述

  1. 事件到達Activity時,會呼叫Activity#dispatchTouchEvent方法,在這個方法,會把事件傳遞給Window,然後Window把事件傳遞給DecorView根佈局,我們所設定的佈局是它的一個子View。最後再從DecorView傳遞給我們的根ViewGroup。所以在Activity傳遞事件給ViwGroup的流程是這樣的:Activity->Window->DecorView->ViewGroup
  2. dispatchTouchEvent方法中如果是ACTION_DOWN的情況下會接著觸發onUserInteraction方法。
  3. 若Activity下面的子view攔截了TouchEvent事件(返回true)則Activity.onTouchEvent方法就不會執行。