Android O: 触摸事件传递流程源码分析(上)

前面的博客中,我们通过例子分析了一下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;

//判断是否可以将事件分发给多个子View
final 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_DOWN
if (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<View> 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: 触摸事件传递流程源码分析(上)》

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

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

    原文作者:ZhangJianIsAStark
    原文地址: https://blog.csdn.net/Gaugamela/article/details/78785528
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞