Android板级支持与硬件相关子系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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获取输入设备的校准信息。它们的功能已经被挪到本地层实现。