天天看点

Android 事件传递Android 事件传递

Android 事件传递

看了很多的博客都没有涉及到源码的层面来讲解事件传递,看完都感觉有点理解又有点疑惑,所以自己整理一篇。

我是从Activity的dispatchTouchEvent方法入手的,至于Activity的dispatchTouchEvent方法是怎么被调用的,具体是在WindowManagerService中,有兴趣的可以自己去了解。

代码流程:

一般我们点击屏幕之后,WindowManagerService会获取到Touch事件,并将该Touch事件派发给Activity,也就是会调用Activity的dispatchTouchEvent方法。具体的可以看代码。

代码都是从Android 5.1.1中截出来的

Activity的dispatchTouchEvent方法:

/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window.  Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //onUserInteraction是一个空函数,什么都没做
        onUserInteraction();
    }
    //getWindow获取到的是PhoneWindow类
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //如果superDispatchTouchEvent返回false,则调用Activity的onTouchEvent方法。
    return onTouchEvent(ev);
}
           

从getWindow().superDispatchTouchEvent(ev)这里可以看到,Activity又将事件派发给PhoneWindow来处理。

如果superDispatchTouchEvent返回false,则会调用Activity的OnTouchEvent方法。

我们接着看一下PhoneWindow的代码。

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
           

这个方法很简单,又将事件传给mDecor来处理,mDecor是一个DecorView对象,DecorView又继承了FrameLayout。

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

     public boolean superDispatchTouchEvent(MotionEvent event) {
        //这里又调用了父类的dispatchTouchEvent,
        //也就是FrameLayout的dispatchTouchEvent方法。
        return super.dispatchTouchEvent(event);
    }

}
           

FrameLayout没有重写dispatchTouchEvent方法,因此调用的是ViewGroup的dispatchTouchEvent方法。该方法代码比较多,慢慢分析。需要注意TouchTarget,TouchTarget主要是用来记录事件接收情况,包括接收该事件的子view、事件详情等。dispatchTouchEvent就是根据TouchTarget来决定派发事件到哪些子View中。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    ......

    //最后会返回这个handled
    boolean handled = false;
    //应用安全策略来过滤一部分事件,比如View被遮挡了就会被过滤
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 处理初始的Down事件,只要收到Down事件,就认为是一个新的Touch事件
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        //检查是否拦截,默认false
        //mFirstTouchTarget 是TouchTarget链表的头节点。
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) { 
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != ;
            //如果允许拦截
            if (!disallowIntercept) {
                //这里调用了onInterceptTouchEvent方法,
                //该方法默认返回false,即不进行拦截
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

        // 检查cancel事件.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != ;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;

        if (!canceled && !intercepted) {//判断是否取消或者被拦截
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                      ? findChildWithAccessibilityFocus() : null;

            //只有DOWN事件(包括多点触控)才会走这个流程,
            //这个流程主要是找到能接受该MotionEvent的TouchTarget
            //后续的其他事件则直接跳过该流程
            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
                final int idBitsToAssign = split ?  << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                //Removes the pointer ids from consideration.
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != ) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);

                    //由前往后遍历所有的子View,
                    //寻找一个能接受该事件的子view,并生成对应的TouchTarget
                    final ArrayList<View> preorderedList = buildOrderedChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;

                    for (int i = childrenCount - ; i >= ; i--) {

                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(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 - ;
                        }

                        //判断该view是否能接受事件且事件坐标不超过view范围
                        if(!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }

                        //获取child对应的TouchTarget,
                        //如果child还没有对应的TouchTarget,则返回null
                        newTouchTarget = getTouchTarget(child);

                        // 若子View已经在TouchTarget链表中
                        if (newTouchTarget != null) {
                            // 将事件id加入到touchTarget中
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);

                        //dispatchTransformedTouchEvent这个方法会把事件传递给子View,
                        //方法内部会调用子View的dispatchTouchEvent方法。
                        //这个方法后面会讲到。
                        //并返回子View的dispatchTouchEvent的返回值。
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            //为true代表子View接收了该事件
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                // childIndex points into presorted list, find original index
                                for (int j = ; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }

                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //重新生成一个TouchTarget,包含了子view
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //标记为已经将事件分发,避免后面重新分发Down事件。
                            alreadyDispatchedToNewTouchTarget = true;
                            //已经找到TouchTarget了,结束遍历
                            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();
                }

                // 没有发现接收事件的子View
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // 把事件赋给最早的TouchTarget
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
        }

        //将事件派发给TouchTarget
        if (mFirstTouchTarget == null) {
            //没有TouchTarget,所以派发给自己
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 派发事件给TouchTarget
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            //遍历TouchTarget链表
            while (target != null) {
                final TouchTarget next = target.next;
                //已经分发过
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    //检查是否取消或者拦截
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    //派发事件
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
       }

       //如果是UP或者Cancel事件,则清空TouchTarget链表
       if (canceled || actionMasked==MotionEvent.ACTION_UP 
           || actionMasked==MotionEvent.ACTION_HOVER_MOVE){
            resetTouchState();
        } else if (split && actionMasked ==  MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove =  << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, );
    }
    return handled;
}
           

从上面的代码可以看到,对事件的派发工作都是在dispatchTransformedTouchEvent方法中。

dispatchTransformedTouchEvent会将事件的坐标转换为特定子view中的坐标,并调用子view的dispatchTouchEvent方法,最后返回dispatchTouchEvent的返回值。

/**
 * Transforms a motion event into the coordinate space of a particular child view,
 * filters out irrelevant pointer ids, and overrides its action if necessary.
 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 */
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
      View child, int desiredPointerIdBits) {
    //最后会返回该值
    final boolean handled;
    final int oldAction = event.getAction();

    //取消事件不需要转换坐标或者过滤,因为取消事件不需要对坐标进行处理。
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        //派发cancel事件
        if (child == null) {
            //注意是调用super的dispatchTouchEvent方法
            //即View的dispatchTouchEvent方法
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    if (newPointerIdBits == ) {
        return false;
    }

    final MotionEvent transformedEvent;

    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                //注意是调用super的dispatchTouchEvent方法
                handled = super.dispatchTouchEvent(event);
            } else {
                //坐标转换
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                //分发给子View
                handled = child.dispatchTouchEvent(event);
                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        //坐标转换
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        //派发给子view
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    // Done.
    transformedEvent.recycle();
    return handled;
}
           

到这里就把事件从Activity分发到View上中。

接下来看看View的dispatchTouchEvent方法。

/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
public boolean dispatchTouchEvent(MotionEvent event) {
    //最后会返回这个值
    boolean result = false;

    ......

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    //应用安全策略来过滤一部分事件,比如View被遮挡了就会被过滤
    if (onFilterTouchEventForSecurity(event)) {

        //ListenerInfo类含有View的很多监听事件,包括OnTouchListener、OnClickListener等。
        ListenerInfo li = mListenerInfo;

        //这里执行li.mOnTouchListener.onTouch方法,也就是执行我们通过setOnTouchListener的监听事件。
        if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //这里要注意,如果我们设置的TouchListener返回false,则还会执行View自身的OnTouchEvent事件。
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, );
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
        actionMasked == MotionEvent.ACTION_CANCEL ||
        (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        //停止滚动
        stopNestedScroll();
    }
    return result;
}
           

TouchTarget

TouchTarget的定义和相关方法

/* Describes a touched view and the ids of the pointers that it has captured.
 *
 * This code assumes that pointer ids are always in the range 0..31 such that
 * it can use a bitfield to track which pointer ids are present.
 * As it happens, the lower layers of the input dispatch pipeline also use the
 * same trick so the assumption should be safe here...
 */
private static final class TouchTarget {
    private static final int MAX_RECYCLED = ;
    private static final Object sRecycleLock = new Object[];
    private static TouchTarget sRecycleBin;
    private static int sRecycledCount;

    public static final int ALL_POINTER_IDS = -; // all ones

    // The touched child view.
    public View child;

    // The combined bit mask of pointer ids for all pointers captured by the target.
    public int pointerIdBits;

    // The next target in the target list.
    public TouchTarget next;

    private TouchTarget() {
    }

    public static TouchTarget obtain(View child, int pointerIdBits) {
        final TouchTarget target;
        synchronized (sRecycleLock) {
            if (sRecycleBin == null) {
                target = new TouchTarget();
            } else {
                target = sRecycleBin;
                sRecycleBin = target.next;
                sRecycledCount--;
                target.next = null;
            }
        }
        target.child = child;
        target.pointerIdBits = pointerIdBits;
        return target;
    }

    public void recycle() {
        synchronized (sRecycleLock) {
            if (sRecycledCount < MAX_RECYCLED) {
                next = sRecycleBin;
                sRecycleBin = this;
                sRecycledCount += ;
            } else {
                next = null;
            }
            child = null;
        }
    }
}
           
/**
 * Resets all touch state in preparation for a new cycle.
 */
private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

/**   
 * Resets the cancel next up flag. Returns true if the flag was previously set.
 */
private static boolean resetCancelNextUpFlag(View view) {
    if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != ) {
            view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
        return true;
    }
    return false;
}


/**
 * Clears all touch targets.
 */
private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
   }
}

/**   
 * Cancels and clears all touch targets.
 */
private void cancelAndClearTouchTargets(MotionEvent event) {
    if (mFirstTouchTarget != null) {
            boolean syntheticEvent = false;
        if (event == null) {
            final long now = SystemClock.uptimeMillis();
            event = MotionEvent.obtain(now, now,
            MotionEvent.ACTION_CANCEL, f, f, );
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            syntheticEvent = true;
        }

        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            resetCancelNextUpFlag(target.child);
            dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
        }
        clearTouchTargets();

        if (syntheticEvent) {
            event.recycle();
        }
    }
}

/**  
 * Gets the touch target for specified child view. Returns null if not found.
 */

private TouchTarget getTouchTarget(View child) {
    for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
        if (target.child == child) {
            return target;
        }
    }
    return null;
}

/**  
 * Adds a touch target for specified child to the beginning of the list. 
 * Assumes the target child is not already present.
 */
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

/**
 * Removes the pointer ids from consideration.
 */
private void removePointersFromTouchTargets(int pointerIdBits) {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if ((target.pointerIdBits & pointerIdBits) != ) {
            target.pointerIdBits &= ~pointerIdBits;
            if (target.pointerIdBits == ) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}


private void cancelTouchTarget(View view) {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if (target.child == view) {
            if (predecessor == null) {
                mFirstTouchTarget = next;
            } else {
                predecessor.next = next;
            }
            target.recycle();
            final long now = SystemClock.uptimeMillis();
            MotionEvent event = MotionEvent.obtain(now, now,
            MotionEvent.ACTION_CANCEL, f, f, );
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            view.dispatchTouchEvent(event);
            event.recycle();
            return;
        }
        predecessor = target;
        target = next;
    }
}
           

总结:

Activity的传递

  1. WindowManagerService将事件传给Activity。
  2. Activity调用dispatchTouchEvent()方法分发事件。
    1. 如果是Down事件,会调用onUserInteraction方法。
    2. 通过PhoneWindow把事件分发给Activity最底层的ViewGroup。
    3. 如果最底层的ViewGroup返回false,则调用Activity的onTouchEvent。

ViewGroup的分发过程

  1. Activity将事件传给最底层的ViewGroup,调用ViewGroup的dispatchTouchEvent()方法。
  2. 如果接受到Down事件,则代表是一个新的Touch事件,会进行初始化,并清除之前的所有状态。
  3. ViewGroup调用onInterceptTouchEvent判断是否拦截事件分发。
  4. 如果没有被拦截,且为Down事件,则遍历所有的子View。
    1. 如果子view不能接受事件,或者事件的坐标不在子View的范围内,则跳过。
    2. 如果子View已经有对应TouchTarget对象,则由那个TouchTarget分发事件。
    3. 调用dispatchTransformedTouchEvent将事件传给子View。
      • 返回true,表示该View接受了该事件,会生成对应的TouchTarget,并加入到TouchTarget链表中。
      • 返回false,表示该View不接受该事件。
  5. 根据TouchTarget链表分发事件。
    • TouchTarget链表为空,则调用super.dispatchTouchEvent(),将事件分发给自己。
    • 否则遍历TouchTarget链表,并分别调用dispatchTransformedTouchEvent进行分发事件。
      • 如果child的参数是空的,则代表是调用给自身的dispatchTouchEvent()。
      • 如果child的参数不为空,则调用child的dispatchTouchEvent()。

View

  1. ViewGroup通过调用View的dispatchTouchEvent方法将事件分发给View。
  2. 如果View设置了OnTouchListener,则调用OnTouchListener的onTouch方法。
    • onTouch返回true,则dispatchTouchEvent返回true。
  3. View没有设置OnTouchListener事件或者onTouch返回false,则调用View的onTouchEvent方法,如果onTouchEvent方法返回true,dispatchTouchEvent也会返回true。
  4. dispatchTouchEvent返回true,则表示该事件已经被接受。