前面的博客中,我们通过例子分析了一下Android中事件传递的流程,
详细内容可以参考:Android触摸事件传递机制简要分析

贯穿整个Android的触摸事件分发的流程,基本可以抽象成以下的伪代码:

public boolean dispatchTouchEvent(MotionEvent ev){    boolean handle = false;    if(onInterceptTouchEvent(ev)){        handle = onTouchEvent(ev);    }else{        handle = child.dispatchTouchEvent(ev);    }    return handle;}

如果一个事件传递到了ViewGroup处,首先会判断当前ViewGroup是否要拦截事件,
即调用onInterceptTouchEvent()方法。

如果返回true,则表示ViewGroup拦截事件,
那么ViewGroup就会调用自身的onTouchEvent来处理事件;

如果返回false,表示ViewGroup不拦截事件,
此时事件会分发到它的子View处,即调用子View的dispatchTouchEvent方法。

如此反复直到事件被消耗掉。

本篇博客,我们以Android O的代码为例,看看对应流程的源码。
考虑到事件分发的流程较为繁琐,本篇博客主要针对Activity和ViewGroup部分。


一、Activity部分
系统有一个线程在循环收集屏幕硬件信息。
当用户触摸屏幕时,该线程会把从硬件设备收集到的信息,
封装成一个MotionEvent对象,然后把该对象存放到一个消息队列中。

与此对应,系统的另一个线程循环的读取消息队列中的MotionEvent,然后交给WMS去派发。
WMS把该事件派发给当前处于Active状态的Activity,即处于活动栈最顶端的Activity。

我们就从Activity的dispatchTouchEvent入手,看看整个事件分发的流程:

    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            //空实现,其用途注释已经说的比较清楚了            //若需要了解用户点击界面,可以自行实现该接口            //Implement this method if you wish to know that the user has            //interacted with the device in some way while your activity is running.            onUserInteraction();        }        //首先进行事件分发,事件被消费掉就会返回true        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        //如果事件没有没消费掉,那么就会调用Activity的onTouchEvent        return onTouchEvent(ev);    }

从上面的代码容易看出,Activity收到触摸事件后首先会进行分发;
如果事件在分发的过程中没被消费掉,最终会被Activity的onTouchEvent处理。

在继续分析之前,我们先看看Activity的getWindow函数:

    public Window getWindow() {        //返回Activity持有的mWindow对象        return mWindow;    }

mWindow对象是Activity显示在界面上时创建的,如下所示:

    final void attach(.....) {        ........        //实际上是一个PhoneWindow对象        mWindow = new PhoneWindow(this, window, activityConfigCallback);        ........    }

由上面的代码易知,Activity分发的事件实际上交给了PhoneWindow。
对应分发事件的函数如下:

    public boolean superDispatchTouchEvent(MotionEvent event) {        //mDecor的类型为DecorView,在PhoneWindow初始化时得到        return mDecor.superDispatchTouchEvent(event);    }

DecorView继承FrameLayout,后者继承ViewGroup,
于是上述代码最终会将触摸事件递交给ViewGroup处理。

二、ViewGroup部分
接下来我们跟进ViewGroup的dispatchTouchEvent函数。

2.1 处理Accessibility Focus事件

public boolean dispatchTouchEvent(MotionEvent ev) {    .......    // If the event targets the accessibility focused view and this is it, start    // normal event dispatch. Maybe a descendant is what will handle the click.    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {        ev.setTargetAccessibilityFocus(false);    }    .......

ViewGroup收到触摸事件后,首先需要处理特殊情况,
即携带FLAG_TARGET_ACCESSIBILITY_FOCUS的MotionEvent。
该标志位的含义可以参考注释:

/*** 1、 Private flag indicating that this event was synthesized by the system and*    should be delivered to the accessibility focused view first.* * 2、 When being dispatched such an event is not handled by predecessors of the accessibility* focused view and after the event reaches that view the flag is cleared and* normal event dispatch is performed.* .......* /

从上述注释可以看出,当MotionEvent具有该Flag时,
若点击区域存在accessibility focused view(或ViewGroup),那么事件会优先被递交给这种View处理。
即其父View或ViewGroup,无法消费该MotionEvent(从代码来看,ViewGroup仍可以拦截)。

当accessibility focused view收到该MotionEvent后,就会调用上面的代码,
清除掉FLAG_TARGET_ACCESSIBILITY_FOCUS标签。
此后,MotionEvent就会按照普通的逻辑被分发和消费。


从上面的代码也可以看出,当MotionEvent携带Flag时,
会利用isAccessibilityFocusedViewOrHost函数,判断当前View是否有能力处理accessibility focused motion event。
如果满足条件,将会调用setTargetAccessibilityFocus函数,取消掉FLAG_TARGET_ACCESSIBILITY_FOCUS。

我们看看isAccessibilityFocusedViewOrHost函数:

    //实际上定义在ViewGroup的父类View中    boolean isAccessibilityFocusedViewOrHost() {        //判断View是否具有PFLAG2_ACCESSIBILITY_FOCUSED        //或者利用view root判断当前View是否具有ACCESSIBILITY_FOCUSED能力        return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()                .getAccessibilityFocusedHost() == this);    }

实际上当一个View调用requestAccessibilityFocus后,就会具有accessibility focus:

public boolean requestAccessibilityFocus() {    //ViewManager enable 且 View可视时,就可以申请accessibility focus    AccessibilityManager manager = AccessibilityManager.getInstance(mContext);    if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {        return false;    }    if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {        return false;    }    if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) == 0) {        //置标志位        mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_FOCUSED;        //在ViewRoot中记录        ViewRootImpl viewRootImpl = getViewRootImpl();        if (viewRootImpl != null) {            viewRootImpl.setAccessibilityFocus(this, null);        }        //刷新        invalidate();        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);        return true;    }    return false;}

2.2 过滤事件
处理完特殊事件后, 需要进行安全性检测,如下代码所示:

//用于记录事件最终是否被处理boolean handled = false;//满足条件的事件才能被继续处理if (onFilterTouchEventForSecurity(ev)) {    .....

onFilterTouchEventForSecurity实际上也定义于ViewGroup的父类View中:

    public boolean onFilterTouchEventForSecurity(MotionEvent event) {        //判断View是否具有FILTER_TOUCHES_WHEN_OBSCURED        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0                //判断事件是否具有FLAG_WINDOW_IS_OBSCURED                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {            // Window is obscured, drop this touch.            //该标志位的含义表示:当View所在的Window被遮挡且事件携带对应标志时,应该过滤收到的触摸事件            return false;        }        return true;    }

为了理解增加过滤判断的原因,我们需要看看FLAG_WINDOW_IS_OBSCURED的注释:

   /**     * 1、window被覆盖时,收到其上window的触摸事件时,就会添加该标志位     * This flag indicates that the window that received this motion event is partly     * or wholly obscured by another visible window above it.  This flag is set to true     * even if the event did not directly pass through the obscured area.     *      * 2、添加标志位的原因: 防止恶意应用覆盖在正常应用之上,截取用户信息或误导用户     * 例如:需要输入密码时,恶意应用添加透明界面,就可能截取用户输入的信息     * A security sensitive application can check this flag to identify situations in which     * a malicious application may have covered up part of its content for the purpose     * of misleading the user or hijacking touches.  An appropriate response might be     * to drop the suspect touches or to take additional precautions to confirm the user's     * actual intent.     */

2.3 拦截事件
如果触摸事件没有被过滤掉,那么就可以开始正常处理了,对应源码如下:

............//首先得到触摸事件的类型final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;//Handle an initial down.//所有的触摸事件都以ACTION_DOWN开始//因此,当收到ACTION_DOWN事件时,表明新的触摸事件发生了//此时会清除旧有的状态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);    //一个MotionEvent从ACTION_DOWN开始,到ACTION_UP结束    //其间会有多个类型的事件传递到View    //这些事件传递的对象,会用一个链表TouchTargets记录起来    //当一个ACTION_DOWN发生时,该函数中会调用clearTouchTargets,    //清除旧有的记录    resetTouchState();}// Check for interception.final boolean intercepted;//如果收到新的ACTION_DOWN事件//或者其它类型的事件,但之前已经找到传递View//则进入常规拦截流程if (actionMasked == MotionEvent.ACTION_DOWN        || mFirstTouchTarget != null) {    //mFirstTouchTarget指向前一个事件传递的View    //不为null, 说明之前已经有事件未被拦截    //首先需要判断ViewGroup是否允许拦截事件    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (!disallowIntercept) {        //允许拦截时调用onInterceptTouchEvent函数        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.    // 进入这个分支,以为收到非ACTION_DOWN事件,且mFirstTouchTarget = null    // 那么说明这个MotionEvent的ACTION_DOWN事件就被拦截了    // 那么之后的UP、MOVE等操作,都会被拦截    intercepted = true;}//If intercepted, start normal event dispatch.//Also if there is already a view that is handling the gesture, do normal event dispatch.if (intercepted || mFirstTouchTarget != null) {    ev.setTargetAccessibilityFocus(false);}..........

除了特殊的MotionEvent以外,默认情况下,
ViewGroup的onInterceptTouchEvent将返回false,
即不拦截MotionEvent。

2.4 查找处理ACTION_DOWN等事件的View
若触摸事件没有被截取,就需要查找处理事件的View了。

// Check for cancelation.// 首先判断该事件是否需要被取消final boolean canceled = resetCancelNextUpFlag(this)    || actionMasked == MotionEvent.ACTION_CANCEL;//判断是否可以将事件分发给多个子Viewfinal boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0//用于记录本次分发事件的目标TouchTarget newTouchTarget = null;//记录是否成功分发boolean alreadyDispatchedToNewTouchTarget = false;//如果事件没有被拦截或取消,就可以开始进行分发if (!canceled && !intercepted) {    //与前面第一部分对应, 对于一个特殊的事件来说    //如果当前ViewGroup可以处理,在第一部分已经清除掉了flag    //若无法处理,则在这里查找是否存在可以处理的child view    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()            ? findChildWithAccessibilityFocus() : null;    //开始处理ACTION_DOWN类型的事件            ..................

从上面代码可以看出,当MotionEvent没有被拦截或取消时,才会执行后续的流程。
否则,将直接进入下一部分(即2.5小节)。

//对于以下类型的MotionEvent,才需要重新查找target View//其中比较有代表性的就是ACTION_DOWNif (actionMasked == MotionEvent.ACTION_DOWN        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {    final int actionIndex = ev.getActionIndex(); // always 0 for down    //如果需要将事件分发给多个子View, 则得到actionIndex对应的pointerId    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)            : TouchTarget.ALL_POINTER_IDS;    // Clean up earlier touch targets for this pointer id in case they    // have become out of sync.    // 清除idBitsToAssign对应target的旧有状态    removePointersFromTouchTargets(idBitsToAssign);    final int childrenCount = mChildrenCount;    //开始进行查找    if (newTouchTarget == null && childrenCount != 0) {        //得到触摸事件对应的坐标,根据坐标才能得到可以处理该事件的child view        final float x = ev.getX(actionIndex);        final float y = ev.getY(actionIndex);        // Find a child that can receive the event.        // Scan children from front to back.        // 看函数名就应该能知道含义        // 此处按顺序得到ViewGroup的child view        // 从变量名来看,得到的是先序遍历的结果        final ArrayList preorderedList = buildTouchDispatchChildList();        //此处对应child view按特定顺序绘制的情况        //需要参考getChildDrawingOrder和setChildrenDrawingOrderEnabled        final boolean customOrder = preorderedList == null                && isChildrenDrawingOrderEnabled();        //逆序开始查找Child View        //这么做的原因是:        //后添加的View作为子View,被绘制在父View的上层        //于是,按照先序遍历时,后添加的子View肯定处于List的后端        //我们点击界面时,按照逻辑,肯定应该让最上面的View优先进行处理        //结合上面的原因,就应该让处于List末端的子View,优先处理MotionEvent        for (int i = childrenCount - 1; i >= 0; i--) {            //从preorderedList取出View            final int childIndex = getAndVerifyPreorderedIndex(                    childrenCount, i, customOrder);            final View child = getAndVerifyPreorderedView(                    preorderedList, children, childIndex);            //需要处理Accessibility Focus事件时,优先找出对应的View            if (childWithAccessibilityFocus != null) {                if (childWithAccessibilityFocus != child) {                    continue;                }                //一旦找到, 就将触摸事件递交给该View                //此处置为null, 表明不再寻找具有Accessibility Focus能力的View                //因此,只有第一个具有Accessiliblity focus能力的View有机会处理这种类型的事件                childWithAccessibilityFocus = null;                //此处,将i重置为childrenCount - 1                //意味着若具有Accessibility Focus能力的View, 无法处理该MotionEvent                //那么将按照正常流程, 重新找出能够处理该MotionEvent的View                i = childrenCount - 1;            }            //判断View能否处理MotionEvent            //canViewReceivePointerEvents主要判断View是否可见(可见或在播放动画)            //isTransformedTouchPointInView主要判断MotionEvent的坐标是否落在View内            if (!canViewReceivePointerEvents(child)                    || !isTransformedTouchPointInView(x, y, child, null)) {                // 如果不能处理该View, 则跳过                //代码进入到这里,意味着:                //没有Accessibility Focus View                //或有Accessibility Focus View, 但无法处理该MotionEvent                //无论哪种情况,都去掉标志,将MotionEvent当作普通事件处理                ev.setTargetAccessibilityFocus(false);                continue;            }            //判断View是否已经接收过该事件(其它的类型)            //如果接收过该事件,那么newTouchTarget != null            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;                //找到了接收事件的View则break, 退出查找过程                break;            }            //清除child view的PFLAG_CANCEL_NEXT_UP_EVENT            resetCancelNextUpFlag(child);            //dispatchTransformedTouchEvent的主要功能,是按需调整MotionEvent,            //然后递交给child view的dispatchTouchEvent处理            //当child为null时,由ViewGroup自己处理            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                //一旦事件被消耗掉,则更新相关的状态                mLastTouchDownTime = ev.getDownTime();                if (preorderedList != null) {                    for (int j = 0; j < childrenCount; j++) {                        if (children[childIndex] == mChildren[j]) {                            mLastTouchDownIndex = j;                            break;                        }                    }                } else {                    mLastTouchDownIndex = childIndex;                }                mLastTouchDownX = ev.getX();                mLastTouchDownY = ev.getY();                //新的TouchTarget被记录到整个链表中, 并作为新的mFirstTouchTarget                newTouchTarget = addTouchTarget(child, idBitsToAssign);                alreadyDispatchedToNewTouchTarget = true;                break;            }            // The accessibility focus didn't handle the event, so clear            // the flag and do a normal dispatch to all children.            ev.setTargetAccessibilityFocus(false);        }        if (preorderedList != null) preorderedList.clear();    }    //处理没有找到newTouchTarget的情况    //将idBitsToAssign付给前一次的TouchTargets    //这么做的目的不是很清楚    if (newTouchTarget == null && mFirstTouchTarget != null) {        // Did not find a child to receive the event.        // Assign the pointer to the least recently added target.        newTouchTarget = mFirstTouchTarget;        while (newTouchTarget.next != null) {            newTouchTarget = newTouchTarget.next;        }        newTouchTarget.pointerIdBits |= idBitsToAssign;    }}............

2.5 其它
对于ACTION_DOWN等类型的事件来说,尝试查找负责处理的View后,
需要开始进行后续的收尾工作。
对于其它类型的事件而言,这里是处理的起点。

//Dispatch to touch targets.if (mFirstTouchTarget == null) {    // No touch targets so treat this as an ordinary view.    // 分发MotionEvent失败,没有找到mFirstTouchTarget, 该事件将被递交给ViewGroup处理    handled = dispatchTransformedTouchEvent(ev, canceled, null,            TouchTarget.ALL_POINTER_IDS);} else {    // Dispatch to touch targets, excluding the new touch target if we already    // dispatched to it.  Cancel touch targets if necessary.    // 进入到这个分支时,可能是ACTION_DOWN事件,也可能是其它类型的事件    TouchTarget predecessor = null;    TouchTarget target = mFirstTouchTarget;    //轮询链表,找到处理事件的target    while (target != null) {        final TouchTarget next = target.next;        //这个if针对的是已经处理的ACTION_DOWN事件, 将handled置为true        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {            handled = true;        } else {            //针对未处理的ACTION_DOWN或其它类型事件            //判断是否取消            final boolean cancelChild = resetCancelNextUpFlag(target.child)                    || intercepted;            //分发给子视图            if (dispatchTransformedTouchEvent(ev, cancelChild,                    target.child, target.pointerIdBits)) {                handled = true;            }            //如果某个视图cancel, 就将其从链表中移除            if (cancelChild) {                if (predecessor == null) {                    mFirstTouchTarget = next;                } else {                    predecessor.next = next;                }                target.recycle();                target = next;                continue;           }        }        //更新“指针”        predecessor = target;        target = next;    }}// 后续收尾工作,主要根据事件类型、处理的结果,更新一些状态...............

三、总结
至此,我们已经结合源码大致了解ViewGroup的事件分发逻辑。
这部分内容细节比较多,但整体逻辑比较清晰,大致如下图所示:
Android O: 触摸事件传递流程源码分析(上)_第1张图片

ViewGroup有拦截事件的能力,同时也有分发事件给Child View的能力。
这导致ViewGroup的事件处理流程比较复杂。

相对而言,View处理触摸事件的流程较为简单,我们在下一篇博客中再来分析。

更多相关文章

  1. Android 上层界面到内核代码的完整的流程分析,以alarm为例子
  2. java/android 使用swig编译c/c++ 代码类型转换
  3. 解决android listview中OnItemClickListener事件和里面button点
  4. Android 启动Activity面流程(Android 9.0)
  5. Android触摸屏事件派发机制详解与源码分析
  6. Android事件分发机制练习---打造属于自己的瀑布流
  7. Android 开机启动流程分析
  8. Android事件处理的两种模型
  9. Android 监听软键盘弹起和收起事件

随机推荐

  1. HttpHuiApplication--下载图片url,HttpUR
  2. Android(安卓)- SwitchButton开关按钮
  3. clock时钟
  4. 多线程下载测试TestDownload
  5. TextView获取行数
  6. Android(安卓)技术总结(016)—— 使用百度
  7. 测试手机多点触摸
  8. 【Android】判断某个App是否安装并启动(qu
  9. 创建文件并进行读写
  10. android广播 demo