Input event --- View hierarchy

输入事件派发第二篇!

1. ViewRootImpl

下图便是"View hierarchy"的树结构,ViewRootImpl居于"View hierarchy"的顶层,但是它并非View的一分子,可以把它理解为管理者,核心任务是和WindowManagerService进行通信。
在这里插入图片描述
Activity resume的时候addView@WindowManagerGlobal方法会调用setView@ViewRootImpl方法,部分代码如下:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                
                ......
                
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel(); //创建InputChannel对象
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //添加窗口
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch (RemoteException e) {
                   ......
                } finally {
                   ......
                }
               
                ......
                
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }

                view.assignParent(this);
                mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
                mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;

                ......
                
                // Set up the input pipeline.
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

                mFirstInputStage = nativePreImeStage;
                mFirstPostImeInputStage = earlyPostImeStage;
                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
            }
        }
    }

上面的代码只保留了三部分:

  • 1.创建InputChannel对象mInputChannel。
  • 2.创建WindowInputEventReceiver对象。
  • 3.设置input event处理的pipeline。

创建的mInputChannel对象会作为参数传入" mWindowSession.addToDisplay(…)“的调用之中,” mWindowSession.addToDisplay(…)"会调用addWindow@WindowManagerService方法,在这个方法中openInputChannel@WindowState方法会被调用,经此方法传入的mInputChannel对象也就和Native层的InputChannel有了联系,这是上篇InputChannel中的内容

WindowInputEventReceiver继承了InputEventReceiver,InputEventReceiver的初始化,请参考上篇,这里就不再说了。构建WindowInputEventReceiver对象时使用了mInputChannel作为参数,当有input event需要派发时,NativeInputEventReceiver会调用dispatchInputEvent@WindowInputEventReceiver方法,后者会调用onInputEvent@WindowInputEventReceiver方法,这样input event就传入了ViewRootImpl中,这部分我们先说到这里,稍后再讲ViewRootImple对input event的处理。

2. InputStage

ViewRootImpl的setView方法设置了input event处理的pipeline, 如下:

                // Set up the input pipeline.
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

                mFirstInputStage = nativePreImeStage;
                mFirstPostImeInputStage = earlyPostImeStage;

InputStage可以理解为用来处理input event的"阶段"。上面代码中的"ViewPostImeInputStage"和"ViewPreImeInputStage"等都是iInputStage的子类。通过这些类的构造方法,我们可以发现创建这些子类对象时传入的参数,其实是"next stage":

       public InputStage(InputStage next) {
            mNext = next;
        }

也就是说越是后面创建的,优先级就越高,所以根据代码中子类stage对象的创建顺序,在处理input event时的优先级顺序如下(从高到低):

stage实例类型commetns
nativePreImeStageNativePreImeInputStage将input event派发给native activity处理,不支持pointer event
viewPr中eImeStageNativePreImeInputStage将input even派发给view hierarchy处理,不至此pointer event
imeStageImeInputStage将input event派发给ime(输入法引擎)处理
earlyPostImeStageEarlyPostImeInputStage对post-ime的input event做早期处理
nativePostImeStageNativePostImeInputStage将post-ime的input event派发给native activity处理
viewPostImeStageViewPostImeInputStage将post-ime的input event派发给view hierarchy处理
mSyntheticInputStageSyntheticInputStage处理unhandled event中的合成event,都是些和track ball,joy stack等相关的event

这些stage分成三类:

  • 输入法之前的stage(pre-ime stage)
  • 输入法stage(ime stage)
  • 输入法之后的stage(post-ime stage)

mFirstInputStage 变量的值是nativePreImeStage,如果从mFirstInputStage开始处理,是一个"正常"的处理顺序。
mFirstPostImeInputStage变量的值是earlyPostImeStage,跳过了"输入法之前的stage"和"输入法stage"。


3. View hierarchy中的派发

ViewRootImpl对input event的处理,上面我们说到input event通过WindowInputEventReceiver的onInputEvent方法派发到了ViewRootImpl中,下面是此方法的代码:

        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            enqueueInputEvent(event, this, 0, true);
        }

onInputEvent@WindowInputEventReceiver方法调用了ViewRootImpl的enqueueInputEvent方法,后者用InputEvent类型的event为参数构造QueuedInputEvent类型的对象,并将新对象放入队列尾部,然后调用doProcessInputEvents@ViewRootImpl方法从队列头部取出QueuedInputEvent,交给deliverInputEvent@ViewRootImpl方法进行处理,后者会调用合适stage的deliver方法,代码如下:

    private void deliverInputEvent(QueuedInputEvent q) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                q.mEvent.getSequenceNumber());
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }

        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            //如果event要跳过输入法,那么就使用mFirstPostImeInputStage,从EarlyPostImeInputStage开始处理;否则使用mFirstInputStage,从NativePreImeInputStage开始处理。
            //拥有FLAG_DELIVER_POST_IME的QueuedInputEvent才需要跳过输入法,通过dispatchKeyFromIme@ViewRootImpl发过来的event才会有这个flag,InputMethodManager.java内会调用这个方法。
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (q.mEvent instanceof KeyEvent) {
            mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
        }

        if (stage != null) {//如果stage不为空,那么就调用stage的deliver方法
            handleWindowFocusChanged();
            stage.deliver(q);
        } else {//否则,就结束派发
            finishInputEvent(q);
        }
    }

拥有FLAG_DELIVER_POST_IME的QueuedInputEvent才需要跳过输入法,通过dispatchKeyFromIme@ViewRootImpl发过来的event才会有这个flag,InputMethodManager.java内会调用这个方法。大部分的event是从mFirstInputStage,即NativePreImeInputStage开始处理的。

InputStage的deliver方法如下:

        /**
         * Delivers an event to be processed.
         */
        public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
               //如果q.mFlags已经含有FLAG_FINISHED,说明已经被处理过了,直接调用forward,起诉forward会转交到下一个阶段
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                //如果需要丢弃就调用finish方法处理
                finish(q, false);
            } else {
                //apply方法调用了onProcess方法,各个子类都重写了这个方法; apply方法用来做最终处理,调用finish或者forward方法
                apply(q, onProcess(q));
            }
        }

deliver方法只是负责根据event的状态调用合适的处理方法,各个子类也没有重写这个方法。
各个stage是在他们的onProcess方法内消耗event。

上面我们将stage分成了三类:“pre-ime”, “ime"和"post-ime”; 其实也可以从派发对象分类成:"native"和"view hierarchy"等,下面我们就看看"View hierarchy"对input event的派发。

3.1 Key event
pre-ime stage

对于key event,"pre-ime stage”是有拦截的,下面是ViewPreImeInputStage相关代码:

    final class ViewPreImeInputStage extends InputStage {
        public ViewPreImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            }
            return FORWARD;
        }

        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;
            if (mView.dispatchKeyEventPreIme(event)) {
                return FINISH_HANDLED;
            }
            return FORWARD;
        }
    }

代码比较简单,processKeyEvent会调用"mView.dispatchKeyEventPreIme(event)"进行处理,这里的"mView"是DecorView。dispatchKeyEventPreIme依然是ViewGroup中的方法:

    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            return super.dispatchKeyEventPreIme(event);
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            return mFocused.dispatchKeyEventPreIme(event);
        }
        return false;
    }

满足if的条件后调用"super.dispatchKeyEventPreIme(event)", 这个"super"是View.java,默认实现最终是直接返回false。如果满足"else if"后会调用当前焦点View的dispatchKeyEventPreIme方法,如果焦点View重新了该方法就可以消耗这个input event。

post-ime stage

我们直接跳过ime stage,看看post-ime stage对key event的处理。
ViewPostImeInputStage的onProcess方法如下:

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }

如果是KeyEvent,那么会调用processKeyEvent方法处理,此方法会调用DecorView的dispatchKeyEvent方法,下面看看这个方法:

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        final int keyCode = event.getKeyCode();
        final int action = event.getAction();
        final boolean isDown = action == KeyEvent.ACTION_DOWN;
        ......
        //省略shotcut相关code
        
        if (!mWindow.isDestroyed()) {
            final Window.Callback cb = mWindow.getCallback();//mWindow是PhoneWindow对象引用,而callback是Activity
            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                    : super.dispatchKeyEvent(event);
            if (handled) {
                return true;
            }
        }

        return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
                : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
    }

此方法对Key event的派发顺序如下:

  1. 如果Call back不为null,并且mFeatureId小于0, 那么调用call back的dispatchKeyEvent方法,Activity就属于这种情况。
  2. 如果Key event没有被call back处理掉,那么就调用PhoneWindow的onKeyDown/onKeyUp方法。

下图是这个方法的流程图:
在这里插入图片描述

上面只是说了ViewPostImeInputStage的部分代码,细节部分还是要看代码。

3.2 Motion event

对于来自屏幕(SOURCE_TOUCHSCREEN)的Touch event, “pre-ime stage"是不会做处理的,所以直接看"post-ime stage”,依然还是上面提到的ViewPostImeInputStage,onProcess方法的代码如下:

  @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                //触摸事件的source是SOURCE_TOUCHSCREEN,
                //而SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }

对于touch event,ViewPostImeInputStage会调用processPointerEvent方法进行处理,后者会调用DecorView的dispatchPointerEvent方法,DecorView间接继承了View但并没有重写dispatchPointerEvent方法,所以仍然是View中的实现:

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

这个方法调用了dispatchTouchEvent方法,DecorView重写了这个方法,代码如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

下图是调用进入dispatchTouchEvent@DecorView后的简单处理流程:
在这里插入图片描述
如果没有"Callback", 那么会调用DecorView间接父类ViewGroup中的dispatchTouchEvent方法。有”Callback“的情况会比较复杂一些,以"Callback"为Activity为例; Activity可以重写dispatchTouchEvent方法,按照需求对Touch event进行处理; 图中是Activity.java源码中的实现,代码如下:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

此方法调用PhoneWindow的superDispatchTouchEvent方法,后者会调用DecorView的superDispatchTouchEvent方法,最终调用ViewGroup的dispatchTouchEvent方法进行处理,如果没有消耗掉事件,那么再由Activity(onTouchEvent方法)进行处理。

在上面的派发过程中,我们发现DecorView的dispatchTouchEvent方法会被调用(如果Callback没有拦截)。dispatchTouchEvent方法的定义在View.java中,ViewGroup重写了这个方法,添加了自己的处理逻辑; ViewGroup的子类也可以按照自己的需求进行重写,但是DecorView作为顶层的ViewGroup,并没有重写这个方法,正常情况下它会将Event交给子view进行处理,当然对于没有重写该方法的ViewGroup也是一样的。

dispatchTouchEvent方法会调用onInterceptTouchEvent和onTouchEvent方法,这三个方法在整个View体系的派发过程中扮演着重要的角色:

  • dispatchTouchEvent
    如果Event能够派发到View,那么它的dispatchTouchEvent方法必然会被调用。返回结果受当前View的onTouchEvent方法和子View的dispatchTouchEvnet方法影响,表示是否消耗当前事件。
  • onInterceptTouchEvent
    此方法是ViewGroup中定义的方法,在dispatchTouchEvent方法内部调用,用于判断当前View是否拦截事件。如果拦截,那么在这个事件序列中,此方法不会再被调用。返回结果表示是否拦截当前事件。
  • onTouchEvent
    在dispatchTouchEvent方法中调用,返回结果表示是否消耗当前事件; 如果不消耗,那么事件就会交给它的父View处理,在同一事件序列中,当前View无法再次接收事件。
3.2.1 ViewGroup的dispatchTouchEvent方法

ViewGroup的dispatchTouchEvent方法的代码挺多, 就不贴了,画了一个简单的流程图:
在这里插入图片描述

从手指触摸屏幕,到手指离开屏幕,这算一个事件序列; 这个事件序列从down事件开始,up事件结束,中间有数量不定的move事件。
mFirstTouchTarget可以理解为单链表,dispatchTouchEvent每次在收到down事件时都会把这个单链表清空,只有当子View消耗事件后,这个链表才会追加新的Touch target。我们看看这块代码,
也只有满足三个条件中的一个才会有机会追加链表, 所以对于down–move–up这样的事件序列,mFirstTouchTarget最多也只有一个节点。

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                       ....
                }
3.2.2 View的dispatchTouchEvent方法

下面的流程图是View.java中dispatchTouchEvent方法的逻辑:
在这里插入图片描述
View没有子view,所以会自己处理event。如果View中有"OnTouchListener",那么Listener的onTouch方法会先被调用; 如果没有消耗掉event,那么View的onTouch方法会被调用; 如果是"ACTION_UP",而且有"OnClickListener", 如果View的onTouch方法没有消耗event,那么Listener的onClick方法会被调用。

3.2.3 举例说明

在这里插入图片描述
在上面的图中,DecorView中的布局包含了一个ViewGroup: "view-group"和一个View:“view-1”, "view-group"显示顺序比View靠前(Z值大); "view-group"中包含了两个View:“view-2"和"view-3”,“view-2"的显示顺序要比"view-3"靠前。
当有input touch event时,dispatchTouchEvent@DecorView方法会先将event派发给"view-group”,“view-group"的dispatchTouchEvent方法会优先调用"view-2"的dispatchTouchEvent方法进行处理(onTouchEvent会被调用);
如果"view-2"没有消耗event,那么"view-group"会将event派发给"view-3"处理;
如果"view-3"也没有消耗event,那么"view-group"的dispatchTouchEvent和onTouchEvent方法会被调用;
如果"view-group"仍然没有被消耗掉event,那么DecorView就会将event派发给"view-1”;
如果"view-1"没有消耗掉event,那么DecorView会调用自己间接父类View的dispatchTouchEvent方法进行处理,这样自己的onTouchEvent会被调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值