Android中MotionEvent的来源和ViewRootImpl

前言

很久没有发表文章了,今天来一篇,大家撒花~~~

本文打算分析下Android中点击事件的来源,顺便提及下ViewRootImpl。

Android中点击事件的来源

这个问题,也许你会说“这还用你说吗?我可是看过艺术探索的人”,我知道艺术探索中的确是详细介绍了点击事件的传递流程,反正大致就是点击事件从Activity传递给PhoneWindow,然后PhoneWindow再传递给DecorView,接着DecorView就进行后续的遍历式的传递。这都没错,但是点击事件是谁传递给Activity的呢?这个大家可能不清楚吧?那本文就是分析这个问题的。

首先看Activity的实现,如下,Activity实现了一个特殊的接口:Window.Callback。

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
    private static final String TAG = "Activity";
    private static final boolean DEBUG_LIFECYCLE = false;

那么Window.Callback到底是什么东西呢?如下:

    /**
     * API from a Window back to its caller.  This allows the client to
     * intercept key dispatching, panels and menus, etc.
     */
    public interface Callback {
        /**
         * Called to process key events.  At the very least your
         * implementation must call
         * {@link android.view.Window#superDispatchKeyEvent} to do the
         * standard key processing.
         *
         * @param event The key event.
         *
         * @return boolean Return true if this event was consumed.
         */
        public boolean dispatchKeyEvent(KeyEvent event);

        /**
         * Called to process touch screen events.  At the very least your
         * implementation must call
         * {@link android.view.Window#superDispatchTouchEvent} to do the
         * standard touch screen processing.
         *
         * @param event The touch screen event.
         *
         * @return boolean Return true if this event was consumed.
         */
        public boolean dispatchTouchEvent(MotionEvent event);

        /**
         * Called to process trackball events.  At the very least your
         * implementation must call
         * {@link android.view.Window#superDispatchTrackballEvent} to do the
         * standard trackball processing.
         *
         * @param event The trackball event.
         *
         * @return boolean Return true if this event was consumed.
         */
        public boolean dispatchTrackballEvent(MotionEvent event);
        ...(省略若干代码,下同)

然后我们似乎看出了一些端倪,难道这个接口和点击事件的传递有关?嗯,你猜对了。在艺术探索这本书中,并没有描述事件是如何传递给Activity的,但是这里我们可以猜测,如果外界想要传递点击事件给Activity,那么它就必须持有Activity的引用,这没错,在Activity的attach方法中,有如下一段:

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);

显然,mWindow持有了Activity的引用,它通过setCallback方法来持有Activity,因此,事件是从Window传递给了Activity。也许你会说:“我不信,这理由不充分!”,没关系,我们继续分析。

我们知道,Activity启动以后,在它的onResume以后,DecorView才开始attach给WindowManager从而显示出来。(什么?你不知道?回去看艺术探索第8章),请看Activity的makeVisible方法,代码如下:

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

接着,系统就会完成添加Window的过程,看WindowManagerGlobal的addView方法,如下:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ViewRootImpl root;
        View panelParentView = null;
        ...这里省略了一堆代码
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

可以看到,ViewRootImpl创建了,在ViewRootImpl的setView方法(此方法运行在UI线程)中,会通过跨进程的方式向WMS(WindowManagerService)发起一个调用,从而将DecorView最终添加到Window上,在这个过程中,ViewRootImpl、DecorView和WMS会彼此向关联,同时会创建InputChannel、InputQueue和WindowInputEventReceiver来接受点击事件的消息。

好了,言归正传,下面来说,点击事件到底怎么传递给Activity的。首先要明白,点击事件是由用户的触摸行为所产生的,因此它必须要通过硬件来捕获,然后点击事件会交给WMS来处理。

在ViewRootImpl中,有一个方法,叫做dispatchInputEvent,如下:

    public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = event;
        args.arg2 = receiver;
        Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }

那么什么是InputEvent呢?InputEvent有2个子类:KeyEvent和MotionEvent,其中KeyEvent表示键盘事件,而MotionEvent表示点击事件。在上面的代码中,mHandler是一个在UI线程创建的Handder,所以它会把执行逻辑切换到UI线程中。

    final ViewRootHandler mHandler = new ViewRootHandler();

这个消息的处理如下:

            case MSG_DISPATCH_INPUT_EVENT: {
                SomeArgs args = (SomeArgs)msg.obj;
                InputEvent event = (InputEvent)args.arg1;
                InputEventReceiver receiver = (InputEventReceiver)args.arg2;
                enqueueInputEvent(event, receiver, 0, true);
                args.recycle();
            }

除此之外,WindowInputEventReceiver也可以来接收点击事件的消息,同样它也有一个dispatchInputEvent方法,注意,WindowInputEventReceiver中的Looper为UI线程的Looper。

    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
             Looper.myLooper());

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
    }

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

可以发现,不管是ViewRootImpl的dispatchInputEvent方法,还是WindowInputEventReceiver的dispatchInputEvent方法,它们本质上都是调用deliverInputEvent方法来处理点击事件的消息,如下:

    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 {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

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

在ViewRootImpl中,有一系列类似于InputStage(输入事件舞台)的概念,每种InputStage可以处理一定的事件类型,比如AsyncInputStage、ViewPreImeInputStage、ViewPostImeInputStage等。当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理。对于点击事件来说,ViewPostImeInputStage可以处理它,ViewPostImeInputStage中,有一个processPointerEvent方法,如下,它会调用mView的dispatchPointerEvent方法,注意,这里的mView其实就是DecorView。

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

            mAttachInfo.mUnbufferedDispatchRequested = false;
            boolean handled = mView.dispatchPointerEvent(event);
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

在View的实现中,dispatchPointerEvent的逻辑如下,这样一来,点击事件就传递给了DecorView的dispatchTouchEvent方法。

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

DecorView的dispatchTouchEvent的实现如下,需要强调的是,DecorView是PhoneWindow的内部类,还记得前面提到的Window.Callback吗?没错,在下面的代码中,这个cb对象其实就是Activity,就这样点击事件就传递给了Activity了。

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

例子

写一个简单的例子,验证下。选择一个View,重写其onTouchEvent方法,然后通过dumpStack方法来打印出当前线程的调用栈信息。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent, ev=" + event.getAction());
        Thread.dumpStack();
        return true;
    }

选择Google nexus 6运行一下,log如下所示:

06-22 13:25:21.368  7365  7365 D FrameLayoutEx: onTouchEvent, ev=0
06-22 13:25:21.368  7365  7365 W System.err: java.lang.Throwable: stack dump
06-22 13:25:21.368  7365  7365 W System.err:    at java.lang.Thread.dumpStack(Thread.java:490)
06-22 13:25:21.368  7365  7365 W System.err:    at com.ryg.reveallayout.ui.FrameLayoutEx.onTouchEvent(FrameLayoutEx.java:27)
06-22 13:25:21.368  7365  7365 W System.err:    at android.view.View.dispatchTouchEvent(View.java:9294)
06-22 13:25:21.368  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2547)
06-22 13:25:21.368  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2240)
06-22 13:25:21.368  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
06-22 13:25:21.369  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2197)
06-22 13:25:21.369  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
06-22 13:25:21.369  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2197)
06-22 13:25:21.369  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
06-22 13:25:21.369  7365  7365 W System.err:    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2197)
06-22 13:25:21.369  7365  7365 W System.err:    at com.android.internal.policy.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2403)
06-22 13:25:21.369  7365  7365 W System.err:    at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1737)
06-22 13:25:21.369  7365  7365 W System.err:    at android.app.Activity.dispatchTouchEvent(Activity.java:2765)
06-22 13:25:21.369  7365  7365 W System.err:    at com.android.internal.policy.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2364)
06-22 13:25:21.369  7365  7365 W System.err:    at android.view.View.dispatchPointerEvent(View.java:9514)
06-22 13:25:21.369  7365  7365 W System.err:    at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4230)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4096)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3787)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3844)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669)
06-22 13:25:21.370  7365  7365 W System.err:    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642)
06-22 13:25:21.371  7365  7365 W System.err:    at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5922)
06-22 13:25:21.371  7365  7365 W System.err:    at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5896)
06-22 13:25:21.371  7365  7365 W System.err:    at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5857)
06-22 13:25:21.371  7365  7365 W System.err:    at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6025)
06-22 13:25:21.371  7365  7365 W System.err:    at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
06-22 13:25:21.371  7365  7365 W System.err:    at android.os.MessageQueue.nativePollOnce(Native Method)
06-22 13:25:21.371  7365  7365 W System.err:    at android.os.MessageQueue.next(MessageQueue.java:323)
06-22 13:25:21.371  7365  7365 W System.err:    at android.os.Looper.loop(Looper.java:135)
06-22 13:25:21.371  7365  7365 W System.err:    at android.app.ActivityThread.main(ActivityThread.java:5417)
06-22 13:25:21.371  7365  7365 W System.err:    at java.lang.reflect.Method.invoke(Native Method)
06-22 13:25:21.371  7365  7365 W System.err:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
06-22 13:25:21.371  7365  7365 W System.err:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

通过上述log,大家不难看出MotionEvent的来源以及传递顺序,本文止。

另外,就在今天,我的微信公众号 “Android开发艺术探索” 竟然有打赏功能了,很新鲜,我打算体验一把,自己给自己打赏一下,大家就不要给我打赏了。

时间: 2024-10-07 14:56:44

Android中MotionEvent的来源和ViewRootImpl的相关文章

Android中的ViewRootImpl类源码解析

转载请注明出处 http://blog.csdn.net/qianhaifeng2012/article/details/51737370 ViewRoot目前这个类已经没有了,是老版本中的一个类,在Android2.2以后用ViewRootImpl代替ViewRoot,对应于ViewRootImpl.java,他是链接WindowManager和DecorView的纽带,另外View的绘制也是通过ViewRootImpl来完成的. 它的主要作用我的总结为如下: A:链接WindowManage

Android中dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent的理解

[转]http://blog.csdn.net/guitk/article/details/7057155 onInterceptTouchEvent用于改变事件的传递方向.决定传递方向的是返回值,返回为false时事件会传递给子控件,返回值为true时事件会传递给当前控件的onTouchEvent(),这就是所谓的Intercept(拦截). [tisa ps:正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回.即便需要拦截也应该直接返回true,然后由onTouchEvent方法进

Android中使用SurfaceView+MediaPlayer+自定义的MediaController实现自定义的视屏播放器

效果图如下: (PS本来是要给大家穿gif动态图的,无奈太大了,没法上传) 功能实现:暂停,播放,快进,快退,全屏,退出全屏,等基本功能 实现的思路: 在主布局中放置一个SurfaceView,在SurfaceView中放置一个MediaPlayer ,在其下方自定义一个MediaController,不过也不能称之为MediaController,使用的是PupupWindow来实现的,在PupupWindow布局中放置几个textView,Button,最重要的使我们的SeekBar控件,创

Android中的事件分发机制(下)——View的事件处理

综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

Android中的多点触摸

代码下载地址 代码一:自定义支持多点触摸的TextView http://download.csdn.net/detail/zhiyuan0932/9513852 什么是多点触摸 允许计算机用户同时通过多个手指来控制图形界面的一种技术 多点触摸的应用场景 对图片.文字.网页进行放大或者缩小 多手指手势操作自定义控件和布局 触摸事件的重要方法 event.getActionMasked(): 获取事件类型 在只使用单手指操作的时候,这个方法我们一般使用的是event.getAction(),来获取

Android中事件传递机制的总结

事件传递虽然算不上某个单独的知识点,但是在实际项目开发中肯定会碰到,如果不明白其中的原理,那在设计各种滑动效果时就会感到很困惑. 关于事件的传递,我们可能会有以下疑问: 事件是如何传递的 事件是如何处理的 自定义view的时候,事件也冲突了怎么解决 带着这三个疑问,我们来总结一下事件传递机制是怎么回事. 一.事件分发的原理: 1.事件是如何传递的: (1)首先由Activity分发,分发给根View,也就是DecorView(DecorView为整个Window界面的最顶层View) (2)然后

Android中图像变换Matrix的原理、代码验证和应用(二)

注:本篇文章为转载文章,因为原文格式排版较乱,但是内容非常棒,所以整理一下,方便以后查看. 查看原文请戳:http://blog.csdn.net/pathuang68/article/details/6991988 Matrix介绍文章请戳:http://blog.csdn.net/pathuang68/article/details/6991867 package com.pat.testtransformmatrix; import android.app.Activity; import

android中根据touch事件判断单击及双击

private static final int MAX_INTERVAL_FOR_CLICK = 250;     private static final int MAX_DISTANCE_FOR_CLICK = 100;     private static final int MAX_DOUBLE_CLICK_INTERVAL = 500;     int mDownX = 0;     int mDownY = 0;     int mTempX = 0;     int mTempY

Android中的帧动画的简单使用

Android中动画主要有下面几种,帧动画(frame),补间动画(tween),属性动画(property) 我们平时项目中主要用的是帧动画和补间动画 帧动画需要我们准备一组静态图片,这些图片是通过分解动画得来的,静态图片连起来播放形成动画效果 我们在res目录下新建一个drawable目录,用来存放动画资源和xml文件 图片如下: girl.xml 注意android:oneshot="false|true" 该属性控制动画是否重复播放,false代表重复播放 <?xml v