本文节选于电子工业出版社 北京博文视点资讯有限公司推出的一书第08章,作者为韩超和梁泉。本书是一本全面介绍Android系统级开发的作品,全书以移植和调试为重点。作者以实际的开发经验为基础,以软件工程思想为指导,完成了本书。本书介绍了从Android开源工程到一个基于实际硬件产品中的主要工作,一方面让读者清晰把握各个子系统的架构,另一方面让读者把握移植这个开发核心环节的要点。本书适合Linux开发人员、移动设备开发人员、Android系统框架层和底层开发人员、有意图深入学习Android的人员、以及从事手机研发的读者阅读 |
一: 用户空间的处理
1.处理的内容和流程
触摸屏和轨迹球上报的是坐标、按下、抬起等信息,信息量比较少。按键处理的过程稍微复杂,从驱动程序到Android的Java层受到的信息,键表示方式经过了两次转化,如图8-4所示。
键扫描码Scancode是由Linux的Input驱动框架定义的整数类型。键扫描码Scancode经过一次转化后,形成按键的标签KeycodeLabel,是一个字符串的表示形式。按键的标签KeycodeLabel经过转换后,再次形成整数型的按键码keycode。在Android应用程序层,主要使用按键码keycode来区分。
图8-4 Android按键输入的两次转化
在本地框架层libui的头文件中KeycodeLabels.h,按键码为整数值的格式,其定义KeyCode(枚举值)如下所示:
进而在定义了KeycodeLabels.h中定义了从字符串到整数的映射关系,数组KEYCODES,定义如下所示:
- static const KeycodeLabel KEYCODES[] = { // {字符串,整数}
- { "SOFT_LEFT", 1 },
- { "SOFT_RIGHT", 2 },
- { "HOME", 3 },
- { "BACK", 4 },
- { "CALL", 5 },
- { "ENDCALL", 6 },
- { "0", 7 }, // ...... 数字按键
- { "1", 8 },
- { "2", 9 },
- { "3", 10 },
- { "4", 11 },
- { "5", 12 },
- { "6", 13 },
- { "7", 14 },
- { "8", 15 },
- { "9", 16 },
- { "STAR", 17 },
- // ...... 省略中间按键映射
- { "MENU", 82 },
- // ...... 省略中间按键映射
- { NULL, 0 }
- };
数组KEYCODES表示的映射关系,左列的内容即表示按键标签KeyCodeLabel,右列的内容为按键码KeyCode(与KeyCode的数值对应)。实际上,在按键信息第二次转化的时候就是将字符串类型KeyCodeLabel转化成整数的KeyCode。
KeycodeLabel的Flags的定义如下所示:
- static const KeycodeLabel FLAGS[] = {
- { "WAKE", 0x00000001 }, // 可以唤醒睡眠,并通知应用层
- { "WAKE_DROPPED", 0x00000002 }, // 可以唤醒睡眠,不通知应用层
- { "SHIFT", 0x00000004 }, // 自动附加SHIFT
- { "CAPS_LOCK", 0x00000008 }, // 自动附加CAPS_LOCK
- { "ALT", 0x00000010 }, // 自动附加ALT
- { "ALT_GR", 0x00000020 },
- { "MENU", 0x00000040 },
- { "LAUNCHER", 0x00000080 },
- { NULL, 0 }
- };
KeycodeLabel表示按键的附属标识。
在本地框架层libui的头文件中KeyCharacterMap.h,定义了按键的字符映射关系,KeyCharacterMap类的定义如下所示:
- class KeyCharacterMap
- {
- public:
- ~KeyCharacterMap();
- unsigned short get(int keycode, int meta);
- unsigned short getNumber(int keycode);
- unsigned short getMatch(int keycode, const unsigned short* chars,
- int charsize, uint32_t modifiers);
- unsigned short getDisplayLabel(int keycode);
- bool getKeyData(int keycode, unsigned short *displayLabel,
- unsigned short *number, unsigned short* results);
- inline unsigned int getKeyboardType() { return m_type; }
- bool getEvents(uint16_t* chars, size_t len,
- Vector<int32_t>* keys, Vector<uint32_t>* modifiers);
- static KeyCharacterMap* load(int id);
- enum {
- NUMERIC = 1,
- Q14 = 2,
- QWERTY = 3 // or AZERTY or whatever
- };
- }
KeyCharacterMap用于将按键的码映射为文本可识别的字符串(例如,显示的标签等)。KeyCharacterMap是一个辅助的功能:由于按键码只是一个与UI无关整数,通常用程序对其进行捕获处理,然而如果将按键事件转换为用户可见的内容,就需要经过这个层次的转换了。
KeyCharacterMap需要从本地层传送到Java层,JNI的代码路径如下所示:
frameworks/base/core/jni/android_text_KeyCharacterMap.cpp
KeyCharacterMap Java框架层次的代码如下所示:
frameworks/base/core/Java/android/view/KeyCharacterMap.Java
android.view.KeyCharacterMap类是Android平台的API可以在应用程序中使用这个类。
android.text.method中有各种Linstener,可以之间监听KeyCharacterMap相关的信息。DigitsKeyListener NumberKeyListener TextKeyListener。
以上关于按键码和按键字符映射的内容是在代码中实现的内容,还需要配合动态的配置文件来使用。在实现Android系统的时候,有可能需要更改这两种文件。
动态的配置文件包括:
Donut及其之前配置文件的路径为:
development/emulator/keymaps/
Eclair及其之后配置文件的路径为:
sdk/emulator/keymaps/
2.kl:按键布局文件
Android默认提供的按键布局文件主要包括qwerty.kl和AVRCP.kl。qwerty.kl为全键盘的布局文件,是系统中主要按键使用的布局文件;AVRCP.kl用于多媒体的控制,ACRCP的含义为Audio/Video Remote Control Profile。
qwerty.kl文件的片断如下所示:
- key 399 GRAVE
- key 2 1
- key 3 2
- key 4 3
- key 5 4
- key 6 5
- key 7 6
- key 8 7
- key 10 9
- key 11 0
- key 158 BACK WAKE_DROPPED
- key 230 SOFT_RIGHT WAKE
- key 60 SOFT_RIGHT WAKE
- key 107 ENDCALL WAKE_DROPPED
- key 62 ENDCALL WAKE_DROPPED
- key 229 MENU WAKE_DROPPED
- # 省略部分按键的对应内容
- key 16 Q
- key 17 W
- key 18 E
- key 19 R
- key 20 T
- key 115 VOLUME_UP
- key 114 VOLUME_DOWN
在按键布局文件中,第1列为按键的扫描码,是一个整数值;第2列为按键的标签,是一个字符串。即完成了按键信息的第1次转化,将整型的扫描码,转换成字符串类型的按键标签。第3列表示按键的Flag,带有WAKE字符,表示这个按键可以唤醒系统。
扫描码来自驱动程序,显然不同的扫描码可以对应一个按键标签。表示物理上的两个按键可以对应同一个功能按键。
例如,上面的扫描码为158的时候,对应的标签为 BACK ,再经过第二次转换,根据KeycodeLabels.h的KEYCODES数组,其对应的按键码为4。
3.kcm:按键字符映射文件
kcm表示按键字符的映射关系,主要功能是将整数类型按键码(keycode)转化成可以显示的字符。
qwerty.kcm表示全键盘的字符映射关系,其片断如下所示:
- [type=QWERTY]
- # keycode display number base caps fn caps_fn
- A 'A' '2' 'a' 'A' '#' 0x00
- B 'B' '2' 'b' 'B' '<' 0x00
- C 'C' '2' 'c' 'C' '9' 0x00E7
- D 'D' '3' 'd' 'D' '5' 0x00
- E 'E' '3' 'e' 'E' '2' 0x0301
- F 'F' '3' 'f' 'F' '6' 0x00A5
- G 'G' '4' 'g' 'G' '-' '_'
- H 'H' '4' 'h' 'H' '[' '{'
- I 'I' '4' 'i' 'I' '$' 0x0302
- J 'J' '5' 'j' 'J' ']' '}'
- K 'K' '5' 'k' 'K' '"' '~'
- L 'L' '5' 'l' 'L' ''' '`'
- M 'M' '6' 'm' 'M' '!' 0x00
- N 'N' '6' 'n' 'N' '>' 0x0303
第一列是转换之前的按键码,第二列之后分别表示转换成为的显示内容(display),数字(number)等内容。这些转化的内容和KeyCharacterMap.h中定义的getDisplayLabel(),getNumber()等函数相对应。
这里的类型,除了QWERTY之外,还可以是Q14(单键多字符对应的键盘),NUMERIC(12键的数字键盘)。
二: 移植需要注意的情况
1.EventHub中基本的处理
libui库中frameworks/base/libs/ui中的EventHub.cpp文件是用户输入系统的中枢,主要的功能都是在这个文件中实现的。
EventHub.cpp中定义设备节点所在的路径,内容如下所示:
- static const char *device_path = "/dev/input"; // 输入设备的目录
- bool EventHub::openPlatformInput(void)
- {
- // ...... 省略其他部分的内容
- res = scan_dir(device_path);
- return true;
- }
EventHub的getEvent()函数负责处理中完成,处理过程是在一个无限循环之内,调用阻塞的函数等待事件到来。
- bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
- int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
- int32_t* outValue, nsecs_t* outWhen)
- {
- while(1) {
- // ...... 省略部分内容
- pollres = poll(mFDs, mFDCount, -1); // 使用poll处理设备节点,进行阻塞
- // ...... 省略部分内容
- for(i = 1; i < mFDCount; i++) {
- if(mFDs[i].revents) {
- if(mFDs[i].revents & POLLIN) {
- res = read(mFDs[i].fd, &iev, sizeof(iev)); // 读取信息
- // ...... 省略部分内容
- }
- }
- }
- }
poll()函数将会阻塞程序的运行,此时为等待状态,无开销,直到Input设备的相应事件发生,事件发生后poll()将返回,然后通过read()函数读取Input设备发生的事件代码。
实际上,系统中可能有一些input设备可能不需要被Android整个系统使用,也就是说不需要经过EventHub的处理,在这种情况下可以根据EventHub中open_device()函数的处理,设置驱动程序中的一些标志,屏蔽一些设备。open_device()中处理了键盘,轨迹球和触摸屏等几种设备,对其他设备可以略过。另外一个简单的方法就是将不需要EventHub处理的设备的设备节点不放置在/dev/input之中。
open_device()函数还将打开system/usr/keylayout/中的kl文件来处理,处理的过程如下所示:
- int EventHub::open_device(const char *deviceName) {
- // ...... 省略部分内容
- const char* root = getenv("ANDROID_ROOT");
- snprintf(keylayoutFilename, sizeof(keylayoutFilename),
- "%s/usr/keylayout/%s.kl", root, tmpfn);
- bool defaultKeymap = false;
- if (access(keylayoutFilename, R_OK)) {
- snprintf(keylayoutFilename, sizeof(keylayoutFilename),
- "%s/usr/keylayout/%s", root, "qwerty.kl");
- defaultKeymap = true;
- }
- // ...... 省略部分内容
- }
由此可见,默认情况下使用的就是qwerty.kl,这里只是扫描各个后缀名为kl的文件,然后交由KeyLayoutMap去解析处理,KeyLayoutMap是一个内部使用的类。
2.按键的增加
Android已经定义了比较丰富、完整的标准按键。在一般情况下,不需要为Android系统增加按键,只需要根据kl配置按键即可。在系统中有比较奇特按键的时候,需要更改Android系统的框架层来更改按键。
增加按键需要更改的文件较多,主要的文件如下所示。
框架层增加完成后,只需要更改kl文件,增加按键的映射关系即可。
提示:在系统需要增加按键的时候,一种简易的做法是使用Android中已经定义的“生僻”按键码作为这个新增按键的键码。使用这种方式Android的框架层不需要做任何改动。这种方式的潜在问题是当某些第三方的应用可能已经使用那些生僻按键时,将意外激发系统的这种新增的按键。
延伸阅读: