Framework源码面试六部曲:4.事件传递机制

Posted 初一十五啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Framework源码面试六部曲:4.事件传递机制相关的知识,希望对你有一定的参考价值。

前言

今天在电脑上翻出了很久之前整理笔记Framework源码面试,Flutter,以及一部分面试专题。拿出来温习一下。
今天先讲Framework源码篇

1.Framework源码面试:1.Activity启动流程
2.Framework源码面试:2.Binder面试
3.Framework源码面试:3.Handler面试
4.Framework源码面试:4.事件分发机制
5.Framework源码面试:5.onMeasure测量原理
6.Framework源码面试:6.android屏幕刷新机制

在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。

废话不多说,总结一句:面试时事件分发机制很重要。

1.1 Android 事件原理和 事件分发流

先弄清楚Android 事件原理后 我们再来看怎么面试

关于Android 事件分发机制网上的博文很多,但是很多都是写个Demo然后贴一下输出的Log或者拿源码分析,然后一堆的注释和说明,如果用心的去看肯定是收获不少但是确实很难把整个流程说清和记住。曾经也是拼命想记住整个流程,但是一段时间又忘了,最后觉得分析这种问题和事件流的走向,一张图来解释和说明会清晰很多,下面我根据画的一张事件分发流程图,说明的事件从用户点击之后,在不同函数不同返回值的情况的最终走向。

注:

  • 仔细看的话,图分为3层,从上往下依次是ActivityViewGroupView

  • 事件从左上角那个白色箭头开始,由ActivitydispatchTouchEvent做分发

  • 箭头的上面字代表方法返回值,(return truereturn falsereturn super.xxxxx(),super
    的意思是调用父类实现。

  • dispatchTouchEvent
    onTouchEvent的框里有个**【true---->消费】**的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。

  • 目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。

  • 之前图中的ActivitydispatchTouchEvent 有误(图已修复),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

仔细看整个图,我们得出事件流 走向的几个结论(希望读者专心的看下图 1,多看几遍,脑子有比较清晰的概念。)
1、如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。

所以如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity---->ViewGroup—>View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View—>ViewGroup—>Activity从下往上调用onTouchEvent方法。

2、dispatchTouchEventonTouchEvent 一旦return true,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。看下图中只要return true事件就没再继续传下去了,对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。

3、dispatchTouchEventonTouchEvent return false的时候事件都回传给父控件的onTouchEvent处理。

看上图深蓝色的线,对于返回false的情况,事件都是传给父控件onTouchEvent处理。

  • 对于dispatchTouchEvent 返回 false 的含义应该是:事件停止往子View传递和分发同时开始往父控件回溯(父控件的onTouchEvent开始从下往上回传直到某个onTouchEvent return true),事件分发机制就像递归,return false 的意义就是递归停止然后开始回溯。
  • 对于onTouchEvent return false 就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动。

4、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent ViewGroupView的这些方法的默认实现就是会让整个事件安装U型完整走完,所以 return super.xxxxxx() 就会让事件依照U型的方向的完整走完整个事件流动路径),中间不做任何改动,不回溯、不终止,每个环节都走到。

所以如果看到方法return super.xxxxx() 那么事件的下一个流向就是走U型下一个目标,稍微记住上面这张图,你就能很快判断出下一个走向是哪个控件的哪个函数。

5、onInterceptTouchEvent 的作用

Intercept 的意思就拦截,每个ViewGroup每次在做分发的时候,问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理)如果要自己处理那就在onInterceptTouchEvent方法中 return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。**默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()return false是一样的,是不会拦截的,事件会继续往子ViewdispatchTouchEvent传递。

6、ViewGroupViewdispatchTouchEvent方法返回super.dispatchTouchEvent()的时候事件流走向。

首先看下ViewGroupdispatchTouchEvent,之前说的return true是终结传递。return false 是回溯到父View的onTouchEvent

然后ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢,return truefalse 都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent

记住这一点。

那么对于ViewdispatchTouchEvent return super.dispatchTouchEvent()的时候呢事件会传到哪里呢,很遗憾View没有拦截器。但是同样的道理return true是终结。return false 是回溯会父类的onTouchEvent,怎样把事件分发给自己的onTouchEvent 处理呢,那只能return super.dispatchTouchEvent,View类的dispatchTouchEvent()方法默认实现就是能帮你调用View自己的onTouchEvent方法的

1.2 如何面试,以及面试官会出现什么考点

1.2.1、为什么会有事件分发机制?

我们知道,android的布局结构是树形结构,这就会导致一些View可能会重叠在一起,当我们手指点击的地方在很多个布局范围之内,也就是说此时有好多个布局可以响应我们的点击事件,这个时候该让哪个view来响应我们的点击事件呢?这就是事件分发机制存在的意义。

1.2.2、ViewGroup的事件分发涉及到哪些过程和方法?

  1. public boolean dispatchTouchEvent(MotionEvent ev)
    是事件分发机制中的核心,所有的事件调度都归它管
    用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用
  2. public boolean onInterceptTouchEvent(MotionEvent ev)
    dispatchTouchEvent中调用,用来判断是否拦截某个事件,返回结果表示是否拦截当前事件
  3. public boolean onTouchEvent(MotionEvent event)
    dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消耗当前事件
1.2.3、View中为什么会有dispatchTouchEvent方法,它存在的意义是什么?

我们知道View可以注册很多监听事件(下文有详细),比如,触摸事件,单击事件,长按事件等,而且view也有自己的onTouchEvent方法,那么这么多事件应该由谁来调度管理呢?这就是是View中dispatchTouchEvent方法存在的意义。

1.2.4、View中为什么没有onInterceptTouchEvent事件拦截方法?

View最为事件传递的最末端,要么消费掉事件,要么不处理进行回传,根本没必要进行事件拦截

1.2.5、用伪代码表示ViewGroup的事件分发过程并解释?
public boolean dispatchTouchEvent(MotionEvent ev) 
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) 
        consume = onTouchEvent(ev);
     else 
        consume = child.dispatchTouchEvent(ev);
    
    return consume;

  1. 对于一个ViewGroup来说,点击事件产生后,首先会传递给它,这时她的dispatchTouchEvent会被调用,如果这个ViewGroup的onInterceptTouchEvent
  2. 方法返回true表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent就会被调用;如果这个这个ViewGroup的onInterceptTouchEvent
  3. 方法返回false就表示它不拦截当前事件,这时事件就会传递给子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件最终被处理。
1.2.6、简述事件传递的流程
  • 事件都是从Activity.dispatchTouchEvent()开始传递
  • 一个事件发生后,首先传递给Activity,然后一层一层往下传,从上往下调用dispatchTouchEvent方法传递事件:
    activity --> ~~ --> ViewGroup --> View
  • 如果事件传递给最下层的View还没有被消费,就会按照反方向回传给Activity,从下往上调用onTouchEvent方法,最后会到Activity的onTouchEvent()函数,如果Activity也没有消费处理事件,这个事件就会被抛弃:
    View --> ViewGroup --> ~~ --> Activity
  • dispatchTouchEvent方法用于事件的分发,Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发,事件没有被消费。返回false则继续往下分发,如果是ViewGroup则分发给onInterceptTouchEvent进行判断是否拦截该事件。
  • onTouchEvent方法用于事件的处理,返回true表示消费处理当前事件,返回false则不处理,交给子控件进行继续分发。
  • onInterceptTouchEvent是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理。返回false则不拦截,继续往下传。这是ViewGroup特有的方法,因为ViewGroup中可能还有子View,而在Android中View中是不能再包含子View的
  • 上层View既可以直接拦截该事件,自己处理,也可以先询问(分发给)子View,如果子View需要就交给子View处理,如果子View不需要还能继续交给上层View处理。既保证了事件的有序性,又非常的灵活。
  • 事件由父View传递给子View,ViewGroup可以通过onInterceptTouchEvent()方法对事件拦截,停止其向子view传递
  • 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来,也就是说ACTION_DOWN必须返回true,之后的事件才会传递进来
1.2.7、ViewGroup 和 View 同时注册了事件监听器(onClick等),哪个会执行?

事件优先给View,会被View消费掉,ViewGroup 不会响应。

1.2.8、当俩个或多个View重叠时,事件该如何分配?

当 View 重叠时,一般会分配给显示在最上面的 View,也就是后加载的View。

1.2.9、dispatchTouchEvent每次都会被调用吗?

是的,onInterceptTouchEvent则不会。

1.2.10、一旦有事件传递给view,view的onTouchEvent一定会被调用吗?

View没有onInterceptTouchEvent方法,一旦有事件传递给它,他的onTouchEvent就一定会被调用。

1.2.11、ViewGroup 默认拦截事件吗?

ViewGroup默认不拦截任何事件;看源码可以知道ViewGroup的onInterceptTouchEvent方法中只有一行代码:return false;

1.2.12、事件分为几个步骤?

down事件开头,up事件结尾,中间可能会有数目不定的move事件。

1.3 View事件的优先级会考哪些内容

1.3.1、基于监听的事件分发有哪些?怎么来设置监听?

我们常用的setOnClickListenerOnLongClickListenersetOnTouchListener等都是基于监听的事件处理。
设置监听可以用如下几种方式:

  1. 匿名内部类:

     view.setOnClickListener(new View.OnClickListener() 
         @Override
         public void onClick(View v) 
    
         
     );
    
  2. 内部类:

     view.setOnClickListener(new MyClickListener());
        class MyClickListener implements View.OnClickListener
        
            @Override
           public void onClick(View v) 
    
          
        
    
  3. 外部类:

     view.setOnClickListener(new MyClickListener());
     public class MyClickListener implements View.OnClickListener 
        @Override
         public void onClick(View v) 
    
        
     
    
  4. Activity实现OnClickLister接口的方式

     public class TestViewActivity extends AppCompatActivity implements View.OnClickListener 
       MyView view;
      @Override
      protected void onCreate(Bundle savedInstanceState) 
          super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test_view);
            view = (MyView) findViewById(R.id.view);
            view.setOnClickListener(this);
         
    
      @Override
      public void onClick(View v) 
       
     
    
  5. 在xml中绑定的方式:

     public class TestViewActivity extends AppCompatActivity
         MyView view;
         @Override
         protected void onCreate(Bundle savedInstanceState) 
            super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_test_view);
           view = (MyView) findViewById(R.id.view);
         
         public void MyClick(View view)
    
         
     
     <com.art.chapter_3.MyView
       android:id="@+id/view"
       android:layout_width="100dip"
       android:layout_height="100dip"
       android:background="@color/colorPrimaryDark"
     android:onClick="MyClick"/>
    
1.3.2、view的onTouchEvent,OnClickListerner和OnTouchListener的onTouch方法 三者优先级如何?

代码验证:

自定义view:
public class MyView extends View 
  @Override
  public boolean onTouchEvent(MotionEvent event) 
       Log.i("--------","MyView onTouchEvent "+MyAction.getActionType(event));
        return super.onTouchEvent(event);
  


监听:
    yelloe.setOnTouchListener(new View.OnTouchListener() 
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) 
            Log.i("--------", "touch  yelloe  " + MyAction.getActionType(motionEvent));
            return false;
        
    );

    yelloe.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View view) 
            Log.i("--------", "click  yelloe  ");
        
    );
输出结果: 插图 优先级高低

优先级高低:
onTouchListener >>> onTouchEvent >>> setOnLongClickListener >>> OnClickListerner

1.3.3、如图有三個嵌套的控件,结构如下,其中黄色部分是一个继承于View的控件,绿色和红色都是继承于LinearLayout的控件: 插图:

代码简单如下:

public class MyView extends View 
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        Log.i("--------","MyView onTouchEvent "+MyAction.getActionType(event));
        return super.onTouchEvent(event);
    


public class MyLinearLayoutRed extends LinearLayout 
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        Log.i("--------","MyLinearLayoutRed onTouchEvent "+MyAction.getActionType(event));
        return super.onTouchEvent(event);
    


public class MyLinearLayoutGreen extends LinearLayout 
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        Log.i("--------","MyLinearLayoutRed onTouchEvent "+MyAction.getActionType(event));
        return super.onTouchEvent(event);
    


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <com.example.administrator.myviewevent.MyLinearLayoutRed
        android:id="@+id/red"
        android:layout_width="300dip"
        android:layout_height="300dip"
        android:background="@color/red">
        <com.example.administrator.myviewevent.MyLinearLayoutGreen
            android:id="@+id/green"
            android:layout_width="200dip"
            android:layout_height="200dip"
            android:background="@color/green">
            <com.example.administrator.myviewevent.MyView
                android:id="@+id/yellow"
                android:layout_width="130dip"
                android:layout_height="130dip"
                android:background="@color/yellow" />
        </com.example.administrator.myviewevent.MyLinearLayoutGreen>
    </com.example.administrator.myviewevent.MyLinearLayoutRed>
</FrameLayout>

问题一:如果不在onTouchEvent方法中做任何处理,只是Log输出每一层的Touch事件类型,现在用手指按下在黄色区域并移动后抬起.请问Log输出的结果是什么?

答:

  1. I/--------: MyView onTouchEvent ACTION_DOWN…
  2. I/--------: MyLinearLayoutGreen onTouchEvent ACTION_DOWN…
  3. I/--------: MyLinearLayoutRed onTouchEvent ACTION_DOWN…

问题二:如果不在onTouchEvent方法和setOnTouchListener的onTouch方法中做任何处理,只是Log输出每一层的Touch事件类型,现在用手指按下在黄色区域并移动后抬起.请问Log输出的结果是什么?
在Activity中增加setOnTouchListener监听

    yelloe.setOnTouchListener(new View.OnTouchListener() 
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) 
            Log.i("--------", "touch  yelloe  " + MyAction.getActionType(motionEvent));
            return false;
        
    );
    green.setOnTouchListener(new View.OnTouchListener() 
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) 
            Log.i("--------", "touch  green  " + MyAction.getActionType(motionEvent));
            return false;
        
    );

    red.setOnTouchListener(new View.OnTouchListener() 
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) 
            Log.i("--------", "touch  red  " + MyAction.getActionType(motionEvent));
            return false;
        
    );

答:

I/--------: touch yelloe ACTION_DOWN…
I/--------: MyView onTouchEvent ACTION_DOWN…
I/--------: touch green ACTION_DOWN…
I/--------: MyLinearLayoutGreen onTouchEvent ACTION_DOWN…
I/--------: touch red ACTION_DOWN…
I/--------: MyLinearLayoutRed onTouchEvent ACTION_DOWN…

1.3.4、setOnTouchListener中onTouch的返回值表示什么意思?

onTouch方法返回true表示事件被消耗掉了,不会继续传递了,此时获取不到到OnClickonLongClick事件;onTouch方法返回false表示事件没有被消耗,可以继续传递,此时,可以获取到OnClickonLongClick事件;
同理 onTouchEventsetOnLongClickListener 方法中的返回值表示的意义一样;

1.3.5、setOnLongClickListener的onLongClick的返回值表示什么?

返回false,长按的话会同时执行onLongClickonClick;如果setOnLongClickListener返回true,表示事件被消耗,不会继续传递,只执行longClick

1.3.6、onTouch和onTouchEvent的异同?
  • onTouch方法是View的 OnTouchListener接口中定义的方法。当一个View绑定了OnTouchLister后,当有touch事件触发时,就会调用onTouch方法。(当把手放到View上后,onTouch方法被一遍一遍地被调用)
  • onTouchEvent方法是overrideActivity的方法。重新了ActivityonTouchEvent方法后,当屏幕有touch事件时,此方法就会被调用。
  • onTouch优先于onTouchEvent执行,如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
  • 相同点是它们都是在在View的dispatchTouchEvent中调用的;
1.3.7、点击事件的传递过程?

Activity-Window-View
从上到下依次传递,当然了如果你最低的那个view onTouchEvent返回false 那就说明他不想处理 那就再往上抛,都不处理的话最终就还是让Activity自己处理了。

1.3.8、如果某个view 处理事件的时候 没有消耗down事件 会有什么结果?

假如一个view,在down事件来的时候 他的onTouchEvent返回false, 那么这个down事件 所属的事件序列 就是他后续的move 和up 都不会给他处理了,全部都给他的父view处理。

1.3.9、如果view 不消耗move或者up事件 会有什么结果?

那这个事件所属的事件序列就消失了,父view也不会处理的,最终都给activity 去处理了。

1.3.10、enable是否影响view的onTouchEvent返回值?

不影响,只要clickablelongClickable有一个为真,那么onTouchEvent就返回true。

以上是关于Framework源码面试六部曲:4.事件传递机制的主要内容,如果未能解决你的问题,请参考以下文章

Framework源码面试六部曲:6.Android屏幕刷新机制

Framework源码面试六部曲:3.Handler面试集合

Framework源码面试六部曲:5.onMeasure测量原理

Framework源码面试六部曲:1.activity启动流程

JDBC六部曲-2(进阶)

Framework大合集:从里到外分析的明明白白