前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你读懂源码,View事件的注册和接收详细剖析

手把手教你读懂源码,View事件的注册和接收详细剖析

作者头像
分享达人秀
发布2018-02-02 16:59:09
6160
发布2018-02-02 16:59:09
举报
文章被收录于专栏:分享达人秀分享达人秀

关于Android的Touch事件传递机制,只是知道事件传入Activity后的流程,但是这些事件是如何传递给Activity的一直模糊不清。现在再来好好回顾一遍,顺道整理一点儿东西出来,同时分享给大家。

前面用源码带着大家分享过View的创建和加载,以及View的绘制流程,可以通过阅读原文跳转回顾。今天就随着Android源码一起来探寻一番Android中各View的Touch事件到底是怎么注册和接收的,虽然有一些大神做过分享,但是源码比较老旧,而且通过自己研究会掌握的更透彻一些。本文章主要分析Java部分,底层C++部分暂不分析。

Android输入系统的主要工作是读取设备节点中的原始事件,将其加工封装,然后派发给一个特定的窗口以及窗口中的控件,如一个活动状态的Activity界面中的Button。

同时我们知道Android系统是Linux内核的,它的事件处理也是在Linux的基础上完成的,因此我们从Linux 内核往应用这个方向慢慢理清它的大致处理过程。首先来看事件是如何注册的。

1、事件注册

从之前的View加载流程源码分析知道(如果不清楚,建议返回先回顾View的加载流程),在ActivityThread中的handleResumeActivity方法会调用wm.addView()方法,将View添加到mWindowManger上,即WindowManagerImpl类的addView方法,而其又是由WindowManagerGlobal代理的。在WindowManagerGlobal的addView方法中会先创建了一个ViewRootImpl对象,然后调用ViewRootImpl.setView()方法。

setView方法调用时序图

在setView方法中会找到如下代码:

ViewRootImpl类的setView方法

会发现在requestLayout后创建一个InputChannel对象,然后会调用addToDisplay方法,继续跟踪分析:

Session类的addToDisplay方法

该方法继续调用了WindowManagerService的addWindow方法:

WindowManagerService类的addWindow方法

该方法先创建了一个WindowState对象,然后调用了openInputChannel方法:

WindowState类的openInputChannel方法

这一段代码主要就是创建一对InputChannel,同时这一对InputChannel中实现了一组全双工管道。InputChannel创建完成后,会将其中一个的native InputChannel 赋值给outInputChannel,也就是对ViewRootImpl端InputChannel对象的初始化,这样随着ViewRootImpl和WindowManagerService两端的InputChannel对象的创建,事件传输系统的管道通信也就建立了起来。后续的具体内容暂时不是分析重点,以后有机会再进行剖析。

按键、触屏等事件是经由WindowManagerService获取,并通过共享内存和管道的方式传递给ViewRootImpl,ViewRootImpl再调用dispatch给Application。当有事件从硬件设备输入时,system_server端在检测到事件发生时,通过管道(pipe)通知ViewRootImpl事件发生,此时ViewRootImpl再去内存中读取这个事件信息。

下面的结构图比较经典,是从网络上找来的,有一部分类对不上最新代码。其中InputManager变成了InputManagerService,ViewRoot变成了ViewRootImpl。

Android事件处理结构图

2、事件接收

通过上面的分析知道在addView时将事件传输系统的管道建立了起来,那么随后当Linux检测到事件发生,会经过层层传递到ViewRootImpl中,就来一起分析一下事件是如何从Native传递给Activity的。

继续来分析ViewRootImpl类的setView方法,

事件处理关联mInputEventReceiver对象

这段代码先创建一个与当前窗口已经生成的InputChannel相关的接受输入事件的处理对象,最后设置当前各种不同类别输入事件到来时候按对应类型依次分别调用的处理对象。

上面分析的生成两个InputChannel输入事件通道,其中一个转移到当前顶层ViewRootImpl中并生成一个与输入事件通道关联的事件处理mInputEventReceiver对象,随着这条线索继续分析。

WindowInputEventReceiver类

看到WindowInputEventReceiver类继承了InputEventReceiver类:

InputEventReceiver类

注意InputEventReceiver类的dispatchInputEvent方法,当输入事件到来时该方法由native层代码发起调用,然后调用了onInputEvent(event)方法。从前面知道WindowInputEventReceiver类重写了onInputEvent方法,因此事件会传递到enqueueInputEvent方法。

enqueueInputEvent方法

该方法先获取一个指向当前事件的输入事件队列QueuedInputEvent对象,最后调用doProcessInputEvents方法:

doProcessInputEvents方法

只要当前待处理事件队列还有事件需要处理,就一直循环调用deliverInputEvent方法:

deliverInputEvent方法

这里会根据q.shouldSendToSynthesizer()和q.shouldSkipIme()判断给stage赋值,然后调用stage的deliver方法。

InputStage类

看到这里,是否感觉到熟悉了,这就是setView方法最后设置各种不同类别输入事件分别调用的处理对象。在setView方法中一共生成了6个InputStage的子类对象,分别是ViewPostImeInputStage、NativePostImeInputStage、EarlyPostImeInputStage、ImeInputStage、ViewPreImeInputStage、NativePreImeInputStage。如果不是当前事件就不断指向下一个,直到找到对应的对象。

这里我们分析的是Touch事件,所以只关注ViewPostImeInputStage。

ViewPostImeInputStage类

对于触摸事件,这里的(source & InputDevice.SOURCE_CLASS_POINTER) != 0为true,所以会调用processPointerEvent方法。

processPointerEvent方法

这里的View就是窗口顶层视图DecroView,所以接下来继续分析View的dispatchPointerEvent方法:

View的dispatchPointerEvent方法

这里调用了dispatchTouchEvent方法,由于DecorView重写了该方法,所以继续查看DecorView的dispatchTouchEvent方法:

DecorView的dispatchTouchEvent方法

这里做了一个判断,当cb!=null且mFeatureId<0是执行cb.dispatchTouchEvent(ev),否则执行super.dispatchTouchEvent(ev),也就是经过FrameLayout继承ViewGroup的dispatchTouchEvent方法。

这里的cb对象是调用mWindow(即PhoneWindow对象)的getCallback方法获取的,是在之前Activity的attach方法中创建PhoneWindow对象后调用setCallback时被赋值的:

调用setCallback方法

因此这里cb.dispatchTouchEvent(ev)即为Activity类的dispatchTouchEvent方法。

Touch事件传递到Activity后,就是我们经常看到的比较熟悉的Activity事件传递流程了。由于篇幅问题,我们下一篇再做具体分析,也欢迎关注,后续会继续推出更多精彩内容。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-03-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 分享达人秀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、事件注册
  • 2、事件接收
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档