前言
问渠那得清如许,为有源头活水来。对于大多数现代操作系统中,“事件”就是它们的“活水源头”,正是在“事件和消息”的不断产生、流转和处理中,整个软件系统才能“动”起来。
Android系统有一套从底层Linux内核到上层应用程序完整的消息产生、投递及处理机制——这同时也是外界与Android设备交互的基
础。对于系统层的开发人员而言,经常要根据具体的硬件配置来扩展、修改和完善消息处理机制,因而深入理解其中的实现原理就显
得异常重要。
一、输入事件
首先应该明白一个问题——什么是“事件”?从广义上来说,事件的发生源分为“软件”与“硬件”两类,这里侧重于对后者的讨论。也就是说,它们是由真实物理硬件产生的消息,表明设备使用者的某种“意愿”。例如用户点击了触摸屏,而相应位置上是音乐播放器的“暂停”键,那么说明他希望暂停当前的音乐播放。
1.1 事件处理
如果从硬件设备角度来为Android系统中的事件分类,主要有以下几种。
- 按键事件(KeyEvent)
由物理按键产生的事件。对于嵌入式设备,通常不会配备太多物理按键。比如手机一般只有Home、Back、Menu、Volume Up、Volume
Down和Camera等常用功能键。
- 触摸事件(TouchEvent)
在触摸屏上点击、拖动,以及由它们的组合所产生的各种事件。这是Android系统中使用最广泛也是相对复杂的一种事件类型。根据Android项目经验,应用开发人员大部分的事件处理工作都和TouchEvent有关。
- 鼠标事件(MouseEvent)
鼠标操作引起的事件,在嵌入式设备中并不常用。
- 轨迹球事件(TrackBallEvent)
轨迹球基本已经被淘汰了。
1.2 事件抽象函数
这里我们结合Android系统中最常见的按键事件和触摸事件来做个简单的事件抽象设计,以便我们能更深刻的理解Android系统的事件设计思想。
1.2.1 按键事件
按键理论上只有两种状态要么按下,要么松开,即对应于KeyDown和KeyUp。r俺儿实际上却没这么简单,为什么呢?因为还有其他一些因素也是要考虑的,如长按、短按,或者按键组合(多个按键)的情况。比如我们在Windows操作系统中同时按Ctrl+Alt+Del组合键可以调出任务管理器,Android系统也同样支持这些操作。所以总结起来,影响一个按键事件的基础因素主要有以下几点:
- 按键按下;
- 按键松开;
- 状态持续的长短;
- 按键数量。
对其进行简单抽象,可以得到以下几个基础接口。
OnKeyDown();
onKeyUp();
onKeyLongPress();
onKeyMultiple();
1.2.2 触摸事件
触摸事件比按键事件要复杂一些。从用户的角度来看,正常的“触摸屏”设备既支持点击,也同时能“感应”滑动事件——这可以说是它和按键事件最大的区别。
- 按住滑块;
- 移动滑块;
- 松开滑块。
以上动作将产生3种触摸事件,即:
- 触摸点按下(TOUCH_DOWN);
- 触摸点移动(TOUCH_MOVE);
- 触摸点释放(TOUCH_UP)
值得一提的是,由MOVE事件还可以派生出其他的事件,如fling。应用程序为了模拟真实的世界,就必须遵循一定的物理现象。举个例
子,我们在地面上拖动一辆小车,放手后小车并不会马上停止,而是会继续向前再前进一段时间。应用到上面的解锁场景,就是当用户的
手势已经释放后(TOUCH_UP),滑块本身也不会马上停止,而是转化为fling事件继续执行一小段时间。这些细节是我们提供良好用户体的一个基础。
触摸事件相关的因素包括:
- 触摸点状态(按下,松开);
- 触摸点移动(移动的距离大小、速度等);
- 触摸点的数量(需要“触摸屏”设备的支持,并不是所有设备都可以多点操作);
- 时间因素(长按、短按等)。
对其进行简单抽象,可以得到以下几个基础接口。
onTouchDown();
onTouchUp();
onTouchMove();
onTouchLongPress();
onTouchMultiple();
1.3 事件抽象接口
针对所有事件的共性,我们需要提取一个统一的抽象接口,这就是InputEvent。从它的名称可以看出,Event属于I/O设备中的Input部分。
InputEvent下有两个子类,KeyEvent和MotionEvent。
按键KeyEvent很容易理解,用于表达按键事件;而MotionEvent则是将所有能产生Movement的事件源进行统一管理,如Trackball、Finger、Mouse等。
二、输入事件的传递
输入事件传递流程主要可以分三部分,分别是输入系统部分、WMS处理部分、View处理部分。
-
输入系统部分
包含输入子系统以及InputManagerService,用于事件的捕获以及分发给上一级 -
WindowManagerService处理部分
输入系统部分将事件分发给对应的Window,而Window正是由WMS来管理的 -
View处理部分
Android应用开发人员所熟知的View事件的传递机制,其实就属于这部分。
这里再提供一张更加详细的Android输入系统模型:
初看这张图可能有点不是很明白,待我们通过本系列文章完全认识了整个输入系统模块之后,再回来看这张图必然会有一种柳暗花明的感觉。
2.1 输入系统部分
输入系统说白了就是捕获事件,并将事件分发给WMS进行处理。关键字:事件捕获,事件分发 那系统是如何进行事件捕获和分发的呢?
输入系统在结构上又可以分为输入子系统和InputManagerService(简称IMS)
2.1.1 输入子系统
Android中的输入设备有很多种,如:键盘、触摸屏、鼠标,、游戏手柄等,Android应用开发中最常见的就是屏幕和按键了。当输入设备可用的时候,Linux内核会在/dev/input中创建对应的设备节点,当用户操作操作这些输入设备时会产生各种事件,比如按键事件、触摸事件、鼠标事件等;这些输入的原始信息会被Linux内核中的输入子系统采集,原始信息会经由内核空间(kernel space)的驱动层一直传递到用户空间(User space)的设备节点中。
Android系统提供了getEvent和sendevent两个工具帮助开发者从设备节点读取输入事件和写入输入事件。
2.1.2 InputManagerService服务
InputManagerService服务所做的工作就是监听/dev/input下所有的设备节点,当设备节点有数据的时候会对数据进行二次甚至三次加工处理,并找到合适的窗口,将输入事件派发给它。
2.2 WMS处理部分
我们在Android 12系统源码_窗口管理(一)WindowManagerService的启动流程这篇文章中有讲过WindowManagerService的职责有四种。
WMS的职责之一就是输入系统的中转站,WMS作为窗口的管理者,会配合IMS将输入事件分配给合适的窗口来处理。
2.3 View处理部分
View的处理部分应该所Android应用开发人员最熟悉的部分了,一般情况下,输入事件最终会交给View来处理,应用开发人员可以通过一些回调方法轻松得到这个事件的封装类并对其进行处理,比如onTouchEvent(MotionEvent ev)方法。