Input机制之-APP侧内部分发

InputStage

由之前的分析,我们知道上层接收事件的入口是InputEventReceiver.dispatchInputEvent方法,但其中就一行代码,调用onInputEvent

所以对于java层的按键分发从ViewRootImpl.java的WindowInputEventReceiver中的onInputEvent开始

        @Override
        public void onInputEvent(InputEvent event) {
                ...
                enqueueInputEvent(event, this, 0, true);
        }
        
    void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        //这里processImmediately为true
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }
    
    void doProcessInputEvents() {
        // 等待队列中其他的输入事件处理完成,调用deliverInputEvent分发这个QueuedInputEvent
        while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            ...
            deliverInputEvent(q);
        }
        ...
    }
    
    
    private void deliverInputEvent(QueuedInputEvent q) {
        
            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }

            if (stage != null) {
                stage.deliver(q);
            } else {
                finishInputEvent(q);
            }
    }

这里主要调用InputStage的deliver方法进行分发,InputStage代表了输入事件的处理阶段,使用责任链模式设计模式,这些InputStage是在ViewRootImpl的setView时生成的,下一个InputStage对象持有上一个对象的引用,当自己处理不了这个事件时交由下一个处理

                // 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对象未处理,调用forward方法给下一个InputStage对象处理
        protected static final int FORWARD = 0;
        //本InputStage对象已处理,直接finish
        protected static final int FINISH_HANDLED = 1;
        //消息虽然没有处理,但是要结束,调用finish方法结束
        protected static final int FINISH_NOT_HANDLED = 2;
        
        public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                //分发给下一个InputStage对象来处理
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                //加FLAG_FINISHED_HANDLED标签,后面的InputStage对象不会处理
                finish(q, false);
            } else {
                final int result;
                try {
                    //实现类重写此方法,真正事件的分发操作是从这开始的
                    result = onProcess(q);
                } finally {
                }
                //根据result做出对应处理
                apply(q, result);
            }
        }

在这里插入图片描述

InputStage将输入事件的处理分成若干个阶段(Stage), 如果当前有输入法窗口,则事件处理从 NativePreIme 开始,否则从EarlyPostIme 开始,事件依次经过每个Stage,如果该事件没有被标识为 “Finished”, 该Stage就会处理它,然后返回处理结果Forward 或 Finish,Forward 运行下一个Stage继续处理,而Finished事件将会简单的Forward到下一级,直到最后一级 SyntheticInputStage。

在讲解Input事件在Activity中的分发逻辑之前,我们先来基本了解一下Activity到View的层次结构
在这里插入图片描述

Activity的UI结构。其实每个Activity中都包含一个Window对象,通常,Android中的Window是由PhoneWindow实现的。而PhoneWindow又将一个DecorView设置为整个窗口的根View(DecorView是一个ViewGroup)。

DecorView里面又有两个View,一个是用作title或者导航栏的,另外一个是ID为content的FrameLayout用来装我们加写的Xml文件布局的View。这也就是我们给Activity设置布局的方法命名为setContentView的原因。我们如果在AndroidStadio中创建一个Activity,setContentView,我们的加载布局的只是title下面那一块矩形区域,然后上面一般是显示我们App名称的一个title。

Key事件

其中ViewPostImeInputStage就是处理事件如何从Activity分发到View的
base/core/java/android/view/ViewRootImpl.java

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

        @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);
                }
            }
        }
    }

按键和触摸事件处理有一定区别,我们先看按键事件

        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;
            // 将按键传递到视图层次结构。
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }
        }

mView变量是在setview方法中赋值的,对于应用窗口来说, mView变量指向PhoneWindow的内部类DecorView对象。调用dispatchKeyEvent函数,按键从根节点开始自上而下分发
base/core/java/com/android/internal/policy/DecorView.java

    public boolean dispatchKeyEvent(KeyEvent event) {
        ...

        if (!mWindow.isDestroyed()) {
            final Window.Callback cb = mWindow.getCallback();
            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);
    }

base/core/java/android/app/Activity.java

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setCallback(this);
    }

这里Window.setCallback函数将activty以this的方式注册进PhoneWindow中,所以cb就是activty,在PhoneWindow初始化时会生成DecorView对象,该函数中传入的mFeatureId是-1,所以此处会调用Activity的dispatchKeyEvent函数,开始在View中分发按键。因此这里继续分析Activity.dispatchKeyEvent,并且若返回为true,消费此事件。
base/core/java/android/app/Activity.java

    public boolean dispatchKeyEvent(KeyEvent event) {

        // PhoneWindow.superDispatchKeyEvent,实际调用的是DecorView的superDispatchKeyEvent,从DecorView开始从顶层View往子视图传递
        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        //到这里如果view层次结构没有返回true,则交给KeyEvent本身的dispatch方法,Activity的onKeyDown/onKeyUp/onKeyMultiple会被触发
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }

若PhoneWindow.superDispatchKeyEvent没有消费此事件,,即返回false,就会走到KeyEvent.dispatch中
base/core/java/android/view/KeyEvent.java

public final boolean dispatch(Callback receiver, DispatcherState state,
            Object target) {
        switch (mAction) {
            case ACTION_DOWN: {
                boolean res = receiver.onKeyDown(mKeyCode, this);
                return res;
            }
            case ACTION_UP:
                return receiver.onKeyUp(mKeyCode, this);
            case ACTION_MULTIPLE:
               ...
                return false;
        }
        return false;
    }

此方法相对简单,我们先分析,这里的receiver对象其实就是Activity,Activity不仅继承了Window.Callback,还继承了KeyEvent.Callback,这里就可以通过这个接口回调到onKeyDown和onKeyUp方法
base/core/java/android/app/Activity.java

public class Activity extends ..., Window.Callback, KeyEvent.Callback,...
    public final boolean dispatch(Callback receiver, DispatcherState state,
            Object target) {
        switch (mAction) {
            case ACTION_DOWN: {
                boolean res = receiver.onKeyDown(mKeyCode, this);
                ...
            }
            case ACTION_UP:
                return receiver.onKeyUp(mKeyCode, this);
                ...
        }
        return false;
    }

所以我们平常写APP时重写onKeyDown,onKeyUp方法就能获取到按键事件其实就是靠的这个逻辑,当然这些是建立在事件没有被提前消费这个基础上,若事件提前被PhoneWindow.superDispatchKeyEvent消费掉了,onKeyDown这些方法也就不会被触发了
base/core/java/com/android/internal/policy/PhoneWindow.java

    public boolean superDispatchKeyEvent(KeyEvent event) {
        return mDecor.superDispatchKeyEvent(event);
    }

这里的mDecor对象就是DecorView
base/core/java/com/android/internal/policy/DecorView.java

    public boolean superDispatchKeyEvent(KeyEvent event) {
        if (super.dispatchKeyEvent(event)) {
            return true;
        }
    }

我们看到这里他又会到了DecorView中,即整个调用过程大概是这样的:
ViewPostImeInputStage.processKeyEvent–>DecorView.dispatchKeyEvent–>Activity.dispatchKeyEvent–>PhoneWindow.superDispatchKeyEvent–>DecorView.superDispatchKeyEvent
而DecorView extends FrameLayout --> extends ViewGroup

所以最终这里进入到ViewGroup的层次结构,接着会调用ViewGroup.dispatchKeyEvent方法,代码实现如下
base/core/java/android/view/ViewGroup.java

    public boolean dispatchKeyEvent(KeyEvent event) {
        
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            //ViewGroup是focused并且具体的大小被设置了
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            //ViewGroup中有focused的child,且child有具体的大小
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }
        return false;
    }

这里处理逻辑是:如果ViewGroup是focused并且具体的大小被设置了(有边界)则交给它处理,这里的super指的是父类View的方法,如果此ViewGroup中有focused的child,且child有具体的大小,则交给mFocused处理。这里可以看出如果ViewGroup满足条件,则优先处理事件而不发给子视图去处理。下面看下View的dispatchKeyEvent实现

public class View implements KeyEvent.Callback
    public boolean dispatchKeyEvent(KeyEvent event) {

        //调用onKeyListener
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }
        //调用View的onKeyUp/onKeyDown
        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }

        return false;
    }

如果View注册了OnKeyListener,并且View属于Enable状态,则触发调用OnKeyListener,然后直接返回true;如果没有,则调用view的onKeyUp/onKeyDown方法并返回true

  • 分发过程中所有方法都是boolean类型函数调用,若返回true表示这个方法消费了这个事件,事件不继续进行传递

总结一下keyEvent从Activity->View的分发流程就是

=>Activity的dispatchKeyEvent 
==>PhoneWindow的superDispatchKeyEvent 
===>DecorView的superDispatchKeyEvent,DecorView的superDispatchKeyEvent再调用父类的dispatchKeyEvent 
===>DecorView是窗口中的顶级视图,按键从DecorView开始往子节点分发,实际调用ViewGroup的dispatchKeyEvent
===>ViewGroup中会先判断是否可以处理KeyEvent;如果可以则调用父类(View)的dispatchKeyEvent,如果当前的ViewGroup不满足条件,则调用mFocused的dispatchKeyEvent,这里的mFocused是焦点子视图,也可以是含有焦点子视图的ViewGroup,因此这里可能会发生递归调用。 
====> 在View的dispatchKeyEvent中会先调用onKey函数,即会调用各个View注册的View.OnKeyListener对象的接口 
====> 在View的dispatchKeyEvent中接着调用KeyEvent的dispatch函数,因为View实现了Window.Callback函数,因此会调用View的onKeyDown/onKeyUp/onKeyMultiple函数
==>调用KeyEvent的dispatch函数,因为Activity实现了Window.Callback函数,因此会调用Activity的onKeyDown/onKeyUp/onKeyMultiple函数 
=>如果整个View层次都没有返回true,则调用PhoneWindow的onKeyDown/onKeyUp函数

在这里插入图片描述

DecorView.dispatchKeyEvent
        |----->Activity.dispatchKeyEvent
        |       |----->PhoneWindow.supDispatchKeyEvent
        |       |       |       |----->DecorView.superDispatchKeyEvent-->ViewGroup.dispatchKeyEvent
        |       |       |       |                                                               |----->super.dispatchKeyEvent-->View.dispatchKeyEvent
        |       |       |       |                                                               |----->ViewGroup.dispatchKeyEvent
        |       |       |       |                                                               |----->View.dispatchKeyEvent
        |       |----->KeyEvent.dispatch-->Activity.onKeyDown/onKeyUp
        |----->PhoneWindow.onKeyDown/onKeyUp
Touch事件

Touch事件和Key事件传递的过程有点类似,最主要不一样的地方就是Key事件只要分发给focus的View就可,而Touch因携带坐标信息,需要计算坐标位置,而且在复杂嵌套布局中还要处理Touch事件传递过程中的滑动冲突问题

ViewGroup的分发事件主要是在dispatchTouchEvent中实现的,View中也有同样的方法
base/core/java/android/view/ViewGroup.java

    public boolean dispatchTouchEvent(MotionEvent ev) {

            // 检查mGroupFlags和onInterceptTouchEvent判断是否拦截事件
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                } else {
                    intercepted = false;
                }
            } 

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);

                        final View[] children = mChildren;
                        //遍历所有子布局
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            //判断子布局能否获得触发事件,计算触摸点xy坐标是否在View内部
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }
                            //传入child,调用dispatchTransformedTouchEvent
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                ...
                                break;
                            }
                        }
                    }
                }
            }
                //传入null,表示没有或者找不到子布局
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
        
        return handled;
    }
    
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            //child为null,说明没有子布局或者找不到符合要求的子布局,调用父类View的dispatchTouchEvent
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            //找到子布局,根据子布局位置调整xy坐标,调用子布局的dispatchTouchEvent方法
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);

            handled = child.dispatchTouchEvent(transformedEvent);
        }
        return handled;
    }

检查拦截:

  • disallowIntercept 是用来判断子view是否允许父控件拦截的,子view可以调用parent.requestDisallowInterceptTouchEvent方法,来控制该属性,并且这个属性会沿着目录树向上传递
  • onInterceptTouchEvent 是用来判断ViewGroup本身是否需要拦截Touch事件,优先级低于disallowIntercept属性
    //子布局调用parent.requestDisallowInterceptTouchEvent
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }
        //修改mGroupFlags标识
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }
    //自定义ViewGroup可以重写这个方法来修改Touch事件拦截逻辑
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

当然如果是自定义的ViewGroup的话,也完全可以直接重写dispatchTouchEvent整个方法达到拦截事件的目的。
说完ViewGroup,我们来看一下View的dispatchTouchEvent方法的实现

    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;

            ListenerInfo li = mListenerInfo;
            //mOnTouchListener,通过View.setOnTouchListener可以设置Touch监听
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
             //没有设置mOnTouchListener,通过默认onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        return result;
    }
    
    public boolean onTouchEvent(MotionEvent event) {
        ...
        final int action = event.getAction();
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            //控件不是enable,直接返回
            return clickable;
        }
            switch (action) {
                case MotionEvent.ACTION_UP:
                    //clickable,控件可点击的,通过View.setClickable
                    if (!clickable) {
                        break;
                    }
                    ...
                    //clickable=true,调用performClickInternal
                    performClickInternal();
                    break;
            }
            return true;
        }
        return false;
    }
    
    
    private boolean performClickInternal() {
        return performClick();
    }
    
    public boolean performClick() {

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        //mOnClickListener,通过View.setOnClickListener可以设置点击的监听
        if (li != null && li.mOnClickListener != null) {
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        return result;
    }

总结:
Activity:

  • 首先会触发Activity的dispatchTouchEvent方法。
  • 接着在dispatchTouchEvent方法中会通过Activity的root View即DecorView(id为content的FrameLayout),实质是ViewGroup,通过super.dispatchTouchEvent把touchevent派发给子view,也就是我们再Activity.onCreat方法中setContentView时设置的view。
  • 若Activity下面的子view拦截了touchevent事件(返回true)则Activity.onTouchEvent方法就不会执行。

ViewGroup:

  • Android事件派发是先传递到最顶级的ViewGroup,再由ViewGroup递归传递到View的。
  • 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
  • 子View中如果将传递的事件消费掉,ViewGroup中将结束这个事件的分发。

View:

  • 触摸控件(View)首先执行dispatchTouchEvent方法。
  • 若View通过setOnTouchListener,设置了监听,执行OnTouchListener.onTouch
  • 如果控件监听的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调用onTouchEvent
  • 如果控件不是enable的设置了onTouch方法也不会执行,否则若设置了setOnClickListener,执行OnClickListener.onClick
  • 优先级:onTouchListener.onTouch > View.onTouchEvent > mOnClickListener.onClick
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值