[置顶] android用户输入系统详细说明
一: 用户空间的处理
1.处理的内容和流程
触摸屏和轨迹球上报的是坐标、按下、抬起等信息,信息量比较少。按键处理的过程稍微复杂,从驱动程序到Android的Java层受到的信息,键表示方式经过了两次转化,如图8-4所示。
图8-4Android按键输入的两次转化
键扫描码Scancode是由Linux的Input驱动框架定义的整数类型。键扫描码Scancode经过一次转化后,形成按键的标签KeycodeLabel,是一个字符串的表示形式。按键的标签KeycodeLabel经过转换后,再次形成整数型的按键码keycode。在Android应用程序层,主要使用按键码keycode来区分。
在本地框架层libui的头文件中KeycodeLabels.h,按键码为整数值的格式,其定义KeyCode(枚举值)如下所示:
1. typedefenumKeyCode{
2. kKeyCodeUnknown=0,
3. kKeyCodeSoftLeft=1,
4. kKeyCodeSoftRight=2,
5. kKeyCodeHome=3,
6. kKeyCodeBack=4,
7. //......省略中间按键码
8. }KeyCode;
进而在定义了KeycodeLabels.h中定义了从字符串到整数的映射关系,数组KEYCODES,定义如下所示:
1. staticconstKeycodeLabelKEYCODES[]={//{字符串,整数}
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. staticconstKeycodeLabelFLAGS[]={
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. classKeyCharacterMap
2. {
3. public:
4. ~KeyCharacterMap();
5. unsignedshortget(intkeycode,intmeta);
6. unsignedshortgetNumber(intkeycode);
7. unsignedshortgetMatch(intkeycode,constunsignedshort*chars,
8. intcharsize,uint32_tmodifiers);
9. unsignedshortgetDisplayLabel(intkeycode);
10. boolgetKeyData(intkeycode,unsignedshort*displayLabel,
11. unsignedshort*number,unsignedshort*results);
12. inlineunsignedintgetKeyboardType(){returnm_type;}
13. boolgetEvents(uint16_t*chars,size_tlen,
14. Vector<int32_t>*keys,Vector<uint32_t>*modifiers);
15. staticKeyCharacterMap*load(intid);
16. enum{
17. NUMERIC=1,
18. Q14=2,
19. QWERTY=3//orAZERTYorwhatever
20. };
21. }
KeyCharacterMap用于将按键的码映射为文本可识别的字符串(例如,显示的标签等)。KeyCharacterMap是一个辅助的功能:由于按键码只是一个与UI无关整数,通常用程序对其进行捕获处理,然而如果将按键事件转换为用户可见的内容,就需要经过这个层次的转换了。
KeyCharacterMap需要从本地层传送到Java层,JNI的代码路径如下所示:
frameworks/base/core/jni/android_text_KeyCharacterMap.cpp
KeyCharacterMapJava框架层次的代码如下所示:
frameworks/base/core/Java/android/view/KeyCharacterMap.Java
android.view.KeyCharacterMap类是Android平台的API可以在应用程序中使用这个类。
android.text.method中有各种Linstener,可以之间监听KeyCharacterMap相关的信息。DigitsKeyListenerNumberKeyListenerTextKeyListener。
以上关于按键码和按键字符映射的内容是在代码中实现的内容,还需要配合动态的配置文件来使用。在实现Android系统的时候,有可能需要更改这两种文件。
动态的配置文件包括:
- KL(KeycodeLayout):后缀名为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/VideoRemoteControlProfile。
qwerty.kl文件的片断如下所示:
1. key399GRAVE
2. key21
3. key32
4. key43
5. key54
6. key65
7. key76
8. key87
9. key109
10. key110
11. key158BACKWAKE_DROPPED
12. key230SOFT_RIGHTWAKE
13. key60SOFT_RIGHTWAKE
14. key107ENDCALLWAKE_DROPPED
15. key62ENDCALLWAKE_DROPPED
16. key229MENUWAKE_DROPPED
17. #省略部分按键的对应内容
18. key16Q
19. key17W
20. key18E
21. key19R
22. key20T
23. key115VOLUME_UP
24. key114VOLUME_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. #keycodedisplaynumberbasecapsfncaps_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. staticconstchar*device_path="/dev/input";//输入设备的目录
在处理过程中,将搜索路径下面的所有Input驱动的设备节点,这在openPlatformInput()中通过调用scan_dir()来实现,scan_dir()将会从目录中查找设备,找到后调用open_device()将其打开。
1. boolEventHub::openPlatformInput(void)
2.
3. {
4.
5. //......省略其他部分的内容
6.
7. res=scan_dir(device_path);
8.
9. returntrue;
10.
11. }
EventHub的getEvent()函数负责处理中完成,处理过程是在一个无限循环之内,调用阻塞的函数等待事件到来。
1. boolEventHub::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.
9. while(1){
10.
11. //......省略部分内容
12.
13. pollres=poll(mFDs,mFDCount,-1);//使用poll处理设备节点,进行阻塞
14.
15. //......省略部分内容
16.
17. for(i=1;i<mFDCount;i++){
18.
19. if(mFDs[i].revents){
20.
21. if(mFDs[i].revents&POLLIN){
22.
23. res=read(mFDs[i].fd,&iev,sizeof(iev));//读取信息
24.
25. //......省略部分内容
26.
27. }
28.
29. }
30.
31. }
32.
33. }
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. intEventHub::open_device(constchar*deviceName){
2.
3. //......省略部分内容
4.
5. constchar*root=getenv("ANDROID_ROOT");
6.
7. snprintf(keylayoutFilename,sizeof(keylayoutFilename),
8.
9. "%s/usr/keylayout/%s.kl",root,tmpfn);
10.
11. booldefaultKeymap=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.
25. }
由此可见,默认情况下使用的就是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中图片的三级cache策略(内存、文件、网络)之三:文件
- Android 读写xml文件
- 如何使用Android Studio打开一个App项目,导入Android App项目需要
- android设备修改默认桌面为自定义应用
- Android应用程序如何访问/sys和/proc等目录下的系统文件
- android使用webview预览png,pdf,doc,xls,txt,等文件
- Android自定义View的官方套路
- Android读取工程内嵌资源文件的两种方法