Android系统输入事件分发详解
什么是输入事件?
我们知道,运行android系统的设备本质上是一台计算机,使用者在和计算机进行交互的时候可以抽象成简单的对计算机的输入和输出(IO)。那么对于运行在计算机上的操作系统来说,操作系统在与使用者进行交互的时候起始也是可以抽象成对外界的输入进行处理,然后在输出返还给使用者。本文只讨论的是android系统中的“输入事件”(因为本文讨论的都是输入事件,所以以下简称“输入事件”为“事件”)相关的内容。
根据以上描述,我们就可以回答上面的问题了:
事件是计算机设备接收到的输入信息。
有了以上的定义,我们先来研究下事件是怎么产生的?
在Android开发
系统中,事件是由用户的动作触发的,进而在android硬件层引起硬件状态的变化,android底层会将硬件的状态的变化封装成更加高级的类对象并传递到android 的Framework层,Framework会继续把事件传递给应用层(确切地说是应用层里面应用开发者编写的具体的View的子类对象),最终应用层层会根据事件来进行响应并将响应结果通过对硬件的调用反馈给用户。在以上的整个过程中,应用开发者只需要关注其中的一小部分就可以了,那就是事件从Framework层传递到应用层之后我们该怎么处理这些事件。当然,本文也主要讨论这些内容。
借用以下android 架构图来展示下事件从底层到应用层经历了多少过程。android架构图
注意:事件的传递并不是只传递一个单一的事件的,每一次用户的动作都会产生一个有序的事件列表,这个有序的事件列会被按顺序一次传递到应用层。
输入事件类型
android中所有的事件被抽象成了一个Java类,InputEvent。它有两个直接子类,MotionEvent和 KeyEvent,分别代表了动作事件(通常由触摸屏所产生,也是最常见的)的事件和按键所产生的事件。但是事实上InputEvent不止有这两种。因为android系统可运行的设备类型非常丰富,在不同的设备上对应不同的硬件,肯定也就回有不同的输入事件了,InputEvent按照事件的输入来源不同可以分为以下几类(非全部):
Sensor InputEvent(传感器输入事件)
Hook InputEvent(耳机Hook键输入事件)
On InputEvent(开机键输入事件)
Touch Screen InputEvent(触摸屏输入事件,也是本文主要介绍的)
Key InputEvent(按键输入事件,Home/Menu/Back,电视遥控器按键等)
Hover InputEvent(鼠标动作输入事件)
一般来说,android系统的运行设备是手机或者平板电脑,它们都会有触摸屏,最常见的输入事件还是Touch Screen InputEvent了。本文将会主要介绍TouchScreen InputEvent 和 Key InputEvent。android系统的输入设备列表参考这里:输入设备
Touch Screen InputEvent的处理和传递
Touch Screen InputEvent基础
Touch Screen InputEvent被封装成了一个java类-MotionEvent,它包含三个基础的事件,其它的事件都是在者三个事件的基础之上进行组合封装而成的,例如,click事件就是一个ACTION_DOWN 和一个 ACTION_UP的组合。
基础事件:
ACTION_DOWN 手指按到屏幕上
ACTION_MOVE 手指在屏幕上移动
ACTION_UP 手指从屏幕上离开
事件序列:
上文以及提到过,Touch Screen的事件并不是单个出现的,用户的每次交互都会产生一个事件序列,这个序列会以一个ACTION_DOWN开头,中间有0 或者 n个ACTION_MOVE事件,最后以一个ACTION_UP结尾。一个事件序列往往可以组成更加高级的事件,比如说点击事件、双击事件、手势事件等,View在消费这些事件的时候也会以一个独立的事件序列作为一个最小单位来消费,而不是一个个的基础事件。
所以,我们可以认识到,Touch Screen的事件是可以分为两种类型的:
基础类型:包含ACTION_DOWN、ACTION_MOVE、ACTION_UP三个。
复合类型:由基础类型的事件序列组合称一个代表了用户特定的输入信息的事件。
几个关键方法及其其作用
在软件开发中,当我们写下一个方法后,我们的目的可能是使用该方法完成某些功能,也有可能是为了程序的可扩展性等其它的理由。打个比方来说,在android的常用组件Activity类中,有一个方法叫做setContentView(),它的作用是给当前的Activity指定一个布局来显示,这样的方法就是我们刚提到的“来完成某些功能”的方法;Activity中还有一个叫做onCreate() 的方法,它的作用是一个占位的方法,表示现在是Activity正在创建的时机,可以在子类来重写该方法完成一些初始化工作,像这类方法并没有完成某些具体的功能,就是我们提到的“为了程序的可扩展性等其它的理由”。为了方便起见,在本文中我将会称第一种方法为“功能型方法”,称第二种为“结构型方法”。但是有些方法并不是简单地确认为某一种类型,它可能两种特性都包含。还以以上提到的Activity的onCreate方法为例,首先,它是个结构型方法,因为它的目的包括通知子类进行初始化工作,其次,它本身也进行了一部分对Activity的初始化,这样来说,他就也完成了某些功能,也可以说是一个功能型方法,事实上onCreate两者都是(以下简称该类方法为Both)。
在MotionEvent的处理和传递过程中有几个关键的方法,他们分别出现咋View、ViewGroup、Activity、Window等多个类中,但是名字则是完全一致,功能也是非常接近的。
以上提到了几个关键方法是MotionEvent事件分发和处理的关键所在,它们涉及到几个类:
Activity
Window
View
ViewGroup
那么,MotionEvent事件是如何在这几个类之间传递的呢?而且我们以及知道了事件是由Framework层传递到应用层的,那么究竟是谁从Framework层传递过来,又是谁在应用层接收到了呢?
通过查看android SDK的源码,可以发现是WindowManagerService这个关键类把事件从Framework层传递到应用层的,并且通过PhoneWindow的成员DecorView传递到了当前的Activity中(具体详情请参考Android FrameWork——Touch事件派发过程详解,本文不再赘述)。也就是说是WindowManagerService做了Framework层和应用层的桥梁,把MotionEvent事件传递给了当前的Activity,当前Activity是应用层最先接收到MotionEvent事件的类。
这时候,就可以理解为什么在Activity中有dispatchTouchEvent 和 onTouchEvent这两个类了,因为Activity是应用层事件分发的第一站。
我们已经了解到了MotionEvent是怎么通过Activity传递到View中的,而android的View层是一个拥有树状结构的View树,树的每一个节点也都是一个View,那么MotionEvent是怎么在View树内部传递的呢?
首先,与该事件相关的根View的dispatchTouchEvent会被调用,一次来在该View内分发事件,事件会被分发给该类的onTouchEvent方法、该View的OnTouchListener的onTouch方法、以及子View的dispatchTouchEvent方法。View会依据以上所提到的三个目标方法的返回值来觉得dispatchTouchEvent的最终返回值。
然后,当事件被分发到onTouchEvent方法中以后,该View会对事件进行处理,View的OnClickListener的onClick方法便是在这里调用的。
假如说该View是一个ViewGroup,那么它的onInterceptTouchEvent方法会在dispatchTouchEvent方法执行的时候被调用,并根据是否打断事件传递来觉得dispatchTouchEvent的返回值。
以下伪代码可以清晰地展示它们三者的关系:
Java
public booleandispatchTouchEvent(MontionEvent e){
boolean consume = false;
if(onInterceptTouchEvent(e)){
consume = onTouchEvent(e);
}else{
consume = child.dispatchTouchEvent(e);
}
return consume;
}
public booleandispatchTouchEvent(MontionEvent e){
boolean consume = false;
if(onInterceptTouchEvent(e)){
consume = onTouchEvent(e);
}else{
consume = child.dispatchTouchEvent(e);
}
return consume;
}
当然,事实上View中的dispatchTouchEvent要复杂的多,它需要处理的东西非常多,这里我们就只挑主要的介绍了。
MotionEvent事件序列是被当做一个整体来处理的,假如一个View不消耗一个ACTION_DOWN事件,那么随后的其它该序列的事件也将不会传递给它。
当一个View觉得要处理某个时间序列的话,该事件序列的所有子事件都将会被交给它处理。
ViewGroup的onInterceptTouchEvent默认返回false,也就是说ViewGroup默认是不拦截事件的。
Key InputEvent的处理和传递
Key InputEvent 基础
在应用层,Key InputEvent被封装成了一个叫做KeyEvent的类,它想MotionEvent一样,也是InputEvent的一个子类。总的来说,KeyEvent在应用层的传递方式和Motion Event是一致的。都是由WindowManagerService获取到事件,由DecorView调用ViewRoot,进而按照Activity-Window-View树的流程传递事件的。
类似于MotionEvent的关键方法,KeyEvent的关键方法分别是:
dispatchKeyEvent
onKeyEvent
onInterceptKeyEvent
在View树中的传递过程与Touch事件的一致,这里不再赘述。
以上所提到的只是Android开发
中事件分发机制的大致轮廓,具体的代码则要复杂的多,大家有时间的话可以研究下具体的源码,相信大家都会有很大的收获的。