Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 事件分發機制詳解

Android 事件分發機制詳解

編輯:關於Android編程

 

網上很多關於Android事件分發機制的解釋,大多數描述的都不夠清晰,沒有吧來龍去脈搞清楚,本文將帶你從Touch事件產生到Touch事件被消費這一全過程作全面的剖析。

產生Touch事件

這部分牽扯到硬件和Linux內核部分;我們簡單講述一下這部分內容,如果有興趣的話可以參考這篇文章。

傳遞Touch事件

觸摸事件是由Linux內核的一個Input子系統來管理的(InputManager),Linux子系統會在/dev/input/ 這個路徑下創建硬件輸入設備節點(這裡的硬件設備就是我們的觸摸屏了)。當手指觸動觸摸屏時,硬件設備通過設備節點像內核(其實是InputManager管理)報告事件,InputManager 經過處理將此事件傳給 Android系統的一個系統Service:WindowManagerService

TouchEvent01

WindowManagerService調用dispatchPointer()從存放WindowState的z-order順序列表中找到能接收當前touch事件的 WindowState,通過IWindow代理將此消息發送到IWindow服務端(IWindow.Stub子類),這個IWindow.Stub屬於ViewRoot(這個類繼承Handler,主要用於連接PhoneWindow和WindowManagerService),所以事件就傳到了ViewRoot.dispatchPointer()中.

我們來看一下ViewRoot的dispatchPointer方法:

1 public void dispatchPointer(MotionEvent event, long eventTime,
2             boolean callWhenDone) {
3         Message msg = obtainMessage(DISPATCH_POINTER);
4         msg.obj = event;
5         msg.arg1 = callWhenDone ? 1 : 0;
6         sendMessageAtTime(msg, eventTime);
7     }

dispatchPointer方法就是把這個事件封裝成Message發送出去,在ViewRoot Handler的handleMessage中被處理,其調用了mView.dispatchTouchEvent方法(mView是一個PhoneWindow.DecorView對象),PhoneWindow.DecorView繼承FrameLayout(FrameLayout繼承ViewGroup,ViewGroup繼承自View),DecorView裡的dispatchTouchEvent方法如下. 這裡的Callback的cb其實就是Activity的attach()方法裡的設置回調。

 1 //in file PhoneWindow.java
 2 public boolean dispatchTouchEvent(MotionEvent ev) {
 3             final Callback cb = getCallback();
 4             if (mEnableFaceDetection) {
 5                 int pointCount = ev.getPointerCount();
 6 
 7                 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
 8                 case MotionEvent.ACTION_POINTER_DOWN:
 9          
10          .......
11 
12             return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
13                     : super.dispatchTouchEvent(ev);
14         }
15 //in file Activity.java -> attach()
16         mFragments.attachActivity(this, mContainer, null);
17         mWindow = PolicyManager.makeNewWindow(this);
18         mWindow.setCallback(this);//設置回調

也就是說,正常情形下,當前的Activity就是這裡的cb,即調用了Activity的dispatchTouchEvent方法。

下面來分析一下從Activity到各個子View的事件傳遞和處理過程。

首先先分析Activity的dispatchTouchEvent方法。

1 public boolean dispatchTouchEvent(MotionEvent ev) {
2         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3             onUserInteraction();
4         }
5         if (getWindow().superDispatchTouchEvent(ev)) {
6             return true;
7         }
8         return onTouchEvent(ev);
9     }

onUserInteraction() 是一個空方法,開發者可以根據自己的需求覆寫這個方法(這個方法在一個Touch事件的周期肯定會調用到的)。如果判斷成立返回True,當前事件就不在傳播下去了。 superDispatchTouchEvent(ev) 這個方法做了什麼呢? getWindow().superDispatchTouchEvent(ev) 也就是調用了PhoneWindow.superDispatchTouchEvent 方法,而這個方法返回的是 mDecor.superDispatchTouchEvent(event),在內部類 DecorView(上文中的mDecor) 的superDispatchTouchEvent 中調用super.dispatchTouchEvent(event),而DecorView繼承自ViewGroup(通過FrameLayout,FrameLayout沒有dispatchTouchEvent),最終調用的是ViewGroup的dispatchTouchEvent方法。

小結一下。Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法通過 CallBack 調用了 Activity 的 dispatchTouchEvent 方法,在 Activity 這裡,我們可以重寫 Activity 的dispatchTouchEvent 方法阻斷 touch事件的傳播。接著在Activity裡的dispatchTouchEvent 方法裡,事件又再次傳遞到DecorView,DecorView通過調用父類(ViewGroup)的dispatchTouchEvent 將事件傳給父類處理,也就是我們下面要分析的方法,這才進入網上大部分文章講解的touch事件傳遞流程。

為什麼要從 PhoneWindow.DecorView 中 傳到 Activity,然後在傳回 PhoneWindow.DecorView 中呢? 主要是為了方便在Activity中通過控制dispatchTouchEvent 來控制當前Activity 事件的分發, 下一篇關於數據埋點文章就應用了這個機制

OK,我們要重點分析的就是ViewGroup中的dispatchTouchEvent方法。

  1 @Override
  2     public boolean dispatchTouchEvent(MotionEvent ev) {
  3             //......
  4 
  5             // Check for interception.
  6             final boolean intercepted;//是否被攔截
  7             if (actionMasked == MotionEvent.ACTION_DOWN
  8                     || mFirstTouchTarget != null) {//Touch按下事件
  9                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 10                 if (!disallowIntercept) {
 11                     intercepted = onInterceptTouchEvent(ev);//判斷消息是否需要被viewGroup攔截,這個方法我們可以覆寫,
 12                                             //覆寫生效的前提是 disallowIntercept 為FALSE,否則寫了也沒用
 13                     ev.setAction(action); // restore action in case it was changed
 14                 } else {//不允許攔截
 15                     intercepted = false;
 16                 }
 17             } else {
 18                 // There are no touch targets and this action is not an initial down
 19                 // so this view group continues to intercept touches.
 20         //這個操作不是一開始down事件,我們把它置為TRUE,攔截之
 21                 intercepted = true;
 22             }
 23 
 24             // Check for cancelation.
 25             final boolean canceled = resetCancelNextUpFlag(this)
 26                     || actionMasked == MotionEvent.ACTION_CANCEL;
 27 
 28                 //.........
 29 
 30                     final int childrenCount = mChildrenCount;//ViewGroup中子View的個數
 31                     if (newTouchTarget == null && childrenCount != 0) {
 32                         final float x = ev.getX(actionIndex);//獲取坐標,用來比對
 33                         final float y = ev.getY(actionIndex);
 34                         // Find a child that can receive the event.
 35                         // Scan children from front to back.
 36                         final View[] children = mChildren;//獲取viewgroup所有的子view
 37 
 38                         final boolean customOrder = isChildrenDrawingOrderEnabled();//子View的繪制順序
 39                         ////從高到低遍歷所有子View,找到能處理touch事件的child View
 40                         for (int i = childrenCount - 1; i >= 0; i--) {
 41                             final int childIndex = customOrder ?
 42                                     getChildDrawingOrder(childrenCount, i) : i;//根據Order獲取子view
 43                             final View child = children[childIndex];
 44                             //判斷是不是我們需要的View
 45                             if (!canViewReceivePointerEvents(child)
 46                                     || !isTransformedTouchPointInView(x, y, child, null)) {
 47                                 continue;
 48                             }
 49 
 50                             newTouchTarget = getTouchTarget(child);//從鏈表裡找子view
 51                             if (newTouchTarget != null) {//找到子view
 52                                 // Child is already receiving touch within its bounds.
 53                                 // Give it the new pointer in addition to the ones it is handling.
 54                 //已經找到,循環結束,目標就是newTouchTarget
 55                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
 56                                 break;
 57                             }
 58 
 59                     //.......
 60                         }
 61                     }
 62                 }
 63             }
 64 
 65             // Dispatch to touch targets.
 66             if (mFirstTouchTarget == null) {
 67                 // No touch targets so treat this as an ordinary view.
 68     /*dispatchTransformedTouchEvent方法中,如果child是null,那麼就調用super.dispatchTouchEvent,
 69    *也就是ViewGroup的父類View的dispatchTouchEvent(如果我們在前面攔截了touch事件,那麼就會這樣處理),
 70    *如果不是null,則調用child.dispatchTouchEvent。
 71    **/
 72                 handled = dispatchTransformedTouchEvent(ev, canceled, null,
 73                         TouchTarget.ALL_POINTER_IDS);
 74             } else {
 75             //....
 76             }
 77         //........
 78         return handled;
 79     }
 80  /**
 81      * Transforms a motion event into the coordinate space of a particular child view,
 82      * filters out irrelevant pointer ids, and overrides its action if necessary.
 83      * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 84      */
 85     private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
 86             View child, int desiredPointerIdBits) {
 87         final boolean handled;
 88  
 89         //……
 90  
 91         // Perform any necessary transformations and dispatch.
 92         if (child == null) {
 93             handled = super.dispatchTouchEvent(transformedEvent);
 94         } else {
 95             //……
 96             handled = child.dispatchTouchEvent(transformedEvent);
 97         }
 98  
 99         // Done.
100         //……
101         return handled;
102     }

我們來總結一下 ViewGroup 的 dispatchTouchEvent 的調用過程。

  1. 首先判斷此 MotionEvent 能否被攔截,如果是的話,能調用我們覆寫 onInterceptTouchEvent來處理攔截到的事件;如果此方法返回TRUE,表示需要攔截,那麼事件到此為止,就不會傳遞到子View中去。這裡要注意,onInterceptTouchEvent 方法默認是返回FALSE。
  2. 若沒有攔截此Event,首先找到此ViewGroup中所有的子View,通過方法 canViewReceivePointerEvents和isTransformedTouchPointInView,對每個子View通過坐標(Event事件坐標和子View坐標比對)計算,找到坐標匹配的View。
  3. 調用dispatchTransformedTouchEvent方法,處理Event事件。
     1 //ViewGroup.java dispatchTransformedTouchEvent方法截取
     2         if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
     3             event.setAction(MotionEvent.ACTION_CANCEL);
     4             if (child == null) {//Event事件被截獲,調用父類View的dispatchTouchEvent方法
     5                 handled = super.dispatchTouchEvent(event);
     6             } else {
     7                 handled = child.dispatchTouchEvent(event);//調用子View的dispatchTouchEvent方法
     8             }
     9             event.setAction(oldAction);
    10             return handled;
    11         }
    1. 假設這個子View是一個Button,會調用Button.dispatchTouchEvent 方法,Button和它的父類TextView都沒有dispatchTouchEvent方法,只能繼續看父類View了,其實最終調用的還是View.dispatchTouchEvent 方法。
    2. 我們繼續分析View.dispatchTouchEvent 方法。mOnTouchListener 是OnTouchListener對象,由setOnTouchListener 方法設置;
       1 public boolean dispatchTouchEvent(MotionEvent event) {
       2         if (mInputEventConsistencyVerifier != null) {
       3             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
       4         }
       5 
       6         if (onFilterTouchEventForSecurity(event)) {
       7             //noinspection SimplifiableIfStatement
       8             ListenerInfo li = mListenerInfo;//View 內部類,管理一些listener
       9             if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
      10                     && li.mOnTouchListener.onTouch(this, event)) {
      11                 return true;
      12             }
      13 
      14             if (onTouchEvent(event)) {
      15                 return true;
      16             }
      17         }
      18 
      19         if (mInputEventConsistencyVerifier != null) {
      20             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
      21         }
      22         return false;//沒有消費掉,只能返回false,讓調用者來處理了。
      23     }
      24     /**
      25      * Register a callback to be invoked when a touch event is sent to this view.
      26      * @param l the touch listener to attach to this view
      27      */
      28     public void setOnTouchListener(OnTouchListener l) {
      29         getListenerInfo().mOnTouchListener = l;
      30     }

      若當前ListenerInfo 方法初始化並且 li.mOnTouchListener 的值不為空且ENABLE掩碼為Enable,那麼調用mOnTouchListener(this,event)方法。boolean onTouch(View v, MotionEvent event) 這個方法是在View的內部接口 OnTouchListener中的,是一個空方法,需要用戶自己來實現。拿一個Button來舉例;我們覆寫的onTouch()方法在這裡被調用。

      1 button.setOnTouchListener(new OnTouchListener() {
      2             @Override
      3             public boolean onTouch(View v, MotionEvent event) {
      4               //實現自己的功能
      5                 return true;
      6             }
      7         });
      1. 若onTouch方法返回true,則表示被消費,不會繼續傳遞下去;返回false,表示時間還沒被消費,繼續傳遞到 onTouchEvent 這個方法裡。
         1 public boolean onTouchEvent(MotionEvent event) {
         2         //……
         3         if (((viewFlags & CLICKABLE) == CLICKABLE ||
         4                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
         5             switch (event.getAction()) {
         6                 case MotionEvent.ACTION_UP:
         7                     //……
         8                     //如果沒有觸發長按事件,手指動作是up,則執行performClick()方法
         9                         if (!mHasPerformedLongPress) {
        10                             // This is a tap, so remove the longpress check
        11                             removeLongPressCallback();
        12  
        13                             // Only perform take click actions if we were in the pressed state
        14                             if (!focusTaken) {
        15                                 // Use a Runnable and post this rather than calling
        16                                 // performClick directly. This lets other visual state
        17                                 // of the view update before click actions start.
        18                                 //這裡判斷並去執行單擊事件
        19                                 if (mPerformClick == null) {
        20                                     mPerformClick = new PerformClick();
        21                                 }
        22                                 if (!post(mPerformClick)) {
        23                                     performClick();
        24                                 }
        25                             }
        26                         }
        27  
        28                     break;
        29                 case MotionEvent.ACTION_DOWN:
        30                         //……
        31                         //是否觸發長按事件是在這裡判斷的,具體細節我就不貼出來了
        32                         checkForLongClick(0);
        33                         //……
        34                     break;
        35                     //……
        36             }
        37             return true;
        38         }
        39  
        40         return false;
        41     }

        若狀態不是CLICKABLE,那麼會直接跳過判斷執行return false,這意味著後續的touch事件不會再傳遞過來了。而大家注意看,只要是CLICKABLE,那麼無論case哪個節點,最後都是return true,這樣就保證了後續事件可以傳遞過來。 很明顯在onTouchEvent 方法裡面,主要就是判斷應該執行哪個操作,是長按還是單擊,然後去執行對應的方法。我們看看如果是單擊,執行的方法:

         1 public boolean performClick() {
         2         //……
         3  
         4         ListenerInfo li = mListenerInfo;
         5         if (li != null && li.mOnClickListener != null) {
         6             //播放點擊音效
         7             playSoundEffect(SoundEffectConstants.CLICK);
         8             //執行onClick方法
         9             li.mOnClickListener.onClick(this);
        10             return true;
        11         }
        12  
        13         return false;
        14     }

        其實就是調用了我們OnClickListener裡面的onClick方法。所以說,當onTouch() 和 onClick()都存在時候,肯定是先執行onTouch,之後再執行onClick;如果onTouch 把事件截獲直接return true,那麼 onClick 方法就不會執行了。 到這裡,整個touch事件的傳遞過程我們就分析完了。

        Touch事件一般調用過程總結

        用戶點擊屏幕產生Touch(包括DOWN、UP、MOVE,本文分析的是DOWN)事件 -> InputManager -> WindowManagerService.dispatchPointer() -> IWindow.Stub -> ViewRoot.dispatchPointer() -> PhoneWindow.DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent -> PhoneWindow.DecorView.superDispatchTouchEvent -> ViewGroup.dispatchTouchEvent() -> ViewGroup.dispatchTransformedTouchEvent() -> 子View.dispatchTouchEvent() -> 子View.onTouch() -> 子View.onTouchEvent() -> 事件被消費結束

        有用的參考

        1. Android FrameWork——Touch事件派發過程詳解
        2. android的窗口機制分析------事件處理
        3. Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved