Android中MotionEvent的来源和ViewRootImpl

前言

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

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

Android中点击事件的来源

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

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

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

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

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

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

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

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

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

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

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

这个消息的处理如下:

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

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

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

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

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

例子

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

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

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

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

1 1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部