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类(源码目录:...\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行代码,调用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方法究竟做了什么事情。反过来你能更好理解本篇中的某些点。

  


 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM