Android View 事件分发机制

参考资料

鸿洋版事件分发机制
郭霖版事件分发机制
Android开发艺术探索

Android事件传递整体流程简介

Android输入事件的源头是位于/dev/input/下的设备节点,而输入事件的终点是由WMS管理的某个窗口,最终由窗口中的View处理。最初的输入事件为内核生成的原始事件,而最终交付给窗口的则是KeyEvent(键盘)或MotionEvent(鼠标和触摸屏)对象。输入事件由Native层进入到Java层的第一个函数是InputEventReceiver.dispatchInputEvent(),这样我们的手指触摸事件(MotionEvent)就传递到Java层,到应用层的传递过程遵循如下顺序:Activity->Window->View;即View事件最先传递给Activity,然后由Activity传递给Window,最后由Window传递给View;顶级View(DecorView)接收到事件后,就会按照事件分发机制去分发事件;事件到达Java层以后是谁将事件传递给Activity?

//事件传递整体流程:底层生成原始事件后,经过一列加工处理之后,将事件封装成MotionEvent、keyEvent,然后传递到Java层的InputEventReceiver.dispatchTouchEvent中,事件到达Java层之后,如何一步步传递到Activity?以下调用堆栈可以看到事件如何用从InputEventReceive传递到Activity;事件传递到Activity之后,由Activity传递给Window(PhoneWindow),最后由Window传递给顶级View;顶级View(DecorView)接收到事件后,就会按照事件分发机制去分发事件;本文主要是从Activity开始来分析事件分发机制;

//事件详细信息,action表示事件类型,x,y表示事件发生的位置,deviceId是硬件设备的id值
EventTest: MainActivity dispatchTouchEvent: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=408.94913, y[0]=416.38184, toolType[0]=TOOL_TYPE_MOUSE, buttonState=BUTTON_PRIMARY, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=498914, downTime=498914, deviceId=13, source=0x2002 }
System.err: java.lang.Exception: EventTest2
System.err:     at .*********************.MainActivity.dispatchTouchEvent(MainActivity.java:68)
System.err:     at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
System.err:     at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:434)
System.err:     at android.view.View.dispatchPointerEvent(View.java:12029)
System.err:     at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4834)
//在onProcess方法中,进行事件类型的判断,然后根据不同的事件类型调用不同的处理方法
System.err:     at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4644)
//在deliver中完成事件处理之后,调用finishInputEvent给输入系统一个反馈;
System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4176)  
System.err:     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4229)
System.err:     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4195)
System.err:     at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4322)
System.err:     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4203)
System.err:     at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4379)
System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4176)
System.err:     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4229)
System.err:     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4195)
System.err:     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4203)
System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4176)
System.err:     at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6707)
System.err:     at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6681)
System.err:     at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6642)
System.err:     at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6810)
//事件由native层正式进入到Java层
System.err:     at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:187)
System.err:     at android.os.MessageQueue.nativePollOnce(Native Method)
System.err:     at android.os.MessageQueue.next(MessageQueue.java:325)
System.err:     at android.os.Looper.loop(Looper.java:142)
System.err:     at android.app.ActivityThread.main(ActivityThread.java:6627)
System.err:     at java.lang.reflect.Method.invoke(Native Method)
System.err:     at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

ViewPostImeInputStage.onProcess():

    final class ViewPostImeInputStage extends InputStage {
        @Override
        protected int onProcess(QueuedInputEvent q) {
            // 当onProcess被回调时,processKeyEvent、processPointerEvent、processTrackballEvent、
            // processGenericMotionEvent至少有一个方法就会被调用,这些方法都是属于ViewPostImeInputStage的。
            // 这些方法中都有一句很关键的一句代码处理按键事件
            // mView.dispatchKeyEvent(event)
            // mView.dispatchPointerEvent(event)
            // mView.dispatchTrackballEvent(event)
            // mView.dispatchGenericMotionEvent(event)
            // mView的实例化在ViewRootImpl的setView方法中,其实就是DecorView
            // 这样一来,可以知道ViewPostImeInputStage将事件分发到了DecorView
            if (q.mEvent instanceof KeyEvent) {
                // Key事件会调用到processKeyEvent,处理key事件,如键盘事件
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    //触摸屏事件(MotionEvent),鼠标事件(MotionEvent),鼠标事件到应用层是MotionEvent,不是KeyEvent
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    //轨迹球事件
                    return processTrackballEvent(q);
                } else {
                    //其它motion事件,如游戏手柄
                    return processGenericMotionEvent(q);
                }
            }
        }
    }

事件分发整体流程:底层生成原始事件后,经过一列加工处理之后,将事件封装成MotionEvent、keyEvent,然后传递到Java层InputEventReceiver.dispatchTouchEvent中,事件到达Java层之后,如何一步步传递到Activity?通过调用堆栈可以看到事件如何用从InputEventReceive传递到Activity;事件传递到Activity之后,由Activity传递给Window(PhoneWindow),最后由Window传递给顶级View;顶级View(DecorView)接收到事件后,就会按照事件分发机制去分发事件;本文主要是从Activity开始来分析事件分发机制;
与事件分发过程相关的几个方法主要有:
(1)dispatchTouchEvent:用来进行事件分发,如果事件能够传递给当前View,那么次方法一定会被调用,返回值受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响;
(2)onInterceptTouchEvent:在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前VIew拦截了某个事件,那么在同一事件序列中,此方法不会再被调用,返回结果表示是否拦截当前事件;
(3)onTouch:在dispatchTouchEvent方法中调用,其调用优先级高于onTouchEvent;
(4)onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件,反馈结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接受事件;
(5)onclick:在发生点击事件时,会调用该方法,表示有点击事件;onclick发生的前提是当前View可以点击,并且它收到down和up事件;
(6)onLongClick:当长按事件发生时会回调该方法,手指或鼠标按下500ms之后就会发生长按事件,如果onLongClick返回true,就 不会在调用onClick方法,返回false,就会调用onClick;

1.Activity对事件的分发过程

Activity.dispatchTouchEvent代码如下:

    /**
     * 一个点击事件产生后,它的传递过程遵循如下顺序:
     * 

Activity——>Window——>View

* 即事件总是先传递给Activity,由Activity的dispatchTouchEvent方法来进行事件派发, * 具体的工作是由Activity内部的Window来完成的。Activity传递给Window后,Window再 * 传递给顶级View。顶级View接受事件后,就会按照事件分发机制去分发事件。 * @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(); } // 传递给Window对象。getWindow()返回的是PhoneWindow if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
2.Window(PhoneWindow)对事件的分发过程:
    // This is the top-level view of the window, containing the window decor.
    //DecorView是顶级View,也叫根View
    private DecorView mDecor;
      @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        // PhoneWindow将事件直接传递给了DecorView
        return mDecor.superDispatchTouchEvent(event);
    }

通过getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0)这种方法可以获取Activity所设置的View,setContentView所设置的View是DecorView的子View,关于DecorView后续会专门进行讲解,目前事件传递已经传递到DecorView这里,及事件已经从Activity传递到View中。

3.View事件分发机制

当事件传递到View中时,首先进入View的dispatchTouchEvent

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * 如果事件能够传递给当前View,那么此方法一定会被调用,
     * 返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
     * View是一个单独的元素,它没有子元素因此无法向下传递事件,
     * 所以它只能自己处理事件,所以View的onTouchEvent方法默认返回true。
     *
     * @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) {
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            // 首先会判断当前View有没有设置OnTouchListener,
            // 如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会被调用,
            // 可见OnTouchListener的优先级要高于onTouchEvent,这样做的好处是方便在外界处理点击事件。
            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;
    }

4.View的onTouchEvent

接下来是View的onTouchEvent:

    public boolean onTouchEvent(MotionEvent event) {
    
        // 对点击事件的具体处理。只要CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件,因为返回了true
        // View的LONG_CLICKABLE属性默认为false,而CLICKABLE属性默认为true,不过具体的View的CLICKABLE又不一定,
        // 确切来说是可点击的View其CLICKABLE属性true,比如Button,不可点击的View的CLICKABLE为false,比如TextView。
        // 通过setClickable和setLongClickable可以设置这两个属性。
        // 另外setOnClickListener和setOnLongClickListener会自动将View的这两个属性设为true。
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        //移除长按监测
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        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.
                                // 当up事件发生时,会触发performClick方法
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    // 如果View设置OnClickListener,那么performClick就会调用View的onClick方法。
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    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
                        //设置当前View的状态,以及更新View的drawable state,例如button按下、悬浮时显示不同的背景,即刷新背景
                        setPressed(true, x, y);
                        //开始长按监测,所以View的长按监测位于
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();//当移出当前View时,会删除当前View的长按监测;
                        ...
                    }
                    break;
            }
            return true;
        }
        return false;
    }

    private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            //ViewConfiguration.getLongPressTimeout()时间为500ms,所以当用户按下去500ms之后就会回OnLongClickListener.onLongClick方法;
            //如果500ms之内手指弹起,会发生ACTION_UP事件,此时会移出长按监测,这样就不会发生长按事件
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
    public boolean performClick() {
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            // 如果设置了OnClickListener监听器,就回调onClick方法。
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        return result;
    }

通过以上代码可以看出:
(1)当发生ACTION_DOWN的时候,开始长按监测,如果用户手指按下时间超过500ms才会发生长按事件,少于500ms就是onClick事件;可以得知,是否是长按事件是在Java层进行判断的,并且和ACTION_MOVE事件没有关系。
(2)onLongClick方法返回true之后会将mHasPerformedLongPress属性置为true,在Up的时候就不会执行onClick,如果onlongClick返回false在Up事件到来时就会执行onClick
(3)onClick是否会发生的前提是当前View是可点击的,并且它收到了Down和Up事件,
(4)只要View的CLICKABLE和LONG_CLICKABLE有一个为TRUE,那么该View就会消耗掉该事件,可以看出以上无论是Down、Move还是Up事件,最终都return true;

5.总结:

(1)整个View的事件分发流程:
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。
(2)onTouchEvent中的DOWN,MOVE,UP
DOWN时:
a、首先设置标志为PREPRESSED,设置mHasPerformedLongPress=false ;然后发出一个115ms后的mPendingCheckForTap;
b、如果115ms内没有触发UP,则将标志置为PRESSED,清除PREPRESSED标志,同时发出一个延时为500-115ms的,检测长按任务消息;
c、如果500ms内(从DOWN触发开始算),则会触发LongClickListener:
此时如果LongClickListener不为null,则会执行回调,同时如果LongClickListener.onClick返回true,才把mHasPerformedLongPress设置为true;否则mHasPerformedLongPress依然为false;
MOVE时:
主要就是检测用户是否划出控件,如果划出了:
115ms内,直接移除mPendingCheckForTap;
115ms后,则将标志中的PRESSED去除,同时移除长按的检查:removeLongPressCallback();
UP时:
a、如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;
b、如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;
c、如果是500ms以后,那么有两种情况:
i.设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;
ii.没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;
d、最后执行setPressed刷新背景,然后将PRESSED标识去除;

本篇博文完成了对View的事件分发机制的整个流程的说明,并且对源码进行了分析;下篇会讲解ViewGroup的事件分发机制;

本文部分内容直接从其他文章直接Copy而来,感谢本文内容所参考文章的作者;

你可能感兴趣的:(Android View 事件分发机制)