zoukankan      html  css  js  c++  java
  • Android 事件分发机制具体解释

    很多其它内容请參照我的个人网站: http://stackvoid.com/

    网上非常多关于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事件分发机制全然解析,带你从源代码的角度彻底理解(下)
  • 相关阅读:
    Jenkins安装及配置
    数据库命令扩展
    常用的数据库命令
    如何使用NiFi等构建IIoT系统
    云计算之概念——IaaS、SaaS、PaaS、Daas
    emqx的一个配置参数
    利用jsoup抓取网页图片
    nohup使用
    jsoup的使用
    java知识点链接
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4388117.html
Copyright © 2011-2022 走看看