Android事件传递机制详解及最新源码分析——Activity篇

版权声明:本文出自汪磊的博客,转载请务必注明出处。

在前两篇我们共同探讨了事件传递机制《View篇》《ViewGroup篇》,我们知道View触摸事件是ViewGroup传递过去的,比如一个很简单的布局最外层是LinearLayout,里面就一个Button,我们点击Button的时候触摸事件是由外层LinearLayout传递给里面Button的,但是有没有想过当前触摸事件是谁传递给外层的LinearLayout的呢?带着这个疑问我们继续来共同探讨一下。

从Demo示例说起

我们还是先写一个简单的demo,很简单,代码如下:自定义Button:

 1 public class MyButton extends Button {
 2 
 3     private final String TAG = "WL";
 4     
 5     public MyButton(Context context, AttributeSet attrs) {
 6         super(context, attrs);
 7     }
 8 
 9     @Override
10     public boolean dispatchTouchEvent(MotionEvent ev) {
11         //
12         Log.i(TAG, "MyButton_dispatchTouchEvent_Action:"+ev.getAction());
13         return super.dispatchTouchEvent(ev);
14     }
15     
16     @Override
17     public boolean onTouchEvent(MotionEvent event) {
18         //
19         Log.i(TAG, "MyButton_onTouchEvent_Action:"+event.getAction());
20         return super.onTouchEvent(event);
21     }
22 }

自定义LinearLayout:

 1 public class MyLinearLayout extends LinearLayout {
 2     
 3     private final String TAG = "WL";
 4 
 5     public MyLinearLayout(Context context, AttributeSet attrs) {
 6         super(context, attrs);
 7         //
 8     }
 9     
10     @Override
11     public boolean dispatchTouchEvent(MotionEvent ev) {
12         //
13         Log.i(TAG, "MyLinearLayout_dispatchTouchEvent_Action:"+ev.getAction());
14         return super.dispatchTouchEvent(ev);
15     }
16     
17     @Override
18     public boolean onTouchEvent(MotionEvent event) {
19         //
20         Log.i(TAG, "MyLinearLayout_onTouchEvent_Action:"+event.getAction());
21         return super.onTouchEvent(event);
22     }
23 }

布局文件:

 1 <com.wl.activitydispatchtouchevent.MyLinearLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:layout_width="match_parent"
 5     android:layout_height="match_parent"
 6     android:background="#0099cc"
 7     android:id="@+id/mylinearlayout"
 8     android:gravity="center"
 9     tools:context="com.wl.activitydispatchtouchevent.MainActivity" >
10 
11     <com.wl.activitydispatchtouchevent.MyButton
12         android:id="@+id/mybutton"
13         android:layout_width="wrap_content"
14         android:layout_height="wrap_content"
15         android:textSize="20sp"
16         android:text="WL_Button" />
17 
18 </com.wl.activitydispatchtouchevent.MyLinearLayout>

Activity中代码:

 1 public class MainActivity extends Activity implements OnClickListener,
 2         OnTouchListener {
 3 
 4     private final String TAG = "WL";
 5 
 6     @Override
 7     protected void onCreate(Bundle savedInstanceState) {
 8         super.onCreate(savedInstanceState);
 9         setContentView(R.layout.activity_fullscreen);
10         //
11         findViewById(R.id.mybutton).setOnClickListener(this);
12         findViewById(R.id.mybutton).setOnTouchListener(this);
13         //
14         findViewById(R.id.mylinearlayout).setOnClickListener(this);
15         findViewById(R.id.mylinearlayout).setOnTouchListener(this);
16     }
17 
18     @Override
19     public boolean onTouch(View v, MotionEvent event) {
20         //
21         Log.i(TAG, "onTouch___v:" + v + "___action:" + event.getAction());
22         return false;
23     }
24 
25     @Override
26     public void onClick(View v) {
27         //
28         Log.i(TAG, "onClick___v:" + v);
29     }
30 
31     @Override
32     public boolean dispatchTouchEvent(MotionEvent ev) {
33         Log.i(TAG, "MainActivity__dispatchTouchEvent__action:" + ev.getAction());
34         return super.dispatchTouchEvent(ev);
35     }
36 
37     @Override
38     public boolean onTouchEvent(MotionEvent event) {
39         Log.i(TAG, "MainActivity___onTouchEvent___action=" + event.getAction());
40         return super.onTouchEvent(event);
41     }
42 }

怎么样,很简单吧。和上一篇讲解ViewGroup传递机制的Demo几乎差不多,主要差别就是在Activity中我们重写了Activity的dispatchTouchEventonTouchEvent方法。

我们看一下运行效果,点击Button,打印信息如下:

 1 MainActivity__dispatchTouchEvent__action:0
 2 MyLinearLayout_dispatchTouchEvent_Action:0
 3 MyButton_dispatchTouchEvent_Action:0
 4 onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:0
 5 MyButton_onTouchEvent_Action:0
 6 MainActivity__dispatchTouchEvent__action:1
 7 MyLinearLayout_dispatchTouchEvent_Action:1
 8 MyButton_dispatchTouchEvent_Action:1
 9 onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:1
10 MyButton_onTouchEvent_Action:1
11 onClick___v:com.wl.activitydispatchtouchevent.MyButton

除去与Activity有关的信息,其余信息打印顺序相信你应该轻松理解了。我们看到触摸事件实现传递到Activity中的,其次才传递到MyLinearLayout,最后传递给MyButton。是不是触摸事件就是Activity先获取到接下来才继续向下传递的呢?别急着下结论,我们看看Activity中dispatchTouchEvent都做了什么。

Activity事件传递机制源码分析(源码版本为API23

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     }

是不是爽歪歪?这么短,我们看2-4行代码,首先判断如果是ACTION_DOWN事件则执行onUserInteraction()方法,对于onUserInteraction()方法这里不做具体分析,不是本篇重点。

我们继续向下分析,5-9代码,如果if条件成立则直接返回true,不成立则dispatchTouchEvent最终返回值由onTouchEvent决定,那么if判断就是关键了。

5行代码,getWindow()返回mWindow对象,在Activity的attach方法中进行的初始化,如下:

 1     final void attach(Context context, ActivityThread aThread,
 2             Instrumentation instr, IBinder token, int ident,
 3             Application application, Intent intent, ActivityInfo info,
 4             CharSequence title, Activity parent, String id,
 5             NonConfigurationInstances lastNonConfigurationInstances,
 6             Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
 7         
 8         ...........
 9         mWindow = new PhoneWindow(this);
10         mWindow.setCallback(this);
11         ...........
12 }
mWindow其实就是PhoneWindow对象,接下来我们找到PhoneWindow类(源码目录:...sdksourcesandroid-23comandroidinternalpolicy)。
PhoneWindow类继承自Window类,我们先看看父类中superDispatchTouchEvent是怎么处理的。
Window类中superDispatchTouchEvent源码如下:
1  /**
2      * Used by custom windows, such as Dialog, to pass the touch screen event
3      * further down the view hierarchy. Application developers should
4      * not need to implement or call this.
5      *
6      */
7 public abstract boolean superDispatchTouchEvent(MotionEvent event);

看到了吧,很简单,父类中就是一个抽象方法, 看注释就知道此方法主要用来屏幕事件传递的,开发者不需要实现或者调用这个方法。

接下来我们看看PhoneWindow类中的superDispatchTouchEvent方法:

1  @Override
2     public boolean superDispatchTouchEvent(MotionEvent event) {
3         return mDecor.superDispatchTouchEvent(event);
4   }

是不是更简单?直接调用mDecor的superDispatchTouchEvent方法,mDecor是什么玩意呢?这里就直说说了,mDecor是DecorView的实例。

DecorView类是PhoneWindow类的内部类,源码如下:

1 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
2 
3    ..........
4    public boolean superDispatchTouchEvent(MotionEvent event) {
5          return super.dispatchTouchEvent(event);
6    }
7     
8    ..........
9 }

我勒个去,搞半天DecorView 继承自FrameLayout,我们知道 FrameLayout继承自ViewGroup,最终就是调用ViewGroup中的dispatchTouchEvent方法进行事件分发。

但是到这里我们还有一个疑问,以我们Demo为例,通过上述分析事件先传递到Activity的dispatchTouchEvent方法,然后调用DecorView 的superDispatchTouchEvent方法最终调用的ViewGroup的dispatchTouchEvent方法,但是跟我们Demo中的MyLinearLayout有什么关系呢?或者说是怎么传递到MyLinearLayout的呢?

要解答这个疑问我们就必须熟知我们平时调用Activity中的setContentView方法设置布局的时候我们自己的布局到底是怎么挂载到Activity上的,这篇我们就不进入深入源码解析了,不是本篇重点,直说一些结论性东西。后续会单独写一篇文章专门分析setContentView究竟都做了什么。

我们在调用setContentView设置布局的时候其实都是被放置在id为content的FrameLayout 布局中的,注意id为content的FrameLayout 布局并不是上面讲的DecorView,具体层级关系如下:

看到了吧,id为content的FrameLayout 布局DecorView的子View布局。我们自己的布局最后总会替换掉id为content的FrameLayout

到这里你该明白了吧,Activity将触摸事件经过层层传递给DecorView, DecorView会调用ViewGroup的dispatchTouchEvent方法将事件传递给子View。之后的逻辑就是我们上两篇所讲的内容了。

接下来我们回看Activity中dispatchTouchEvent方法,第5行根据我们上述分析的,如果最终找到子View消耗事件则返回值为true,进而整个方法返回true。如果没有子View处理当前触摸事件则返回false,执行Activity中onTouchEvent方法。

我们接下来分析一下Activity中onTouchEvent方法,源码如下;

 1 /**
 2      * Called when a touch screen event was not handled by any of the views
 3      * under it.  This is most useful to process touch events that happen
 4      * outside of your window bounds, where there is no view to receive it.
 5      *
 6      * @param event The touch screen event being processed.
 7      *
 8      * @return Return true if you have consumed the event, false if you haven't.
 9      * The default implementation always returns false.
10      */
11     public boolean onTouchEvent(MotionEvent event) {
12         if (mWindow.shouldCloseOnTouch(this, event)) {
13             finish();
14             return true;
15         }
16 
17         return false;
18     }

 逻辑也不复杂,主要就是第12行代码,调用mWindowshouldCloseOnTouch方法,如果此方法返回true则整个方法返回true,反之则返回false。

mWindow上面我们分析过就是PhoneWindow的实例,好了我们就去PhoneWindow类中找shouldCloseOnTouch方法看一下吧,然而PhoneWindow中并没有这个方法,那我们看看父类Window中有没有这个方法呢,果然这个方法在其父类中找到,源码如下:

1 public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
2         if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
3                 && isOutOfBounds(context, event) && peekDecorView() != null) {
4             return true;
5         }
6         return false;
7   }

这里主要逻辑也是一个判断,判断mCloseOnTouchOutside标记以及是否为ACTION_DOWN事件,然后判断点击事件的坐标x,y有没有超出边界,最后调用peekDecorView()判断是否为空。peekDecorView()在Window类中就是一个抽象方法,具体实现在PhoneWindow类中如下:

1     public final View peekDecorView() {
2         return mDecor;
3     }

很简单,就是返回mDecor,上面我们分析过mDecor就是DecorView的实例,这里我们需要知道我们在Activity中调用setContentView的时候mDecor就会初始化,具体分析会在下一篇文章中,这里只要知道mDecor不为null就可以了。

其余的都不难理解但是mCloseOnTouchOutside 是个什么鬼呢?我们知道Activity设置成Dialog样式的时候默认点击外部的时候是会关闭的,同样我们也可以调用setFinishOnTouchOutside(false)设置为点击外部时候Activity不关闭,mCloseOnTouchOutside 就是用来记录这个的,如果我们将Activity设置为Dialog样式mCloseOnTouchOutside 默认就被设置为true,我们知道大部分情况下Activity是不会设置为Dialog样式的,所以mCloseOnTouchOutside 默认为false。(关于mCloseOnTouchOutside其实是想从源码角度分析一下的,但是这部分内容实在和传递机制不沾边,就这部分有一个判断,所以就不仔细分析了,在下一篇分析setContentView的时候在提一下吧 )

这里我们稍微总结一下:mCloseOnTouchOutside 默认情况下是false,如果Activity样式设置为Dialog系统默认会将mCloseOnTouchOutside 设置为true,所以Dialog样式的Activity默认情况下点击外部会关闭,如果我们调用setFinishOnTouchOutside(false)或者在styles文件中设置了 <item name="android:windowCloseOnTouchOutside">false</item> 那么最终都会将mCloseOnTouchOutside 变量置为false,点击Activity外部也就不会关闭了。

综上分析,Window中shouldCloseOnTouch大多数情况下是返回false的,从而Activity中onTouchEvent大多说情况下也是返回false,除非我们进行了特殊设置。这也就是Activity中onTouchEvent注释是The default implementation always returns false而不是The default implementation returns false,就多了一个always。

好了,到此为止关于安卓事件传递机制最重要的部分都已经讲解完毕,最最核心的还是要掌握View以及ViewGroup的部分,至于Activity的传递大体了解一下流程就可以了。

下一篇我们一起探究一下Activity中setContentView方法究竟做了什么事情。反过来你能更好理解本篇中的某些点。

  


原文地址:https://www.cnblogs.com/leipDao/p/7483822.html