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的dispatchTouchEvent与onTouchEvent方法。
我们看一下运行效果,点击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类(源码目录:...\sdk\sources\android-23\com\android\internal\policy\)。
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行代码,调用mWindow的shouldCloseOnTouch方法,如果此方法返回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方法究竟做了什么事情。反过来你能更好理解本篇中的某些点。