Android应用开发之从源码的角度分析Android的事件分发机制的具体实现
凌雪 2018-09-21 来源 :网络 阅读 582 评论 0

摘要:本文将带你了解Android应用开发之从源码的角度分析Android的事件分发机制的具体实现,希望本文对大家学Android有所帮助。

本文将带你了解Android应用开发之从源码的角度分析Android的事件分发机制的具体实现,希望本文对大家学Android有所帮助。


今天,我们从源码的角度来分析Android的事件分发机制的具体实现。
1、Activity的事件处理机制
Android的触屏事件首先是从Activity的dispatchTouch   Event开始分发的,所以我们先来看看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这个函数,这个方法在Activity里的实现是空的,用户在继承Activity时可以重写该方法。往下看到getWindow这个方法:
?123public Window getWindow() { return   mWindow;}
返回的是一个mWindow的Window对象,而这个mWindow实际上是一个PhoneWindow对象,于是就变成了调用PhoneWindow类的superDispatchTouchEvent,继续看源码:
?123public boolean   superDispatchTouchEvent(MotionEvent event) { return   mDecor.superDispatchTouchEvent(event);}
这里又跑出了一个mDecor对象,它是DecorView的对象,这里简单提一下Activity的组成,Activity实际是有Window来管理视图的,而这个Window一般由PhoneWindow实现,但PhoneWindow实际并不程序界面效果,这个操作就由DecorView来实现。DecorView继承自Framelayout,是整个视图的根,如下图所示:
   
    所以,上面mDecor.superDispatchTouchEvent最终调的方法是ViewGroup的dispatchTouchEvent这个方法。
2、ViewGroup的事件处理流程
    我们接下来看ViewGroup的源码,由于ViewGroup源码较长,我们挑关键部分解析:
if (actionMasked ==   MotionEvent.ACTION_DOWN) { // Throw away all previous state when   starting a new touch gesture. // The framework may have dropped the up   or cancel event for the previous gesture // due to an app switch, ANR,   or some other state   change. cancelAndClearTouchTargets(ev); resetTouchState();}
这一段代码主要是在检测到ACTION_DOWN事件做一些标记清除和初始化事件
?1 
    final boolean intercepted;if (actionMasked ==   MotionEvent.ACTION_DOWN  || mFirstTouchTarget != null) { final   boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) !=   0; if (!disallowIntercept) {  intercepted =   onInterceptTouchEvent(ev);  ev.setAction(action); // restore action   in case it was changed } else {  intercepted = false; }}   else { // There are no touch targets and this action is not an initial   down // so this view group continues to intercept   touches. intercepted = true;}
    从上面代码我们可以看到,当事件类型为ACTION_DOWN或者mFirstTouchTarget不为空时,ViewGroup会调用onInterceptTouchEvent来判断当前事件是否要拦截,ACTION_DOWN我们很熟悉了,那么mFirstTouchTarget是什么呢?在ViewGroup的子元素成功处理事件时,mFirstTouchTarget会被赋值并指向子元素,回头看初始化那一段代码,cancelAndClearTouchTargets实际上就是清除mFirstTouchTarget所指向的元素。从这一段代码可以看到这两种情况:当事件类型为ACTION_DOWN时,VIewGroup肯定会进入拦截判断;而在后续的如果有子元素处理了即消费了事件,那么这个mFirstTouchTarget不为空,那么ViewGroup也会进入拦截判断。这样印证了我们上一节中如果是ViewGroup自己消费了事件,后续的Move和up事件是不会回调onInterceptTouchEvent的。所以如果我们想提前处理所有的点击事件,一般使用dispatchTouchEvent而不是onInterceptTouchEvent。
    接着往下看:
final   View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--)   { final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i,   customOrder); final View child =   getAndVerifyPreorderedView(preorderedList, children,   childIndex);  // If there is a view that has accessibility focus we   want it // to get the event first and if not handled we will perform   a // normal dispatch. We may do a double iteration but this is //   safer given the timeframe. if (childWithAccessibilityFocus != null)   {  if (childWithAccessibilityFocus != child)   {continue;  }  childWithAccessibilityFocus =   null;  i = childrenCount - 1; }  if   (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y,   child, null))   {  ev.setTargetAccessibilityFocus(false);  continue; }  newTouchTarget   = getTouchTarget(child); if (newTouchTarget != null) {  //   Child is already receiving touch within its bounds.  // Give it the   new pointer in addition to the ones it is   handling.  newTouchTarget.pointerIdBits |=   idBitsToAssign;  break; }  resetCancelNextUpFlag(child); if   (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))   {  // Child wants to receive touch within its   bounds.  mLastTouchDownTime = ev.getDownTime();  if   (preorderedList != null) {// childIndex points into presorted list, find   original indexfor (int j = 0; j < childrenCount; j++) { if   (children[childIndex] == mChildren[j]) {  mLastTouchDownIndex = j;  break; }}  }   else {mLastTouchDownIndex =   childIndex;  }  mLastTouchDownX =   ev.getX();  mLastTouchDownY = ev.getY();  newTouchTarget   = addTouchTarget(child, idBitsToAssign);  alreadyDispatchedToNewTouchTarget   = true;  break; }
当ViewGroup不拦截事件时,ViewGroup会遍历子元素,判断子元素是否能够接收到点击事件。如果子元素View能接收到事件,则调用dispatchTransformedTouchEvent这个方法,我们看看这个方法:
?12345678910if (cancel || oldAction ==   MotionEvent.ACTION_CANCEL)   { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null)   {  handled = super.dispatchTouchEvent(event); } else   {  handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return   handled;}
这个方法实际会调用子元素的dispatchTouchEvent方法,这里就把事件传递给子View了,从而完成了这一轮的事件分。子元素的事件处理我们后面在分析,我们回到上面的代码,可以看到如果这个dispatchTransformedTouchEvent返回true,就会在
?12newTouchTarget = addTouchTarget(child,   idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;
给mFirstTouchTarget赋值,也就是我们前面锁看到的进入拦截判断的那个条件。而假如遍历完整个ViewGroup,mFirstTouchTarget仍为null,由上面代码可知一种情况是子元素的dispatchTouchEvent返回false(即子元素没有消费事件),另一种情况是ViewGroup没有子元素,此时:
?123456// Dispatch to touch targets.if   (mFirstTouchTarget == null) { // No touch targets so treat this as an   ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled,   null,TouchTarget.ALL_POINTER_IDS);}
这里又再一次调用了dispatchTransformedTouchEvent这个方法,只不过第三个参数(参数传的是子元素)设为null,回头看刚刚dispatchTransformedTouchEvent的方法:
?12345if (child == null) {  handled =   super.dispatchTouchEvent(event); } else {  handled =   child.dispatchTouchEvent(event); }
假如子元素不为null才会调子元素的dispatchTouchEvent,而如果子元素为null,则调ViewGroup的super.dispatchTouchEvent自己处理事件。这里的super.dispatchTouchEvent即调的是View的dispatchTouchEvent方法来处理事件(ViewGroup实际也是View,它继承自View)。
3、View的事件处理过程
    接下来我们来看View的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent   event) { ...//省略部分代码  if   (onFilterTouchEventForSecurity(event)) {  if ((mViewFlags &   ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result =   true;  }  //noinspection   SimplifiableIfStatement  ListenerInfo li =   mListenerInfo;  if (li != null && li.mOnTouchListener !=   null && (mViewFlags & ENABLED_MASK) ==   ENABLED && li.mOnTouchListener.onTouch(this, event)) {result =   true;  }   if (!result &&   onTouchEvent(event)) {result = true;  } }  ...//省略部分代码 return result;}
    View的dispatchTouchEvent相对简单一些,从上面的代码可以看到,View在处理事件时,首先会判断有没有设置OnTouchEListener,如果设置了则会先执行onTouch来处理事件,如果onTouch返回true,则不会进入onTouchEvent这个方法,这里正印证了上一篇博客最后面的分析,onTouch先于onTouchEvent执行,且如果onTouch消费了事件,则后面的onTouchEvent不会执行的结论。
    接下来分析View的onTouchEvent的实现:
   
    final boolean clickable = ((viewFlags & CLICKABLE) ==   CLICKABLE  || (viewFlags & LONG_CLICKABLE) ==   LONG_CLICKABLE)  || (viewFlags & CONTEXT_CLICKABLE) ==   CONTEXT_CLICKABLE;
    if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action ==   MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {   setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled   view that is clickable still consumes the touch // events, it just doesn't   respond to them. return clickable;}
    先进来判断View是否是disabled不可用的状态,由上面的代码可知,即使VIew处于不可用的状态,也是会消费事件的,只是不会对事件做出响应(看注释翻译),接着往下看
if (clickable || (viewFlags & TOOLTIP) ==   TOOLTIP) { switch (action) {  case MotionEvent.ACTION_UP:...//省略部分代码boolean prepressed =   (mPrivateFlags & PFLAG_PREPRESSED) != 0;if ((mPrivateFlags &   PFLAG_PRESSED) != 0 || prepressed) { .../  if   (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {  // This   is a tap, so remove the longpress   check  removeLongPressCallback();   // Only perform   take click actions if we were in the pressed state  if   (!focusTaken) {// Use a Runnable and post this rather than calling//   performClick directly. This lets other visual state// of the view update   before click actions start.if (mPerformClick == null) { mPerformClick =   new PerformClick();}if (!post(mPerformClick))   { performClick();}  } } ...//省略部分代码}break; }  return   true;
这里的clickable的赋值过程在上面的代码里已经给出,由此可以知道只要当CLICKABLE或者LONG_CLICKABLE当中一个为true,onTouchEvent就会返回true,即消耗了这个事件,回顾上一章,也印证了这个结论。然后在ACTION_UP触发时,会调用performClick(),如果View设置了OnclickListener,performClick内部就会调用它的onClick方法:
public boolean performClick() { final   boolean result; final ListenerInfo li = mListenerInfo; if (li !=   null && li.mOnClickListener != null)   {  playSoundEffect(SoundEffectConstants.CLICK);  li.mOnClickListener.onClick(this);  result   = true; } else {  result =   false; }  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  notifyEnterOrExitForAutoFillIfNeeded(true);  return   result;}
然后结合View的OnCickListener源码:
public void setOnClickListener(@Nullable   OnClickListener l) { if (!isClickable())   {  setClickable(true); } getListenerInfo().mOnClickListener   = l;}
    public void setClickable(boolean clickable) { setFlags(clickable    CLICKABLE : 0, CLICKABLE);}
    这里把CLICKABLE标志位设置为true,然后给mOnClickListener赋值,结合上面的代码,逻辑就清晰了。    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注移动开发之Android频道!

本文由 @凌雪 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程