Android用户空间的处理及移植需要注意的情况

本文节选于电子工业出版社 北京博文视点资讯有限公司推出的《Android系统级深入开发——移植与调试》一书第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(枚举值)如下所示:

 
  
  1. typedef enum KeyCode { 
  2.     kKeyCodeUnknown = 0
  3.     kKeyCodeSoftLeft = 1
  4.     kKeyCodeSoftRight = 2
  5.     kKeyCodeHome = 3
  6.     kKeyCodeBack = 4
  7. // ...... 省略中间按键码 
  8. } KeyCode; 

进而在定义了KeycodeLabels.h中定义了从字符串到整数的映射关系,数组KEYCODES,定义如下所示:

 
  
  1. static const KeycodeLabel KEYCODES[] = {    // {字符串,整数} 
  2.     { "SOFT_LEFT"1 }, 
  3.     { "SOFT_RIGHT"2 }, 
  4.     { "HOME"3 }, 
  5.     { "BACK"4 }, 
  6.     { "CALL"5 }, 
  7.     { "ENDCALL"6 }, 
  8.     { "0"7 },                              // ...... 数字按键 
  9.     { "1"8 }, 
  10.     { "2"9 }, 
  11.     { "3"10 }, 
  12.     { "4"11 }, 
  13.     { "5"12 }, 
  14.     { "6"13 }, 
  15.     { "7"14 }, 
  16.     { "8"15 }, 
  17.     { "9"16 }, 
  18.     { "STAR"17 }, 
  19. // ...... 省略中间按键映射 
  20.     { "MENU"82 }, 
  21. // ...... 省略中间按键映射 
  22.     { NULL, 0 } 
  23. }; 

数组KEYCODES表示的映射关系,左列的内容即表示按键标签KeyCodeLabel,右列的内容为按键码KeyCode(与KeyCode的数值对应)。实际上,在按键信息第二次转化的时候就是将字符串类型KeyCodeLabel转化成整数的KeyCode。

KeycodeLabel的Flags的定义如下所示:

 
  
  1. static const KeycodeLabel FLAGS[] = { 
  2.     { "WAKE"0x00000001 },                  // 可以唤醒睡眠,并通知应用层 
  3.     { "WAKE_DROPPED"0x00000002 },          // 可以唤醒睡眠,不通知应用层 
  4.     { "SHIFT"0x00000004 },                 // 自动附加SHIFT 
  5.     { "CAPS_LOCK"0x00000008 },             // 自动附加CAPS_LOCK 
  6.     { "ALT"0x00000010 },                   // 自动附加ALT 
  7.     { "ALT_GR"0x00000020 }, 
  8.     { "MENU"0x00000040 }, 
  9.     { "LAUNCHER"0x00000080 }, 
  10.     { NULL, 0 } 
  11. }; 

KeycodeLabel表示按键的附属标识。

 提示:frameworks/base/core/Java/android/view/KeyEvent.Java中定义了类android.view. KeyEvent类,其中定义整数类型的数值与KeycodeLabels.h中定义的KeyCode枚举值是对应的。

在本地框架层libui的头文件中KeyCharacterMap.h,定义了按键的字符映射关系,KeyCharacterMap类的定义如下所示:

 
  
  1. class KeyCharacterMap 
  2. public
  3.     ~KeyCharacterMap(); 
  4.     unsigned short get(int keycode, int meta); 
  5.     unsigned short getNumber(int keycode); 
  6.     unsigned short getMatch(int keycode, const unsigned short* chars, 
  7.                             int charsize, uint32_t modifiers); 
  8.     unsigned short getDisplayLabel(int keycode); 
  9.     bool getKeyData(int keycode, unsigned short *displayLabel, 
  10.                     unsigned short *number, unsigned short* results); 
  11.     inline unsigned int getKeyboardType() { return m_type; } 
  12.     bool getEvents(uint16_t* chars, size_t len, 
  13.                    Vector* keys, Vector* modifiers); 
  14.     static KeyCharacterMap* load(int id); 
  15.     enum { 
  16.         NUMERIC = 1
  17.         Q14 = 2
  18.         QWERTY = 3              // or AZERTY or whatever 
  19.     }; 

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系统的时候,有可能需要更改这两种文件。

动态的配置文件包括:

  •   KL(Keycode Layout):后缀名为kl的配置文件
  •   KCM(KeyCharacterMap):后缀名为kcm的配置文件

Donut及其之前配置文件的路径为:

development/emulator/keymaps/

Eclair及其之后配置文件的路径为:

sdk/emulator/keymaps/

这些配置文件经过系统生成后,将被放置在目标文件系统的/system/usr/keylayout/目录或者/system/usr/keychars/目录中。

 提示:kl文件将被直接复职到目标文件系统中;由于尺寸较大,kcm文件放置在目标文件系统中之前,需要经过压缩处理。KeyLayoutMap.cpp负责解析处理kl文件,KeyCharacterMap.cpp负责解析kcm文件。

2.kl:按键布局文件

Android默认提供的按键布局文件主要包括qwerty.kl和AVRCP.kl。qwerty.kl为全键盘的布局文件,是系统中主要按键使用的布局文件;AVRCP.kl用于多媒体的控制,ACRCP的含义为Audio/Video Remote Control Profile。

qwerty.kl文件的片断如下所示:

 
  
  1. key 399   GRAVE 
  2. key 2     1 
  3. key 3     2 
  4. key 4     3 
  5. key 5     4 
  6. key 6     5 
  7. key 7     6 
  8. key 8     7 
  9. key 10    9 
  10. key 11    0 
  11. key 158   BACK              WAKE_DROPPED 
  12. key 230   SOFT_RIGHT        WAKE 
  13. key 60    SOFT_RIGHT        WAKE 
  14. key 107   ENDCALL           WAKE_DROPPED 
  15. key 62    ENDCALL           WAKE_DROPPED 
  16. key 229   MENU              WAKE_DROPPED 
  17. # 省略部分按键的对应内容 
  18. key 16    Q 
  19. key 17    W 
  20. key 18    E 
  21. key 19    R 
  22. key 20    T 
  23. key 115   VOLUME_UP 
  24. key 114   VOLUME_DOWN 

在按键布局文件中,第1列为按键的扫描码,是一个整数值;第2列为按键的标签,是一个字符串。即完成了按键信息的第1次转化,将整型的扫描码,转换成字符串类型的按键标签。第3列表示按键的Flag,带有WAKE字符,表示这个按键可以唤醒系统。

扫描码来自驱动程序,显然不同的扫描码可以对应一个按键标签。表示物理上的两个按键可以对应同一个功能按键。

例如,上面的扫描码为158的时候,对应的标签为 BACK ,再经过第二次转换,根据KeycodeLabels.h的KEYCODES数组,其对应的按键码为4。

 提示:按键布局文件其实同时兼顾了input驱动程序的定义和Android中按键的定义。例如:input驱动程序中定义的数字扫描码KEY_1的数值为2,这里2对应的按键标签也为“1”;input驱动程序中定义字母扫描码KEY_Q的数值为16,这里对应的按键标签也为“Q”。然而移动电话的全键盘毕竟有所不同,因此有一些按键是和input驱动程序的定义没有对应关系的。

kl文件将以原始的文本文件的形式,放置于目标文件系统的/system/usr/keylayout/目录或者/system/usr/keychars/目录中。

3.kcm:按键字符映射文件

kcm表示按键字符的映射关系,主要功能是将整数类型按键码(keycode)转化成可以显示的字符。

qwerty.kcm表示全键盘的字符映射关系,其片断如下所示:

 
  
  1. [type=QWERTY]  
  2. # keycode       display number  base    caps    fn      caps_fn                                                        
  3.  
  4. A               'A'     '2'     'a'     'A'     '#'     0x00 
  5.  
  6. B               'B'     '2'     'b'     'B'     '<'     0x00 
  7.  
  8. C               'C'     '2'     'c'     'C'     '9'     0x00E7 
  9.  
  10. D               'D'     '3'     'd'     'D'     '5'     0x00 
  11.  
  12. E               'E'     '3'     'e'     'E'     '2'     0x0301 
  13.  
  14. F               'F'     '3'     'f'     'F'     '6'     0x00A5 
  15.  
  16. G               'G'     '4'     'g'     'G'     '-'     '_' 
  17.  
  18. H               'H'     '4'     'h'     'H'     '['     '{' 
  19.  
  20. I               'I'     '4'     'i'     'I'     '$'     0x0302 
  21.  
  22. J               'J'     '5'     'j'     'J'     ']'     '}' 
  23.  
  24. K               'K'     '5'     'k'     'K'     '"'     '~' 
  25.  
  26. L               'L'     '5'     'l'     'L'     '''     '`' 
  27.  
  28. M               'M'     '6'     'm'     'M'     '!'     0x00 
  29.  
  30. N               'N'     '6'     'n'     'N'     '>'     0x0303 

第一列是转换之前的按键码,第二列之后分别表示转换成为的显示内容(display),数字(number)等内容。这些转化的内容和KeyCharacterMap.h中定义的getDisplayLabel(),getNumber()等函数相对应。

这里的类型,除了QWERTY之外,还可以是Q14(单键多字符对应的键盘),NUMERIC(12键的数字键盘)。

kcm文件将被makekcharmap工具转化成二进制的格式,放在目标系统的/system/usr/keychars/目录中。

二:  移植需要注意的情况

1.EventHub中基本的处理

libui库中frameworks/base/libs/ui中的EventHub.cpp文件是用户输入系统的中枢,主要的功能都是在这个文件中实现的。

EventHub.cpp中定义设备节点所在的路径,内容如下所示:

 
  
  1. static const char *device_path = "/dev/input";   // 输入设备的目录 

在处理过程中,将搜索路径下面的所有Input驱动的设备节点,这在openPlatformInput()中通过调用scan_dir()来实现,scan_dir()将会从目录中查找设备,找到后调用open_device()将其打开。

 
  
  1. bool EventHub::openPlatformInput(void
  2.  
  3.  
  4. // ...... 省略其他部分的内容 
  5.  
  6.     res = scan_dir(device_path); 
  7.  
  8.     return true
  9.  

EventHub的getEvent()函数负责处理中完成,处理过程是在一个无限循环之内,调用阻塞的函数等待事件到来。

 
  
  1. bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, 
  2.  
  3.         int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, 
  4.  
  5.         int32_t* outValue, nsecs_t* outWhen) 
  6.  
  7.  
  8.     while(1) { 
  9.  
  10.     // ...... 省略部分内容 
  11.  
  12.         pollres = poll(mFDs, mFDCount, -1);      // 使用poll处理设备节点,进行阻塞 
  13.  
  14.     // ...... 省略部分内容 
  15.  
  16.         for(i = 1; i < mFDCount; i++) { 
  17.  
  18.             if(mFDs[i].revents) { 
  19.  
  20.                 if(mFDs[i].revents & POLLIN) { 
  21.  
  22.                     res = read(mFDs[i].fd, &iev, sizeof(iev));   // 读取信息 
  23.  
  24.     // ...... 省略部分内容 
  25.  
  26.             } 
  27.  
  28.         } 
  29.  
  30.     } 
  31.  

poll()函数将会阻塞程序的运行,此时为等待状态,无开销,直到Input设备的相应事件发生,事件发生后poll()将返回,然后通过read()函数读取Input设备发生的事件代码。

注意,EventHub默认情况可以在/dev/input之中扫描各个设备进行处理,通常情况下所有的输入设备均在这个目录中。

实际上,系统中可能有一些input设备可能不需要被Android整个系统使用,也就是说不需要经过EventHub的处理,在这种情况下可以根据EventHub中open_device()函数的处理,设置驱动程序中的一些标志,屏蔽一些设备。open_device()中处理了键盘,轨迹球和触摸屏等几种设备,对其他设备可以略过。另外一个简单的方法就是将不需要EventHub处理的设备的设备节点不放置在/dev/input之中。

open_device()函数还将打开system/usr/keylayout/中的kl文件来处理,处理的过程如下所示:

 
  
  1. int EventHub::open_device(const char *deviceName) { 
  2.  
  3.     // ...... 省略部分内容 
  4.  
  5.         const char* root = getenv("ANDROID_ROOT"); 
  6.  
  7.         snprintf(keylayoutFilename, sizeof(keylayoutFilename), 
  8.  
  9.                  "%s/usr/keylayout/%s.kl", root, tmpfn); 
  10.  
  11.         bool defaultKeymap = false
  12.  
  13.         if (access(keylayoutFilename, R_OK)) { 
  14.  
  15.             snprintf(keylayoutFilename, sizeof(keylayoutFilename), 
  16.  
  17.                      "%s/usr/keylayout/%s", root, "qwerty.kl"); 
  18.  
  19.             defaultKeymap = true
  20.  
  21.         } 
  22.  
  23.     // ...... 省略部分内容 
  24.  

由此可见,默认情况下使用的就是qwerty.kl,这里只是扫描各个后缀名为kl的文件,然后交由KeyLayoutMap去解析处理,KeyLayoutMap是一个内部使用的类。

2.按键的增加

Android已经定义了比较丰富、完整的标准按键。在一般情况下,不需要为Android系统增加按键,只需要根据kl配置按键即可。在系统中有比较奇特按键的时候,需要更改Android系统的框架层来更改按键。

增加按键需要更改的文件较多,主要的文件如下所示。

  •   frameworks/base/include/ui/KeycodeLabels.h:中的KeyCode枚举数值和KeycodeLabel 类型Code数组(以NULL为结尾)
  •   frameworks/base/core/Java/android/view/KeyEvent.Java:定义整数值,作为平台的API供Java应用程序使用
  •   frameworks/base/core/res/res/values/attrs.xml:表示属性的资源文件,需要修改其中的name="keycode"的attr。

框架层增加完成后,只需要更改kl文件,增加按键的映射关系即可。

 提示:在系统需要增加按键的时候,一种简易的做法是使用Android中已经定义的“生僻”按键码作为这个新增按键的键码。使用这种方式Android的框架层不需要做任何改动。这种方式的潜在问题是当某些第三方的应用可能已经使用那些生僻按键时,将意外激发系统的这种新增的按键。


 

延伸阅读:

Android用户输入系统的移植与调试

 

Android用户输入系统在模拟器和MSM中的实现

Android用户输入系统在OMAP中的实现和虚拟按键的实现

 

你可能感兴趣的:(android,后台功能,android,input,框架,java,null,menu)