Android的事件处理机制

Android的事件处理机制

Android的事件产生是从我们触摸屏幕开始,在经过Input子系统,最后达到我们的应用程序(或者经过WindowManagerService到达应用程序)。

而其中Input子系统在Java层对应着InputManagerService,其主要在native层,由InputReader读取EventHub的元数据,将这些数据加工成InputEvent,最后发到InputDispatcher,而InputDispatcher则负责将事件发到应用程序,Input子系统流程可以参见这篇文章 Android Framework——之Input子系统。 和 Android 事件捕捉和处理流程分析

对于应用层的时间流程,主要是下面的流程图所示:
key-transfer

其中最后一步就是我们经常说的View事件派发流程。而在流程图中,我们也看到,调了两次DecorView的方法,第一次是调用DecorView的dispatchTouchEvent,它的源码是:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Callback cb = getCallback();
    return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
            : super.dispatchTouchEvent(ev);
}

调用了Callback的dispatchTouchEvent,那这个Callback是什么类型呢?

Callback就是Window.Callback,Activity实现了这个接口。在Activity的attach函数中,会调用window的setCallback,将Activity设置给Window。所以这里getCallback返回的就是Activity,最终会调用Activity的dispatchTouchEvent。

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

在ACTION_DOWN事件处理的时候会调用onUserInteraction方法,然后调用Window(实际上是PhoneWindow)的superDispatchTouchEvent,如果Window的superDispatchTouchEvent消耗了事件,则直接返回,不会调用Activity的onTouchEvent方法。

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

而DecorView的superDispatchTouchEvent为:

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

最终还是调用DecorView的父类的dispatchTouchEvent,DecorView的父类是FrameLayout,它没实现该方法,最终会调用ViewGroup的dispatchTouchEvent方法。从这里开始就进入了view树的事件派发流程了。

此外,关于Android view树的事件派发流程,众说纷纭,给出一篇参考文章

Reference:Android事件传递机制

然后,站在前人的肩膀上,接下俩文章主要做些验证。

Question:

  • Activity中dispatchTouchEvent在什么时候调用?

  • MotionEvent事件向下分发,是通过什么途径?通过dispatchKeyEvent的返回值?

简单列举下工程

  • Activity:

    // haio.com.event.MainActivity
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("Activity---onTouchEvent---DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("Activity---onTouchEvent---MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("Activity---onTouchEvent---UP");
                break;
            default:
                break;
        }
        boolean isTouch = super.onTouchEvent(event);
        System.out.println("Activity---onTouchEvent---isTouch =" + isTouch);
        return isTouch;
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("Activity---dispatchTouchEvent---DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("Activity---dispatchTouchEvent---MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("Activity---dispatchTouchEvent---UP");
                break;
            default:
                break;
        }
    //        boolean isDispatch = super.dispatchTouchEvent(event);
        boolean isDispatch = true;
        System.out.println("Activity---dispatchTouchEvent---isDispatch =" + isDispatch);
        return isDispatch;
    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        System.out.println("Activity---onKeyDown---DOWN");
        boolean isDown = super.onKeyDown(keyCode, event);
        System.out.println("Activity---onKeyDown---isDown =" + isDown);
        return isDown;
    }
    
  • HButton

    View(如 Button等) 涉及事件分发共涉及两个个事件

    • dispatchTouchEvent
    • onTouchEvent

      //haio.com.event.widget.HButton
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  System.out.println("HButton---onTouchEvent---DOWN");
                  break;
              case MotionEvent.ACTION_MOVE:
                  System.out.println("HButton---onTouchEvent---MOVE");
                  break;
              case MotionEvent.ACTION_UP:
                  System.out.println("HButton---onTouchEvent---UP");
                  break;
              default:
                  break;
          }
          boolean isTouch = super.onTouchEvent(event);
          System.out.println("HButton---onTouchEvent---isTouch =" + isTouch);
          return isTouch;
      }
      
      @Override
      public boolean dispatchTouchEvent(MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  System.out.println("HButton---dispatchTouchEvent---DOWN");
                  break;
              case MotionEvent.ACTION_MOVE:
                  System.out.println("HButton---dispatchTouchEvent---MOVE");
                  break;
              case MotionEvent.ACTION_UP:
                  System.out.println("HButton---dispatchTouchEvent---UP");
                  break;
              default:
                  break;
          }
          boolean isDispatch = super.dispatchTouchEvent(event);
      //        boolean isDispatch = true;
          System.out.println("HButton---dispatchTouchEvent---isDispatch =" + isDispatch);
          return isDispatch;
      }
      
  • ViewGroup

    ViewGroup 涉及事件分发共涉及三个事件

    • dispatchTouchEvent
    • onInterceptTouchEvent
    • onTouchEvent

      //haio.com.event.widget.HLinearLayout
      
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  System.out.println("HLinearLayout---onTouchEvent---DOWN");
                  break;
              case MotionEvent.ACTION_MOVE:
                  System.out.println("HLinearLayout---onTouchEvent---MOVE");
                  break;
              case MotionEvent.ACTION_UP:
                  System.out.println("HLinearLayout---onTouchEvent---UP");
                  break;
              default:
                  break;
          }
          boolean isTouch = super.onTouchEvent(event);
          System.out.println("HLinearLayout---onTouchEvent---isTouch =" + isTouch);
          return isTouch;
      }
      
      @Override
      public boolean onInterceptTouchEvent(MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  System.out.println("HLinearLayout---onInterceptTouchEvent---DOWN");
                  break;
              case MotionEvent.ACTION_MOVE:
                  System.out.println("HLinearLayout---onInterceptTouchEvent---MOVE");
                  break;
              case MotionEvent.ACTION_UP:
                  System.out.println("HLinearLayout---onInterceptTouchEvent---UP");
                  break;
              default:
                  break;
          }
      
          boolean isIntercept = super.onInterceptTouchEvent(event);
          System.out.println("HLinearLayout---onInterceptTouchEvent---isIntercept =" + isIntercept);
          return isIntercept;
      }
      
      @Override
      public boolean dispatchTouchEvent(MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  System.out.println("HLinearLayout---dispatchTouchEvent---DOWN");
                  break;
              case MotionEvent.ACTION_MOVE:
                  System.out.println("HLinearLayout---dispatchTouchEvent---MOVE");
                  break;
              case MotionEvent.ACTION_UP:
                  System.out.println("HLinearLayout---dispatchTouchEvent---UP");
                  break;
              default:
                  break;
          }
          boolean isDispatch = super.dispatchTouchEvent(event);
      //        boolean isDispatch = true;
          System.out.println("HLinearLayout---dispatchTouchEvent---isDispatch =" + isDispatch);
          return isDispatch;
      }
      

上述代码,细心的小伙伴们会发现,将MainActivity 中dispatchTouchEvent的返回值改为true,并屏蔽掉

super.dispatchTouchEvent(event)

的调用,然后分别点击 Button 和 TextView的日志为:

1.点击按钮:
06-14 17:41:58.639 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---DOWN
06-14 17:41:58.639 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true
06-14 17:41:58.689 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---MOVE
06-14 17:41:58.689 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true
06-14 17:41:58.694 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---UP
06-14 17:41:58.694 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true

2.点击文本:
06-14 17:41:58.694 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true
06-14 17:51:09.684 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---DOWN
06-14 17:51:09.684 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true
06-14 17:51:09.809 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---UP
06-14 17:51:09.809 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true

然后,我们在将MainActivity 中dispatchTouchEvent的返回值改为false,查看日志:

06-14 17:54:23.509 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---DOWN
06-14 17:54:23.509 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false
06-14 17:54:23.524 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---MOVE
06-14 17:54:23.524 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false
06-14 17:54:23.579 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---UP
06-14 17:54:23.579 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false

从上面,我们得知,dispatchTouchEvent返回true 表明当前view/viewGroup负责处理该事件 ,false表明不负责处理该事件, 从日志打印结果中看出return true是不会有任何区别的。

那么,问题来了,Activity是通过调用上面来分发事件的呢?

我们将 MainActivity 中dispatchTouchEvent 改成如下,放开 super.dispatchTouchEvent(event) 的调用,并且返回true,表示 负责处理该事件。

public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("Activity---dispatchTouchEvent---DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("Activity---dispatchTouchEvent---MOVE");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("Activity---dispatchTouchEvent---UP");
            break;
        default:
            break;
    }


    boolean isDispatch = super.dispatchTouchEvent(event);
//        boolean isDispatch = false;
    isDispatch = true;


    System.out.println("Activity---dispatchTouchEvent---isDispatch =" + isDispatch);
    return isDispatch;
}

日志打印为:

06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---isIntercept =false
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HButton---onTouch---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---DOWN
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---isTouch =true
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---MOVE
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---MOVE
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---MOVE
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---isIntercept =false
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---MOVE
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HButton---onTouch---MOVE
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---MOVE
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---isTouch =true
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.234 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---UP
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---UP
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---UP
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---isIntercept =false
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---UP
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HButton---onTouch---UP
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---UP
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---isTouch =true
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.239 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.249 17504-17504/haio.com.event I/System.out: HButton---onClick---DOWN

事件分发了,这说明:

dispatchTouchEvent返回true 表明当前view/viewGroup负责处理该事件 false表明不负责处理该事件 在MainActivity中 如果你把return值改为false 打印结果和return true是不会有任何区别的 因为你根本就没有向下进行分发 MainActivity中分发是调用super.dispatchTouchEvent的。

注意,一个事件分发的完整过程:

06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---isIntercept =false
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HButton---onTouch---DOWN
06-14 17:58:12.159 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---DOWN
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---isTouch =true
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---isDispatch =true
06-14 17:58:12.169 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =true

后面三行打印,说明HButton、HLinearLayout、Activity都负责此事件,因此后续事件还会分发到HButton、HLinearLayout、Activity上。

如果 子View不消耗事件,则回溯给Activity进行执行

当你把HButton中的dispatchTouchEvent返回值改变为false的时候 说明没有调用View.onTouchEvent来消费事件,日志如下:

06-14 18:09:10.934 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---DOWN
06-14 18:09:10.959 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---DOWN
06-14 18:09:10.979 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---DOWN
06-14 18:09:10.979 17504-17504/haio.com.event I/System.out: HLinearLayout---onInterceptTouchEvent---isIntercept =false
06-14 18:09:10.979 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---DOWN
06-14 18:09:10.979 17504-17504/haio.com.event I/System.out: HButton---onTouch---DOWN
06-14 18:09:10.979 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---DOWN
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---isTouch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---isDispatch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HLinearLayout---onTouchEvent---DOWN
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HLinearLayout---onTouchEvent---isTouch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---isDispatch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---DOWN
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---isTouch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false
06-14 18:09:11.014 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---MOVE
06-14 18:09:11.044 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---MOVE
06-14 18:09:11.044 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---isTouch =false
06-14 18:09:11.044 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false
06-14 18:09:11.044 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---UP
06-14 18:09:11.049 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---UP
06-14 18:09:11.049 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---isTouch =false
06-14 18:09:11.049 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false

如上,当你把HButton中的dispatchTouchEvent返回值改变为false的时候 说明没有调用View.onTouchEvent来消费事件, 所以返回之后最终MainActivity的onTouchEvent还是会对Event进行处理, 假如某层dispatchTouchEvent返回值为false,那么后续的move up等等action就不会再被分发到当前层了.

注意,针对以上,下面日志反应一个事件的分发过程

06-14 18:09:10.979 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---DOWN
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HButton---onTouchEvent---isTouch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HButton---dispatchTouchEvent---isDispatch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HLinearLayout---onTouchEvent---DOWN
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HLinearLayout---onTouchEvent---isTouch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: HLinearLayout---dispatchTouchEvent---isDispatch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---DOWN
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---isTouch =false
06-14 18:09:10.994 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false

打印说明HButton、HLinearLayout都不负责此事件(返回false),因此后续事件就不会分发到HButton、HLinearLayout上。

06-14 18:09:11.044 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---UP
06-14 18:09:11.049 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---UP
06-14 18:09:11.049 17504-17504/haio.com.event I/System.out: Activity---onTouchEvent---isTouch =false
06-14 18:09:11.049 17504-17504/haio.com.event I/System.out: Activity---dispatchTouchEvent---isDispatch =false
坚持原创技术分享,您的支持将鼓励我继续创作!