android touch事件解析 (从wms到viewgroup)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuezhe__/article/details/50977747



不多说,进入正题:


关于Android中的Touch事件分发, 分为 服务端WindowManagerService(WmS,以下都以WmS简称), 负责采集与分发. 经过一些列调用, 会分发到client端 viewrootimpl中, 然后viewrootimpl进行client端的touch事件分发



首先我们要知道的是,在WmS启动后,经过逐层调用,会在native层启动InputReaderThread,InputDispatchThread,这两个线程负责touch事件的采集和分发,前者用来读取输入事件,后者则用来分发事件. 经过native的层层调用,最后会传递到ViewRootImpl的内部类 WindowInputEventReceiver中.

那么WmS是如何传递到ViewRootImpl的呢? 我们来分析..




 

我们需要知道的是,Touch事件从WmS传递到client 用的并不是IPC机制, 而是内存管道和共享. 上面图中 只有WmS、InputQueue、InputManager、ViewRootImpl 是在framework层中实现的. 其他则是在native层.

在WmS中,InputReaderThread 会不断从EventHub 中取出touch事件,InputDispatchThread分发事件, 然而分发事件实际操作的是InputPublisher.它内部保存了两个指针. 一个是指向服务端的 InputChannel指针,一个是指向ShareMemory(共享内存)的指针,当有事件要分发时,会把事件写入到ShareMemory中. 并向InputChannel传递一个特定的字符串,由InputChannel 将该字符串写入到管道中.

一旦client端的InputChannel从管道中读取到有事件过来.就会通知InputConsumer从ShareMemory中取出事件.并传递到InputQueue中,最后进入ViewRootImpl. 当事件消费完毕,client会回传通知WmS.


以上就是从WmS到client端的大体流程,下面我们就来重点说ViewRootImpl内的事件传递.


当事件传递过来后会被 WindowInputEventReceiver接口到 并进行 一系列调用, onInputEvent -> enqueueInputEvent  ->  doProcessInputEvents -> deliverInputEvent   我们可以看到,最终会调用deliverInputEvent(QueuedInputEvent q) 这个方法. 


在说到deliverInputEvent这个方法前, 我们首先要知道一个概念:

在ViewRootImpl中,有一系列InputStage概念, 每种InputStage可以处理一定的事件类型,它有很多子类 如AsyncInputStage、ViewPreImgInputStage、ViewPostImeInputStage等.当一个InputEvent到来时,ViewRootImpl会找合适的stage来处理. 对于点击事件来说,ViewPostImeInputStage 可以处理. 这时我们来看deliverInputEvent方法.

    private void deliverInputEvent(QueuedInputEvent q) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                q.mEvent.getSequenceNumber());
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }
        //在ViewRootImpl中,有一系列InputStage(输入舞台事件)概念.
        //每种InputStage可以处理一定的事件类型.
        // 如:AsyncInputStage、ViewPreImeInputStage、ViewPostImeInputStage等.
        // 当一个InputEvent到来时,ViewRootImpl会寻找合适的InputStage来处理.
        //对于点击事件来说, ViewPostImeInputStage可以处理.
        //ViewPostImeInputStage可以处理 中有一个processPointerEvent方法.
        //在该方法中会调用 mView的dispatchPointerEvent方法, mView 就是DecorView.
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

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

可以看到.最终会调用 stage.deliver(q) 
     /**
         * Delivers an event to be processed.
         */
        public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                finish(q, false);
            } else {
                apply(q, onProcess(q));
            }
        }
在deliver经过一系列调用后, 会调用onPrucess这个方法. 而 ViewRootImeInputStage 重写了这个方法. 我们来看看它里面是怎么实现的.


   @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                // If delivering a new non-key event, make sure the window is
                // now allowed to start updating.
                handleDispatchWindowAnimationStopped();
                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);
                }
            }
        }
可以看到, 在这里最后会调用 processPointerEvent(q) 这个方法.


在processPointerEvent(q) 中, 会调用ViewRootImpl 内部变量 mView.dispatchPointerEvent(event) 这个方法, 看到这里会有很多人问了, mView是什么 ?  它是个DecorView, 是在Activity setContentView后 创建的,最后经过ActivityThread内的 handleResumeActivity 方法 通过 wm.addView, 最后调用了WindowManagerGlobal 内的addView, 在这里会创建ViewRootImpl, 之后会调用它的 setView方法,将DecorView传递进去,并赋值给mView变量.  关于这方面内容,不在详细介绍~


经过上面说明我们知道,mView 是DecorView, 由于DecorView内没有dispatchPointerEvent方法, 所以会调用它的超父类View 内部的 dispatchPointerEvent方法.

public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }
在这里,先判断是否是touchEvent,该方法会调用natvice层的代码. 不在叙述. 我们要知道的是, 这里会调用dispatchTouchEvent方法,而DecorView重写了该方法. 所以会调用到DecorView的dispatchTouchEvent,我们来看看它
  @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback();
            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                    : super.dispatchTouchEvent(ev);
        }


在该方法内 首先会调用getCallback()对象. 它实际就是一个Activity, 在ActivityThread内经过handleLaunchActivity ->performLaunchActivity, 在这里会创建activity,并调用activity的attach方法, 在attach方法内.  会创建个 PhoneWindow,并 调用phoneWindow.setCallback(this);然后这里会经过 三个判断, 全部满足则调用 activity的 dispatchToucherEvent, 否则调用 父类的dispatchTouchEvent.  这里其实最终都会调用到 super.dispatchTouchEvent方法.  

我们来看看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();
        }
        //最终会执行到ViewGroup的 dispatchTouchEvent, 如果该方法返回true,则不会执行到自身的onTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
第一个if内的方法,onUserInteraction 是个空实现. 我们来看下个if. 如果该方法返回false, 则会调用它自身的onTouchEvent方法.

  /**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }


这里会调用 Window类的 shouldCloseonTouch, 如果返回true就会关闭掉当前页. 来看看 shouldCloseonTouch
   /** @hide */
    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        //如果mCloseOnTouchOutside(对应xml中 android:windowCloseOnTouchOutside) 为true 并且是当事件, 并且down事件在activity范围之外, 返回true
        //该情况同于Dialog
        if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
    }

由注释可知,实际dialog 就是这种形式存在的.  会到Activity的 dispatchTouchEvent内, 如果第二个if返回true,那么结果就为true, 我们看看if内的方法. 会调用PhoneWindow内的superDispatchTouchEvent, 然后会调用DecorView的superDispatchTouchEvent 最终调用到ViewGroup的dispatchTouchEvent(event).


以上.就是事件传递到ViewGroup内的过程.





阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页