1. 程式人生 > >Android FrameWork——Touch事件派發過程詳解

Android FrameWork——Touch事件派發過程詳解

對於android的視窗window管理,一直感覺很混亂,總想找個時間好好研究,卻不知如何入手,現在寫的Touch事件派發過程詳解,其實跟android的視窗window管理服務WindowManagerService存在緊密聯絡,所以從這裡入手切入到WindowManagerService的研究,本blog主要講述一個touch事件如何從使用者訊息的採集,到WindowManagerService對Touch事件的派發,再到一個Activity視窗touch事件的派發,並著重講了Activity視窗touch事件的派發,因為這個的理解對我們寫應用很好地處理touch事件很重要

一.使用者事件採集到WindowManagerService和派發

--1.WindowManagerService,顧名思義,它是是一個視窗管理系統服務,它的主要功能包含如下:
        --視窗管理,繪製
        --轉場動畫--Activity切換動畫
        --Z-ordered的維護,Activity視窗顯示前後順序
        --輸入法管理
        --Token管理
        --系統訊息收集執行緒
        --系統訊息分發執行緒
這裡,我關注的是系統訊息的收集和系統訊息的分發,其他功能,當我對WindowManagerService有一個完整的研究後在發blog

--2.系統訊息收集和分發執行緒的建立
這個的從WindowManagerService服務的建立說起,與其他系統服務一樣,WindowManagerService在systemServer中建立的:
ServerThread.run
-->WindowManagerService.main
   -->WindowManagerService.WMThread.run(構建一個專門執行緒負責WindowManagerService)
      -->WindowManagerService s = new WindowManagerService(mContext, mPM,mHaveInputMethods);
         --mQueue = new KeyQ();//訊息佇列,在構造KeyQ中會建立一個InputDeviceReader執行緒去讀取使用者輸入訊息

         --mInputThread = new InputDispatcherThread();//建立一個訊息分發執行緒,讀取並處理mQueue中訊息

整個過程處理原理很簡單,典型的生產者消費者模型,我先畫個圖,後面針對程式碼進一步說明

--3.InputDeviceReader執行緒,KeyQ構建時,會啟動一個執行緒去讀取使用者訊息,具體程式碼在KeyInputQueue.mThread,在建構函式中,mThread會start,接下來,接研究一下mThread.run:
    //使用者輸入事件訊息讀取執行緒
    Thread mThread = new Thread("InputDeviceReader") {
        public void run() {
            RawInputEvent ev = new RawInputEvent();
            while (true) {//開始訊息讀取迴圈
                try {
                    InputDevice di;
                    //本地方法實現,讀取使用者輸入事件
                    readEvent(ev);

                    //根據ev事件進行相關處理
                    ...
                    synchronized (mFirst) {//mFirst是keyQ佇列頭指標
                    ...
                    addLocked(di, curTimeNano, ev.flags,RawInputEvent.CLASS_TOUCHSCREEN, me);
                    ...
                    }
                }
        }
       }
函式我也沒有看大明白:首先呼叫本地方法readEvent(ev);去讀取使用者訊息,這個訊息包括按鍵,觸控,滾輪等所有使用者輸入事件,後面不同的事件型別會有不同的處理,不過最後事件都要新增到keyQ的佇列中,通過addLocked函式

--4佇列新增和讀取函式addLocked,getEvent
addLocked函式比較簡單,就分析一下,有助於對訊息佇列KeyQ的資料結構進行理解:
    //event加入inputQueue佇列
    private void addLocked(InputDevice device, long whenNano, int flags,
            int classType, Object event) {
        boolean poke = mFirst.next == mLast;//poke為true表示訊息佇列為空
        //從QueuedEvent快取QueuedEvent獲取一個QueuedEvent物件,並填入使用者事件資料,包裝成一個QueuedEvent
        QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event);
        QueuedEvent p = mLast.prev;//佇列尾節點為mLast,把ev新增到mlast前
        while (p != mFirst && ev.whenNano < p.whenNano) {
            p = p.prev;
        }
        ev.next = p.next;
        ev.prev = p;
        p.next = ev;
        ev.next.prev = ev;
        ev.inQueue = true;

        if (poke) {//poke為true,意味著在空佇列中添加了一個QueuedEvent,這時系統訊息分發執行緒可能在wait,需要notify一下
            long time;
            if (MEASURE_LATENCY) {
                time = System.nanoTime();
            }
            mFirst.notify();//喚醒在 mFirst上等待的執行緒
            mWakeLock.acquire();
            if (MEASURE_LATENCY) {
                lt.sample("1 addLocked-queued event ", System.nanoTime() - time);
            }
        }
    }
很簡單,使用mFirst,mLast實現的指標佇列,addLocked是QueuedEvent物件新增函式,對應在系統訊息分發執行緒中會有一個getEvent函式來讀取inputQueue佇列的訊息,我在這裡也先講一下:
    QueuedEvent getEvent(long timeoutMS) {
        long begin = SystemClock.uptimeMillis();
        final long end = begin+timeoutMS;
        long now = begin;
        synchronized (mFirst) {//獲取mFirst上同步鎖
            while (mFirst.next == mLast && end > now) {
                try {//mFirst.next == mLast意味佇列為空,同步等待mFirst鎖物件
                    mWakeLock.release();
                    mFirst.wait(end-now);
                }
                catch (InterruptedException e) {
                }
                now = SystemClock.uptimeMillis();
                if (begin > now) {
                    begin = now;
                }
            }
            if (mFirst.next == mLast) {
                return null;
            }
            QueuedEvent p = mFirst.next;//返回mFirst的下一個節點為處理的QueuedEvent
            mFirst.next = p.next;
            mFirst.next.prev = mFirst;
            p.inQueue = false;
            return p;
        }
    }

通過上面兩個函式得知,訊息佇列是通過mFirst,mLast實現的生產者消費模型的同步連結串列佇列

--5.InputDispatcherThread執行緒
InputDispatcherThread處理InputDeviceReader執行緒存放在KeyInputQueue佇列中的訊息,分發到具體的一個客戶端的IWindow
InputDispatcherThread.run
-->windowManagerService.process{               
            ...
            while (true) {               
                // 從mQueue(KeyQ)獲取一個使用者輸入事件,正上呼叫我上面提到的getEvent方法,若佇列為空,執行緒阻塞掛起
                QueuedEvent ev = mQueue.getEvent(
                    (int)((!configChanged && curTime < nextKeyTime)
                            ? (nextKeyTime-curTime) : 0));
                ...
                try {
                    if (ev != null) {
                        ...
                        if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {//touch事件
                            eventType = eventType((MotionEvent)ev.event);
                        } else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
                                    ev.classType == RawInputEvent.CLASS_TRACKBALL) {//鍵盤輸入事件
                            eventType = LocalPowerManager.BUTTON_EVENT;
                        } else {
                            eventType = LocalPowerManager.OTHER_EVENT;//其他事件
                        }
                        ...
                        switch (ev.classType) {
                            case RawInputEvent.CLASS_KEYBOARD:
                                ...
                                dispatchKey((KeyEvent)ev.event, 0, 0);//鍵盤輸入,派發key事件
                                mQueue.recycleEvent(ev);
                                break;
                            case RawInputEvent.CLASS_TOUCHSCREEN:
                                dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);//touch事件,派發touch事件
                                break;
                            case RawInputEvent.CLASS_TRACKBALL:
                                dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);//滾輪事件,派發Trackball事件
                                break;
                            case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
                                configChanged = true;
                                break;
                            default:
                                mQueue.recycleEvent(ev);//銷燬事件
                            break;
                        }

                    }
                } catch (Exception e) {
                    Slog.e(TAG,
                        "Input thread received uncaught exception: " + e, e);
                }
            }       
   }

WindowManagerService.dispatchPointer,一旦判斷QueuedEvent為螢幕點選事件,就呼叫函式WindowManagerService.dispatchPointer進行處理:
WindowManagerService.dispatchPointer
-->WindowManagerService.KeyWaiter.waitForNextEventTarget(獲取touch事件要派發的目標windowSate)
   -->WindowManagerService.KeyWaiter.findTargetWindow(從一個一個WindowSate的z-order順序列表mWindow中獲取一個能夠接收當前touch事件的WindowSate)
-->WindowSate target = waitForNextEventTarget返回的WindowSate物件
-->target.mClient.dispatchPointer(ev, eventTime, true);(往目標window派發touch訊息
target.mClient是一個IWindow代理物件IWindow.Proxy,它對應的代理類是ViewRoot.W,通過遠端代理呼叫,WindowManagerService把touch訊息派發到了對應的Activity的PhoneWindow
之後進一步WindowManagerService到Activity訊息的派發在下文中說明

二WindowManagerService派發Touch事件到當前top Activity

--1.先我們看一個system_process的touch事件訊息呼叫堆疊,在WindowManagerService中的函式dispatchPointer,通過一個IWindow的客戶端代理物件把訊息傳送到相應的IWindow服務端,也就是一個IWindow.Stub子類。
Thread [<21> InputDispatcher] (Suspended (breakpoint at line 321 in IWindow$Stub$Proxy))       
        IWindow$Stub$Proxy.dispatchPointer(MotionEvent, long, boolean) line: 321       
        WindowManagerService.dispatchPointer(KeyInputQueue$QueuedEvent, MotionEvent, int, int) line: 5270              
        WindowManagerService$InputDispatcherThread.process() line: 6602       
        WindowManagerService$InputDispatcherThread.run() line: 6482  

--2.通過IWindow.Stub.Proxy代理物件把訊息傳遞給IWindow.Stub物件。code=TRANSACTION_dispatchPointer,IWindow.Stub物件被ViewRoot擁有(成員mWindow,它是一個ViewRoot.W類物件)

--3.在case TRANSACTION_dispatchPointer會呼叫IWindow.Stub子類的實現方法dispatchPointer

--4.IWindow.Stub.dispatchPointer
        -->ViewRoot.W.dispatchPointer
                -->ViewRoot.dispatchPointer
    public void dispatchPointer(MotionEvent event, long eventTime,
            boolean callWhenDone) {
        Message msg = obtainMessage(DISPATCH_POINTER);
        msg.obj = event;
        msg.arg1 = callWhenDone ? 1 : 0;
        sendMessageAtTime(msg, eventTime);
    }

--5.ViewRoot繼承自handle,在handleMessage函式的case-DISPATCH_POINTER會呼叫mView.dispatchTouchEvent(event),
mView是一個PhoneWindow.DecorView物件,在PhoneWindow.openPanel方法會建立一個ViewRoot物件,並設定ViewRoot物件的mView為一個PhoneWindow.decorView成員,PhoneWindow.DecorView是真正的root view,它繼承自FrameLayout,這樣呼叫mView.dispatchTouchEvent(event)
其實就是呼叫PhoneWindow.decorView的dispatchTouchEvent方法:
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback();
            return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
                    .dispatchTouchEvent(ev);
        } 

--6.分析上面一段紅色程式碼,可以寫成return (cb != null) && (mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev)).當cb不為null執行後面,如果mFeatureId<0,執行cb.dispatchTouchEvent(ev),否則執行super.dispatchTouchEvent(ev),也就是FrameLayout.dispatchTouchEvent(ev),那麼callback cb是什麼呢?是Window類的一個成員mCallback,我下面給一個圖你可以看到何時被賦值的:
setCallback(Callback) : void - android.view.Window
        -->attach(Context, ActivityThread, Instrumentation, IBinder, int, Application, Intent, ActivityInfo, CharSequence, Activity, String, Object, HashMap<String, Object>, Configuration) : void - android.app.Activity
               --> performLaunchActivity(ActivityRecord, Intent) : Activity - android.app.ActivityThread
performLaunchActivity我們很熟識,因為我前面在講Activity啟動過程詳解時候講過,在啟動一個新的Activity會執行該方法,在該方法裡面會執行attach方法,找到attach方法對應程式碼可以看到:
        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);
mWindow就是一個PhoneWindow,它是Activity的一個內部成員,通過呼叫mWindow的setCallback(this),把新建立的Activity設定為PhoneWindow一個mCallback成員,這樣我們就清楚了,前面的cb就是擁有這個PhoneWindow的Activity,cb.dispatchTouchEvent(ev)也就是執行:Activity.dispatchTouchEvent
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //getWindow()返回的就是PhoneWindow物件,執行superDispatchTouchEvent,就是執行PhoneWindow.superDispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //執行Activity.onTouchEvent方法
        return onTouchEvent(ev);
    }

--7.再看PhoneWindow.superDispatchTouchEvent:
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
                -->        public boolean superDispatchTouchEvent(MotionEvent event) {
                                    return super.dispatchTouchEvent(event);//FrameLayout.dispatchTouchEvent
        }
    }
superDispatchTouchEvent呼叫super.dispatchTouchEvent,我前面講過mDector是一個PhoneWindow.DecorView,它是一個真正Activity的root view,它繼承了FrameLayout,通過super.dispatchTouchEvent他會把touchevent派發給各個activity的子view,也就是我們再Activity.onCreat方法中setContentView時設定的view,touch event時間如何在Activity各個view中進行派發的我後面再作詳細說明,但是從上面我們可以看出一點若Activity下面的子view攔截了touchevent事件(返回true),Activity.onTouchEvent就不會執行。

--8.這部分,我再畫一個靜態類結構圖把前面講到的一些類串起來看一下:

我用紅色箭頭線把整個訊息派發過程過程給串起來,然後system_process程序和ap程序分別用虛線橢圓圈起,這樣以後相信你更理解各個類之間關係。

對應的物件空間圖如下,與上面圖是對應的,只是從不同角度去看:

--9.其實上面所講的大部分已經是在客戶端ap中執行了,也就是在ap程序中,只是執行邏輯基本是框架程式碼中,還沒有到達我們使用layout.xml佈局的view中來,這裡我先在我們的一個view中onTouchEvent插入一個斷點看一看訊息從WindowManagerService到達Activity.PhoneWindow後執行堆疊情況(我插入的斷點在Launcher2的HandleView中),後面繼續講解:
Thread [<1> main] (Suspended (breakpoint at line 4280 in View))       
        HandleView(View).onTouchEvent(MotionEvent) line: 4280       
        HandleView.onTouchEvent(MotionEvent) line: 71       
        HandleView(View).dispatchTouchEvent(MotionEvent) line: 3766       
        RelativeLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863       
        DragLayer(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863       
        FrameLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863       
        PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863       
        PhoneWindow$DecorView.superDispatchTouchEvent(MotionEvent) line: 1671       
        PhoneWindow.superDispatchTouchEvent(MotionEvent) line: 1107       
        ForyouLauncher(Activity).dispatchTouchEvent(MotionEvent) line: 2086       
        PhoneWindow$DecorView.dispatchTouchEvent(MotionEvent) line: 1655       
        ViewRoot.handleMessage(Message) line: 1785       
        ViewRoot(Handler).dispatchMessage(Message) line: 99       
        Looper.loop() line: 123       
        ActivityThread.main(String[]) line: 4634

三.Activity中View中的Touch事件派發

--1.首先我畫一個Activity中的view層次結構圖:

前面我講過,來自windowManagerService的touch訊息最終會派發到到Decorview,Decorview繼承子FrameLayout,它只有一個子view就是mContentParent,我們寫ap的view全部新增到到mContentParent。

--2.瞭解了Activity中的view的層次結構,那先從DecorView開始看touch事件是如何被派發的,前面講過最終訊息會派發到FrameLayout.dispatchTouchEvent也就是ViewGroup.dispatchTouchEvent(FrameLayout也沒有覆蓋該方法),
同樣mContentParent也是執行ViewGroup.dispatchTouchEvent來派發touch訊息,那我們就詳細看一下ViewGroup.dispatchTouchEvent(若要很好掌握應用程式touch事件處理,這部分要重點看):
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ......
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//計算是否禁止touch Intercept
        if (action == MotionEvent.ACTION_DOWN) {//按下事件,也就是touch開始
            if (mMotionTarget != null) {
                mMotionTarget = null;//清除mMotionTarget,也就是說每次touch開始,mMotionTarget要被重新設定
            }
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {//判斷訊息是否需要被viewGroup攔截
                // 訊息不被viewGroup攔截,找到相應的子view進行touch事件派發
                ev.setAction(MotionEvent.ACTION_DOWN);//重新設定event 為action_down
              
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;//獲取viewgroup所有的子view
                final int count = mChildrenCount;
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                            || child.getAnimation() != null) {//若子view可見或者有動畫在執行的,才能夠接收touch事件
                        child.getHitRect(frame);//獲取子view的佈局座標區域
                        if (frame.contains(scrolledXInt, scrolledYInt)) {//若子view 區域包含當前touch點選區域
                            // offset the event to the view's coordinate system
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                            if (child.dispatchTouchEvent(ev))  {//派發TouchEvent給包含這個touch區域的子view
                                // 若該子view消費了對應的touch事件
                                mMotionTarget = child;//設定viewgroup訊息派發的目標子view
                                return true;//返回true,該touch事件被消費掉
                            }
                        }
                    }
                }
            }
          //若touch事件被攔截,mMotionTarget = null,後面touch訊息不再派發給子view
        }

        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||//計算是up或者cancel
                (action == MotionEvent.ACTION_CANCEL);

        if (isUpOrCancel) {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

      
        final View target = mMotionTarget;
        if (target == null) {
            //target為null,意味著在ACTION_DOWN時沒有找到能消費touch訊息的子view或者在ACTION_DOWN時訊息被攔截了,這個時候
            //呼叫父類view的dispatchTouchEvent訊息進行派發,也就是說,此時viewgroup處理touch訊息跟普通view一致。
            ev.setLocation(xf, yf);
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
                ev.setAction(MotionEvent.ACTION_CANCEL);
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            }
            return super.dispatchTouchEvent(ev);
        }

        //target!=null,意味在ACTION_DOWN時touch訊息沒有被攔截,而且子view target消費了ACTION_DOWN訊息,需要再判斷訊息是否被攔截
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
            //訊息被攔截,而前面ACTION_DOWN時touch訊息沒有被攔截,所以需要傳送ACTION_CANCEL通知子view target
            final float xc = scrolledXFloat - (float) target.mLeft;
            final float yc = scrolledYFloat - (float) target.mTop;
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xc, yc);
            if (!target.dispatchTouchEvent(ev)) {
                // 派發訊息ACTION_CANCEL給子view target
            }
            // mMotionTarget=null,後面訊息不再派發給子view
            mMotionTarget = null;
            return true;
        }

        if (isUpOrCancel) {
            //isUpOrCancel,設定mMotionTarget=null,後面訊息不再派發給子view
            mMotionTarget = null;
        }

        ......
        //沒有被攔截繼續派發訊息給子view target
        return target.dispatchTouchEvent(ev);
    }

--3.ViewGroup.dispatchTouchEvent我查看了一下所有子類,只有PhoneWindow.DecorView覆蓋了該方法,該方法前面講DecorView訊息派發時提過,它會找到對應包含這個PhoneWindow.DecorView物件的Activity把訊息交給Activity去處理,其它所有viewGroup的子類均沒有覆蓋dispatchTouchEvent,也就是說所有包含子view的父view對於touch訊息派發均採用上面的邏輯,當然,必要的時候我們可以覆蓋該方法實現自己的touch訊息派發邏輯,如Launcher2中的workspace類就是重新實現的該dispatchTouchEvent方法,從上面的dispatchTouchEvent函式邏輯其實我們也可以總結幾條touch訊息派發邏輯:
(1).onInterceptTouchEvent用來定義是否擷取touch訊息邏輯,若在groupview中想擷取touch訊息,必須覆蓋viewgroup中該方法
(2).訊息在整個dispatchTouchEvent過程中,若子view.dispatchTouchEvent返回true,父view中將不再處理該訊息,但前提是該訊息沒有被父view擷取,在整個touch訊息處理過程中,若處理函式返回true,我們稱之為消費了該touch事件,並且後面的父view將不再處理該訊息。
(3).在整個touch事件過程中,從action_down到action_up,若父ViewGroup的函式onInterceptTouchEvent一旦返回true,訊息將不再派發給子view,細分可為兩種情況,若是在action_down時onInterceptTouchEvent返回true,不會派發任何訊息給子view,並且後面onInterceptTouchEvent函式將不再會被執行若是action_down時onInterceptTouchEvent返回false ,而後面touch過程中onInterceptTouchEvent==true,父viewGroup會把action_cancel派發給子view,也之後不再派發訊息給子view,並且onInterceptTouchEvent函式後面將不再被執行。

--4.為了更清楚的理解viewGroup訊息的派發流程,我畫一個流程圖如下:

--5.上面我只是講了父view與子view之間當有touch事件的訊息派發流程,對於view的訊息是怎麼派發的(也包裹viewGroup沒有子view或者有子view但是不消費該touch訊息情況),因為從繼承結構上看viewgroup繼承了view,viewgroup覆蓋了view的dispatchTouchEvent方法,不過從上面流程圖也可以看到當mMotionTarget為Null它會執行父類view.dispatchTouchEvent,其他view的子類都是執行view.dispatchTouchEvent派發touch事件,不過若我們自定義view是可以覆蓋該方法的。下面就仔細研究一下view.dispatchTouchEvent方法的程式碼:
    public final boolean dispatchTouchEvent(MotionEvent event) {
        //mOnTouchListener是被View.setOnTouchListener設定的,(mViewFlags & ENABLED_MASK)計算view是否可被點選
        //當view可被點選並且mOnTouchListener被設定,執行mOnTouchListener.onTouch
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;//若mOnTouchListener.onTouch返回true,函式返回true
        }
        return onTouchEvent(event);//若mOnTouchListener.onTouch返回false,呼叫onToucheEvent
    }
函式邏輯很簡單,前面的viewGroup touch事件流程圖中我已經畫出的,為區別我把它著色成青綠色,總結一句話若mOnTouchListener處理了touch訊息,不執行onTouchEvent,否則交給onTouchEvent進行處理。
不知道是否講清楚的,要清楚掌握估計還得寫些例子測試一下是否是我上面所說的流程,不過我想了解事件的派發流程,對寫應用的事件處理相信很有用,比如我以前碰到一個問題是手指點選螢幕到底是子view執行onclick還是執行父view的view移動,這個時候就需要深入瞭解viewde touch事件派發流程,該響應點選的時候響應子view的點選,該父view移動的時候攔截touch事件交給父view進行處理。