前言

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

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

Android中点击事件的来源

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

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

public class Activity extends ContextThemeWrapperimplements 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 thingstry {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);}@Overridepublic 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方法来打印出当前线程的调用栈信息。

    @Overridepublic 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开发艺术探索” 竟然有打赏功能了,很新鲜,我打算体验一把,自己给自己打赏一下,大家就不要给我打赏了。

Android中MotionEvent的来源和ViewRootImpl相关推荐

  1. Android中Activity、Window、ViewRootImpl与子线程更新UI

    三者层级关系 1.Window Window是一个抽象类,唯一的实现类是PhoneWindow Window分为三种类型应用Window.子Window.系统Window.子Window无法独立存在必 ...

  2. android motionevent 坐标,Android开发中MotionEvent坐标获取方法分析

    本文实例讲述了Android开发中MotionEvent坐标获取方法.分享给大家供大家参考,具体如下: Android MotionEvent中getX()与getRawX()都是获取屏幕坐标(横), ...

  3. Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理

    在Android中需要经常对用户手势进行判断,在判断手势时需要精细的分清楚每个触摸事件以及每个View对事件的接收情况,在View,ViewGroup,Activity中都可以接收事件,在对事件进行处 ...

  4. Android中的事件处理总结

    Android中的事件处理总结 Android系统对动作做出的响应机制就是事件处理.Android提供了两种事件处理机制. 基于监听的事件处理机制 基于回调的事件处理机制 一.基于监听的事件处理机制 ...

  5. Android中的四大组件详解

    Android中的四大组件详解 我们都知道Android系统应用层框架中,为开发者提供了四大组件来便于应用的开发,它们是Activity.Service.BroadcastReceiver.Conte ...

  6. 利用JNI技术在Android中调用C++形式的OpenGL ES 2.0函数

    1.                 打开Eclipse,File-->New-->Project--->Android-->AndroidApplication Projec ...

  7. Android中的ViewDragHelper

    我们在Android中经常会遇到需要滑动的场合,关于在Android如何实现滑动可以看我的这篇博客Android中实现滑动的七种方法,这篇博客我们会详细介绍一下Android中的ViewDragHel ...

  8. Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析

    转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...

  9. Android中Handler的使用

    在Android开发中,我们常常会遇到这样一种情况:在UI界面上进行某项操作后要运行一段非常耗时的代码,比方我们在界面上点击了一个"下载"button,那么我们须要运行网络请求,这 ...

最新文章

  1. 【SLAM建图和导航仿真实例】(一)- 模型构建
  2. 分享一下SecureCRT输出颜色和格式
  3. python基础练习(九)
  4. Forms Builder 学习笔记 1 ――安装
  5. typedef的用法总结
  6. setInterval()方法只执行一次的解决方法
  7. boost::hana::curry用法的测试程序
  8. [小技巧]C#中如何为枚举类型添加描述方法
  9. VLAN的Hybrid和Trunk端口有何区别
  10. PHP RSS/Feed 生成类库(支持RSS 1.0/2.0和ATOM)
  11. 继上海之后 美团买菜在北京推出测试服务站
  12. Nodejs模块初始化
  13. mysql8日志文件密码_mysql8 参考手册--mysqlbinlog-处理二进制日志文件的实用程序
  14. 扫描之王——NAMP
  15. ROLLUP函数问题
  16. windows资源监视器中内存项(提交、工作集、可共享、专用)的含义及区别
  17. 使用 emoji表情 实现自己的 表情库
  18. 方程检验格式图片_Excel绘制标准曲线全图片教程
  19. 80072745 80072efd 解决办法
  20. Pollard rho 算法求解离散对数问题

热门文章

  1. [附源码]Python计算机毕业设计大学生校园社团管理系统Django(程序+LW)
  2. MySql轻功-存储过程
  3. react 路由重定向_如何测试与测试库的路由器重定向React
  4. MPP(大规模并行处理)
  5. Markdown里面的序列图
  6. python循环输出1到10_用Python编写一个程序,使用for循环输出0~10之间的整数
  7. DB9公母头引脚定义以及连接
  8. pascal语法基础
  9. RISC-V架构中断定义
  10. Log4cpp: log4cpp快速使用指南