Android7 Input(十一)App View InputEvent事件分发

概述

        本文主要讲述App View中inputEvent事件分发的流程,我们这里不讲述关于Android View窗口管理系统。Android系统中View的事件分发的方向就是:

Activity -> PhoneWindow -> DecorView -> ViewGroup -> View

当事件被处理完成后,根据事件处理的结果然,反向调用路径返回:

从View-> ViewGroup->DecorView->PhoneWindow->Activity

本文涉及的源码路径

        frameworks/base/core/java/android/view/ViewRootImpl.java

        frameworks/base/core/java/android/view/View.java

事件分发

        上篇我们讲了,事件分发过程中,会找到目标View开始分发,如下代码所示:

 private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            final View eventTarget =
                    (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
                            mCapturingView : mView;
            mAttachInfo.mHandlingPointerEvent = true;
            boolean handled = eventTarget.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            mAttachInfo.mHandlingPointerEvent = false;
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

继续调用View中的dispatchPointerEvent方法,如下所示:

 public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

然后调用dispacthTouchEvent方法,开始从顶层的Activity一层一层,向能接受输入事件的view进行处理,为什么时Activity中的dispacthTouchEvent方法,这里不再追踪原因,感兴趣的同学可以学习一下Android系统中的窗口管理相关的知识。

1、Activity中的事件分发

Activity中事件分发,如下所示:

  public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent");

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        // 调用子类实现PhoneWindow中的superDispatchTouchEvent()
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

该方法的核心逻辑如下:

a. 调用PhoneWindow中的superDispatchTouchEvent进行分发;

b. 如果事件在PhoneWindow中没有被处理,则调用Activty自己的onTouchEvent方法,默认返回false, 表示事件没有被处理;

2、PhoneWindow中的事件分发

PhoneWindow中调用比较简单,直接交给DecorView处理,如下所示:

 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "superDispatchTouchEvent");
        return mDecor.superDispatchTouchEvent(event);
    }

3、DecorView中的事件分发

DecorView中也不直接处理,而是调用父类的dispatchTouchEvent处理,如下所示:

public boolean superDispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "superDispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

4、ViewGroup中的事件分发

第3步中,事件由DecorView中父类处理,DecorView的父类为FrameLayout,FrageLayout继承ViewGroup,我们从FrameLayout中没有找到dispatchTouchEvent, 再次向上找父类,从ViewGroup中找到了dispatchTouchEvent方法,饶了一圈,最终调用到了ViewGroup中的dispatchTouchEvent如下所示:

public boolean dispatchTouchEvent(MotionEvent ev) {
        ......
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            ......
            // Check for interception.
            // 检查事件是否可以被拦截
            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.
                // 如果当前事件没有处理它的子view,也不是ACTION_DOWN事件,则父类拦截
                intercepted = true;
            }
            ......
            //如果没取消和拦截
            if (!canceled && !intercepted) {
                ......
                    if (newTouchTarget == null && childrenCount != 0) {
                        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.
                        // 找到一个能接受事件的子view
                        // 按照显示在最上层到最下层的顺序去遍历
                        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);
                                    
                            // canViewReceivePointerEvents方法判断子view是否visible或者子view是否在播动画或者即将播动画
                            // isTransformedTouchPointInView方法判断点击区域是否处于子view内
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //dispatchTransformedTouchEvent方法是真正把事件分发给子view处理的
                            // 返回值为true则代表该子view处理了事件
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                ......
                            }
                    }
                ......
                TouchTarget target = mFirstTouchTarget;
                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;
                }
            }
       .......
        return handled;
    }

该方法事件比较复杂,我为了简化事件分发流程,我在这个只保留了最核心的实现,该方法的实现逻辑如下:

1、遍历ViewGroup中的view,找到能接受input事件的View;

2、调用dispatchTransformedTouchEvent()进行事件处理;

3、返回事件处理的结果handled;

5、View中的事件分发

当找到能接受输入事件的子View后,调用dispatchTransformedTouchEvent进入事件的进一步分发,如下所示:

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
       ......
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                //如果子view为空,直接调了ViewGroup的父类View的dispatchTouchEvent方法
                handled = super.dispatchTouchEvent(event);
            } else {
                //如果子view不等于空,调用子view的dispatchTouchEvent方法
                //如果子view是一个ViewGroup,则递归ViewGroup的dispatchTouchEvent方法,重复上面分析的逻辑
                //如果子view是普通的view,则事件分发到View的dispatchTouchEvent方法
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
     ......
    }

该方法中直接执行view中的dispatchTouchEvent方法,这里有一种ViewGroup嵌套的情况下,则继续重复之前的流程,为了简化描述流程,我们这里假设找到最后的孩子view(比如Button)。 然后调用dispatchTouchEvent如下所示:

public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
        ......
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            // 已经注册了onToucherListenero监听,不再调用onTouchEvent处理
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
             //只有result为false事件才分发到onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
      ......
        return result;
    }

我们这里假设没有注册touch监听,则调用view中的onTouchEvent方法,如下所示:

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                ......
                        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.
                                //调用OnClickListener的onClick方法
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                ......

                case MotionEvent.ACTION_DOWN:
                    ......
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;
            }

            return true;
        }

        return false;
    }

该方法的最终调用performClick让控件去处理事件,如下所示:

 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);
        return result;
    }

        最后,调用到了onClick方法,回调App对输入事件的处理接口,事件在这里就到了它最终的到达的地方。我们不再展开讲述如果在这条路径上,如果事件没有被处理的情况。

总结

        本文主要描述了,Android View事件分发被处理的流程,在讲述过程中,为了简化,我们删除了很多细节的东西。因为讲述Android View Input事件不能单独拿出来讲述,因为涉及的内容比较多,希望以后自己能力达到了,再补充上我删除的部分,Android 7 Input 子系统的框架梳理到此为止,下一个专题Android  HAL层开发;

你可能感兴趣的:(Android7,android)