5.2 Android2.3用户输入子系统
↘5.2.1 总体结构
Android用户输入系统的结构比较简单,自下而上包含了驱动程序、本地库处理部分、Java类对输入事件的处理、对Java程序的接口。Android用户输入系统的结构如图5-1所示。
图5-1 用户输入系统的结构
Android的用户输入系统自下而上,包括以下几个部分的内容。
(1)驱动程序
通常使用Event类型的input驱动程序,在/dev/input目录中可以具有多个设备节点,对应多个输入硬件。
(2)配置文件及其处理
包括UI库中的几个部分。
·frameworks/base/include/ui/:UI库的头文件,包括按键定义。
·frameworks/base/libs/ui/:UI库源代码,包括按键的处理部分EventHub。
(3)JNI
包括事件结构部分和服务部分。
·frameworks/base/core/jni/android_view_KeyEvent.*:按键事件的本地支持。
·frameworks/base/core/jni/android_view_MotionEvent.*:运动事件的本地支持。
·frameworks/base/services/jni/com_android_server_InputManager.cpp:输入管理器部分。
(4)Java框架层部分
包括输入系统结构和输入管理器。
输入系统的结构中包括android.view包中的KeyEvent、MotionEvent等几个类,它们也作为系统的API。代码路径为frameworks/base/core/java/android/view/。
输入管理器InputManager负责Java层的输入管理,作为系统公共部分运行。
代码路径为frameworks/base/services/java/com/android/server/InputManager.java。
提示:在一个运行Android系统中,InputManager运行于系统服务器进程,而按键的事件运行于应用程序进程。
↘5.2.2 本地框架的几个部分
输入系统在UI库中的部分是Android系统输入的基础,并与Java的内容具有对应关系。包括了几个部分内容:Key*.*等文件用于按键结构和处理,EventHub.*文件用于事件处理的核心,Input*.*表示输入方面的逻辑处理。
1.按键配置文件的处理
触摸屏和轨迹球上报的是坐标、按下、抬起等信息。按键处理的过程稍稍复杂,从驱动程序到Android的Java层收到的信息,键表示方式经过了两次转化,如表5-2所示。键扫描码Scancode是由Linux的Input驱动框架定义的整数类型。键扫描码Scancode经过一次转化后,形成按键的标签KeycodeLabel,是一个字符串的表示形式。按键的标签KeycodeLabel经过转换后,再次形成整数型的按键码KeyCode。在Android应用程序层,主要使用按键码KeyCode来区分。
表5-2 Android按键输入的两次转化
KeycodeLabels.h头文件中定义按键码为整数值的格式,以KeyCode枚举值表示,其内容如下所示:
typedef enum KeyCode { kKeyCodeUnknown = 0, kKeyCodeSoftLeft = l, kKeyCodeSoftRight = 2, kKeyCodeHome = 3, kKeyCodeBack = 4, // 省略其他的按键码,按键码表示为连续的整数 } KeyCode;
进而在定义了KeycodeLabels.h中定义了从字符串到整数的映射关系,数组KEYCODES的定义如下所示:
static const KeycodeLabel KEYCODES[] = { // {字符串,整数} { "SOFT_LEFT", l }, { "SOFT_RIGHT", 2 }, { "HOME", 3 }, { "BACK", 4 }, { "CALL", 5 }, { "ENDCALL", 6 }, { "0", 7 }, // 各个数字按键 { "l", 8 }, { "2", 9 }, { "3", l0 }, // 省略中间按键映射 { "A", 29 }, // 各个字母按键 { "B", 30 }, // 省略中间按键映射 { "MENU", 82 }, // 省略中间按键映射 { NULL, 0 } };
数组KEYCODES表示的映射关系,左列的内容即表示按键标签KeyCodeLabel,右列的内容为按键码KeyCode(与KeyCode的数值对应)。实际上,在按键信息第二次转化时就是将字符串类型KeyCodeLabel转化成整数的KeyCode。
KeycodeLabel的Flags的定义如下所示:
static const KeycodeLabel FLAGS[] = { { "WAKE", 0x0000000l }, // 可以唤醒睡眠,并通知应用层 { "WAKE_DROPPED", 0x00000002 }, // 可以唤醒睡眠,不通知应用层 { "SHIFT", 0x00000004 }, // 自动附加SHIFT { "CAPS_LOCK", 0x00000008 }, // 自动附加CAPS_LOCK { "ALT", 0x000000l0 }, // 自动附加ALT { "ALT_GR", 0x00000020 }, { "MENU", 0x00000040 }, { "LAUNCHER", 0x00000080 }, { "VIRTUAL", 0x00000l00 }, // 虚拟按键的标识 { NULL, 0 } };
KeycodeLabel表示按键的附属标识。android.view.KeyEvent类中的KeyCode枚举值与之具有对应关系。这些定义和input.h中的定义相同。
KeyCharacterMap.h定义了按键的字符映射关系。KeyCharacterMap类中具有get()、getNumber()、getMatch()几个函数,用于完成按键码到字符的转换。NUMERIC、Q14、QWERTY枚举值则代表几种键盘类型的名称。
KeyCharacterMap主要将按键码映射为文本可识别的字符串(例如显示的标签等)。KeyCharacterMap是一个辅助的功能:按键码只是一个与UI无关的整数,通常用程序对其进行捕获处理。当需要将按键事件转换为用户可见内容时,就需要此部分的处理了。
Java框架层的android.view包中的KeyCharacterMap类表示相同的含义。android.text.method包中有各种Linstener接口,可以直接监听KeyCharacterMap相关的信息,包括DigitsKeyListener(数字)和TextKeyListener(文本)等。
按键码和按键字符映射的内容是在代码中实现的内容,还需要配合动态的配置文件来使用。在实现Android系统时,可能需要更改这两种文件。KeyLayoutMap.cpp负责解析处理kl文件,KeyCharacterMap.cpp负责解析kcm文件。
2.EventHub对设备的处理
EventHub是输入系统的中枢,也是Android系统中直接和设备相关的文件,EventHub包括以下几个方面的功能。
·监测/dev/input/目录中的设备节点。
·打开Input设备节点,进行poll和read操作。
根据设备名称打开按键布局文件(*.kl)。
获取输入的原始事件(RawEvent)。
EventHub.h中定义了表示原始设备事件的RawEvent,如下所示:
struct RawEvent { nsecs_t when; // 记录时间 int32_t deviceId; // 输入设备的ID int32_t type; // 输入设备的类型 int32_t scanCode; // 扫描码 int32_t keyCode; // 按键码 int32_t value; // 数值 uint32_t flags; // 标志 };
EventHub.h中定义了表示设备类型的枚举值,如下所示:
enum { INPUT_DEVICE_CLASS_KEYBOARD = 0x0000000l, // 键盘设备 INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002, // 带有字母和数字的键盘 INPUT_DEVICE_CLASS_TOUCHSCREEN = 0x00000004, // 触摸屏 INPUT_DEVICE_CLASS_TRACKBALL = 0x00000008, // 轨迹球 INPUT_DEVICE_CLASS_TOUCHSCREEN_MT = 0x000000l0, // 多点触摸的触摸屏 INPUT_DEVICE_CLASS_DPAD = 0x00000020, // 小键盘 INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040, // 游戏键盘 INPUT_DEVICE_CLASS_SWITCH = 0x00000080, // 开关设备 };
INPUT_DEVICE_是表示设备类型的枚举值,用每位表示一种设备。Android的输入系统统一管理键盘、触摸屏等不同类型的设备,类型用整数表示。
EventHub在实现过程中,通过INotify的机制监测/dev/input/目录中的设备节点。这种机制可以支持热插拔的设备,当基于USB、蓝牙连接的键盘、鼠标等设备插入系统后,在/dev/input/目录中可以出现相应的设备节点,EventHub就可以打开这些节点进行处理。
getEvent()是EventHub中的核心函数,RawEvent类型的参数将作为函数返回的信息。对于设备要调用poll()和read()函数获取其中的事件信息。
根据设备名称打开按键布局文件也是EventHub负责的另外一方面的内容,此部分内容在打开设备的过程中进行处理,其逻辑为从/system/usr/keylayout/中查找按键布局文件,如果有名称为<设备名>.kl的文件,则打开该文件,否则使用默认的qwerty.kl。
3.InputManager相关几个部分
UI库中的InputManager负责输入事件管理方面,这部分内容与具体设备无关。这部分内容需要封装到Java层。以上的部分需要引用NDK的头文件:frameworks/base/native/include/input.h。
UI库中的InputManager主要包括以下几个部分。
·Input.*:输入数据结构和内容的封装,提供KeyEvent和MotionEvent类型。
·InputManager.*:输入管理器,对上层的主要接口。
·InputReader.*:输入内容解析处理,也包括虚拟按键、多点触摸的处理。
·InputDispatcher.*:输入事件的分发。
·InputTransport.*:InputChannel、InputPublisher、InputConsumer三个类。
在实现过程中,InputManagerInterface、InputReaderInterface、InputReaderInterface是三个所定义的接口,InputManager、InputReader、InputDispatcher三个类分别是它们的实现。InputManager和InputChannel是需要封装到上层的内容。
↘5.2.3 JNI
1.KeyEvent和MotionEvent的JNI
android.view包中的KeyEvent和MotionEvent两个类具有JNI部分,分别是frameworks/base/core/jni/目录中的几个文件。
·KeyEvent:android_view_KeyEvent.h和android_view_KeyEvent.cpp。
android_view_KeyEvent.h定义的对外的函数如下所示:
·MotionEvent:android_view_MotionEvent.h和android_view_MotionEvent.cpp。
extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event); // 本地到Java extern void android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, KeyEvent* event); // Java到本地
两个函数分别将一个本地的KeyEvent对象复制成Java中的类,并将Java中的KeyEvent对象复制到本地。
android_view_MotionEvent.h定义的对外的函数如下所示:
extern jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event); // 本地到Java extern void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, MotionEvent* event); // Java到本地 extern void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj); // 回收Java对象
三个函数分别将一个本地的MotionEvent对象复制成Java中的类,将Java中的MotionEvent对象复制到本地,并回收一个Java中的MotionEvent对象。
JNI一般是不需要头文件的,此处之所以具有头文件,是因为需要在NDK中使用这些函数,支持在本地应用中直接使用事件。本地应用是Android 2.3(API级别为9)才开始具有的,因此输入事件也做出了相应改动。
2.InputManager的JNI
com_android_server_InputManager.cpp为系统服务库提供支持,通过调用UI库中的InputReader、InputDispatcher和InputManager等来完成功能。
NativeInputManager是一个C++的实现类。NativeInputManager类中包含一个InputManager类型的成员,在实现的各个接口中对其进行了封装调用,并且实现了InputReaderPolicyInterface和InputDispatcherPolicyInterface两个类。因此,此类实际上包含了输入事件的读取策略和分发策略的职责,前者包括了获取信息、过滤事件等功能,后者包括了注入事件、通知等功能。
在与本地的的交互过程中,此处使用反向调用Java层实现通知机制,并直接访问Java类中的一些成员的值。
↘5.2.4 Java层的部分
1.KeyEvent和MotionEvent类及其使用场合
android.view包中KeyEvent和MotionEvent两个类都是Android系统的API。二者均继承自抽象类InputEvent,其中包含的内容和本地层基本相同。
android.view包的View类具有相应的接口:
public boolean onKeyDown (int keyCode, KeyEvent event) // 键按下事件 public boolean onKeyUp (int keyCode, KeyEvent event) // 键抬起事件 public boolean onTouchEvent (MotionEvent event) // 触摸事件 public boolean onTrackballEvent (MotionEvent event) // 轨迹球事件
如果没有特殊情况,鼠标事件类似于轨迹球事件。KeyEvent类中也可以得到扫描码(scancode),但是通常不进行使用。
android.view包中还包括表示输入事件队列的InputQueue和表示输入通道的InputChannel两个类。
2.服务库中的InputManager
com.android.server包中的InputManager负责对用户输入事件进行处理并进行分发,它被窗口管理器服务(WindowManagerService)进行调用,运行于Android系统的Java服务进程(system_server)。
InputManager主要是对本地内容的一些封装,其中自己实现的功能并不多。窗口管理器服务中具有InputManager唯一实例,由此让输入事件的管理尽量独立于窗口的管理。
InputManager中的一些代码在较新的实现中已经不再推荐使用。例如,从sys文件系统的/sys/board_properties/路径中使用virtualkeys.<设备>获取虚拟按键的定义,从*.idc获取输入设备的校准信息。它们的功能已经被挪到本地层实现。