Input系统学习笔记

原创 2015年07月08日 18:27:36

平台:android2.2
场景:全键盘电子书项目需要为系统新增需要按键,同时对于一些touch事件需要做特殊的处理,所以需要对整个input进行一些了解。
时间:2011.12~2012.3

1.为系统添加新的硬件按键。(http://www.eefocus.com/chongzi865458/blog/11-06/225120_23131.html
物理键盘的按键响应,能够被上层应用所接收到,主要涉及到以下几个文件:
(1).system/usr/keylayout/xxx-keypad.kl(qwerty.kl)——与扫描码(kernel上报)相关的按键映射文件(将kernel上报的扫描码转换成字符串,同时带有flag信息),通过KeyLayoutMap.cpp进行解析,然后在EventHub.cpp调用(KeyLayoutMap的map()方法),按照映射文件转换成相应的键值并将扫描码和键码返回给KeyInputQueue。

(2).frameworks/base/include/ui/KeycodeLabels.h——在EventHub.cpp中使用。将qwerty.kl被转换出来的字符串,再次进行转换成int,与KeyEvent.java中的按键码一致。
KeycodeLabels.h中还对qwerty.kl中的flag信息进行了转换。Flag信息如下:
{ “WAKE”, 0x00000001 }, // 可以唤醒睡眠,并通知应用层
{ “WAKE_DROPPED”, 0x00000002 }, // 可以唤醒睡眠,不通知应用层
{ “SHIFT”, 0x00000004 }, // 自动附加SHIFT
{ “CAPS_LOCK”, 0x00000008 }, // 自动附加CAPS_LOCK
{ “ALT”, 0x00000010 }, // 自动附加ALT

(3).frameworks/base/core/Java/android/view/KeyEvent.Java——Java框架的API。

(4).frameworks/base/core/res/res/values/attrs.xml——属性的资源文件,需要修改其中的name=”keycode”的attr。
这里写图片描述
如图所示,从kernel的扫描码,到上层的KeyEvent.java,一共经历了两次转换:kl文件—将扫描码转换成字符串;KeycodeLabels.h—将字符串转换成与KeyEvent一致的int按键码。

添加好4个文件后,就可以在应用中响应到相关的KeyCode。但是,如果在一个EditText中,想通过按下一个物理键盘按键“A”,直接将“A”显示到EditText中,这就还需要另外一个家伙的支持——kcm文件。
关于kcm文件,起作用就是用于将按键的码映射为文本可识别的字符串(例如,显示的标签等)。当然,kcm需要被解析,起对应的解析文件就是KeyCharacterMap.cpp。kcm文件在system/usr/keychars/xxxx-keypad.kcm.bin,因为其本身较大,因此使用makekcharmap工具转化成二进制的格式。其源文件的内容部分如下:
[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
display 键盘上显示的字符(丝印)
number 一般不需要
base 不使用组合键默认 显示的字符
caps Shift + 按键 显示的字符
fn Alt + 按键 显示的字符
caps_fn Shift +Alt+按键 显示的字符

第一列是转换之前的按键码,第二列之后分别表示转换成为的显示内容(display),数字(number)等内容。这些转化的内容和KeyCharacterMap.h中定义的getDisplayLabel(),getNumber()等函数相对应。所以,当你新添加的按键,或者是系统原来的按键,需要有相关的字符显示的时候,可以修改kcm文件。

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。

2.输入事件响应到上层的流程。
从下往上,依次为:
(1).frameworks/base/libs/ui/EventHub.cpp::getEvent()
该函数中有一个while(1)的循环,通过poll的方式进行处理。

(2).frameworks/base/services/jnicom_android_server_KeyInputQueue.cpp::readEvent()
Jni函数,封装了getEven(),被java框架中的KeyInputQueue.java中的InputDeviceReader线程调用。

(3).frameworks/base/services/java/com/android/server/KeyInputQueue.java
此类中有一个关键的线程InputDeviceReader,其run()方法中亦是通过while(true)的方式调用readEvent(),进行监听input事件,当然,其最终调用到getEvent()中poll()。此处通过InputDevice.java描述了input对象,同时监听处理了input设备的变化。并将调用其子类WMS中的KeyQ实现的preprocessEvent()方法进行预处理。
同时,在KeyInputQueue.java中存在一个getEvent()方法,将被WMS中的InputDispatcherThread线程调用,在其中通过mFirst.wait(end-now)进行了超时等待,在KeyInputQueue.java中的addLocked()中进行了mFirst.notify()。而addLocked()函数,正是在InputDeviceReader线程的preprocessEvent()方法后,有机会被进行调用。(即先监听event事件并处理,再确认是否需要dispatcher)

(4).frameworks/base/services/java/com/android/server/WindowManagerService.java
有三个重要的相关对象或线程,分别是:
1#.mQueue,其为KeyQ的对象,KeyQ继承于InputDeviceReader,其实现了关键的preprocessEvent()方法。在其中根据input事件的不同,继续了相关的处理,与mPolicy(PhoneWindowManager对象)进行了交互。
比如说,在case RawInputEvent.EV_KEY的情况下,调用了
mPolicy.interceptKeyTq(event, !screenIsOff),
即为对按键消息抛入到消息队列前的过滤处理。

2#.mInputThread,其为InputDispatcherThread线程的对象,被用来dispatcher输入事件。其run()方法中也是在while(true)的情况下,调用了KeyInputQueue.java中的getEvent()函数,等待着可以dispatcher的输入事件,然后根据输入事件的种类,分别调用dispatchKey(),dispatchPointer(),dispatchTrackball()等方法
。在这些方法中,将再次与PhoneWindowManager交互,调用其interceptKeyTi()函数,即为dispatcher前的过滤函数。

3#.PolicyThread线程,其即为PhoneWindowManager对象mPolicy所在的线程,拥有自己的消息队列和消息循环。

(5).EventHub.cpp中的getEvent()进入死循环,执行pollres = poll(mFDs, mFDCount, -1)等待设备结点处有数据写入(也就是有键按下),当有数据写入时执行下面的for循环,找出是哪个fd中有内容写入,并读出写入的数据res = read(mFDs[i].fd, &iev, sizeof(iev))。
这里只读出了TYPE和Scancode(),而不会有Keycode,硬件层只能向设备文件写入Scancode,而Keycode是上层要用的,由map得到。接下来执行err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags)map出Scancode对应的Keycode和Flags。

3.关于TouchEvent的上报到应用层的过程,以及实例演示解析。
当touch事件上报后,将调用dispatchTouchEvent()分发事件。先看ViewGroup类中的dispatchTouchEvent()函数,其大致过程,是将事件按照View的树形结构一直往里层传递,直到最里层的view获取到事件。查看View类中的dispatchTouchEvent()函数发现,若此时View上存在ouTouch的监听器,则调用它,否则调用view的onTouchEvent()方法。
从这个过程可以知道,默认情况下,View上面的最里层的子View处理touch事件的优先级最高,而activity最低。
同时,根据返回值来确定父view是否可以继续处理这个事件(onTouchEvent()函数返回true则表示此事件已经被处理掉)。
需要注意的是,在ViewGroup类中的dispatchTouchEvent()函数中,在往子View分发事件的时候,有一个关于onInterceptTouchEvent()函数判断。此处,即为打断事件分发函数!在同一个view上,先调用onInterceptTouchEvent(),若返回true,则表示此View接管了这次的事件,而不再将事件往子view上分发。

存在一个这个的问题:
有一个LinearLayout,里面有很多的child view,他问如何监听这个LinearLayout的Click事件?他的做法是:

    setClickable(true);

    setOnClickListener(listener);

    最后他发现listener中的回调函数根本不会被调用。

因为存在一些Event,如:Click、LongPress、DoubleClick等。这些Event通常是人为的将“硬件触发的Event”封装得来的。例如Click Event就是使用了TouchEvent 中的MotionEvent.ACTION_UP和MotionEvent.ACTION_DOWN封装而来的。默认情况下,它们使得onTouchEvent()处理函数返回了true。因此,Linearlayout的孩子们一旦设置了自己的Click事件监听器,Linearlayout本身就拿不到事件了,因为它的孩子已经进行了处理。

4.关于KeyEvent的上报以及处理流程。
(1).interface—->KeyEvent.Callback::onKeyDown()。一般通过KeyEvent对象event.dispatch()进行调用;
(2).interface—->Window.Callback::dispatchKeyEvent();
(3).View::dispatchKeyEvent()。一般是其子类重载此函数。注意区分与Window.Callback::dispatchKeyEvent()的区别!
同时View实现了KeyEvent.Callback。
(4).PhoneWindow中的DecorView继承View,重载了View的dispatchKeyEvent()方法;
(5).Activity实现了Window.Callback和KeyEvent.Callback。因此,其子类亦可以重载onKeyDown()和dispatchKeyEvent()方法。
请看图:
这里写图片描述
在MyAcitivy中重载了dispatchKeyEvent()和OnKeyDown()方法,return调用super.xxx()。
为什么LOG显示先从DecorView开始dispatch?—ViewRoot的调用!WMS跨进程调用ViewRoot中的W类的相关方法,然后在ViewRoot::deliverKeyEvent通过mView.dispatchKeyEventPreIme(event)调用到DecorView里面!

这里写图片描述
在MyAcitivy的dispatchKeyEvent()函数中直接return true。

这里写图片描述
在MyAcitivy的OnKeyDown()函数中return true。

5.其他零碎知识点
(1).Event设备在用户空间大多使用read、ioctl、poll等文件系统的接口进行操作,read用于读取输入信息,ioctl用于获得和设置信息,poll调用可以进行用户空间的阻塞,当内核有按键等中断时,通过在中断中唤醒poll的内核实现,这样在用户空间的poll调用也可以返回。

(2).可以使用getevent对Event设备进行调试。

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

(4).在手机系统中经常使用的键盘(keyboard)和小键盘(kaypad)属于按键设备EV_KEY,轨迹球属于相对设备EV_REL,触摸屏属于绝对设备EV_ABS。

HTML&CSS基础学习笔记1.25-input标签的选择文件和隐藏元素

input标签中,选择文件和隐藏元素的使用方法。

[Linux]input 子系统学习笔记(简单范例和四个基本函数)

输入子系统是为了将输入设备的功能呈现给应用程序。 它支持 鼠标、键盘、蜂鸣器、触摸屏、传感器等需要不断上报数据的设备。 分析了四个函数: 1. input_allocate_device 在内存中...
  • dearsq
  • dearsq
  • 2016年05月19日 14:54
  • 5680

Python学习笔记-Python的字符串,格式化,条件判断,循环、raw_input的注意事项

因为Python的诞生比Unicode标准发布的时间还要早,所以最早的Python只支持ASCII编码,普通的字符串'ABC'在Python内部都是ASCII编码的。Python提供了ord()和ch...

input子系统学习笔记

输入设备(按键、键盘、触摸屏、鼠标)是典型的字符设备,工作机理是底层 在按键、触摸等动作发生时产生一个中断(或驱动timer定时查询),然后CPU 通过SPI、I2C或外部存储器总线读取键值,坐标...

input子系统学习笔记五 按键驱动实例分析上

【感谢终结者投递本文】         下面是按键驱动的简单例子,这个输入设备只有一个按键,按键被连接到一条中断线上,当按键被按下时,将产生一个中断,内核将检测到这个中断,并对其进行处理。代码含注释...
  • linux58
  • linux58
  • 2012年06月27日 22:53
  • 497

jQuery学习笔记---获取表单值(Input Checkbox ListBox Button...)

1.关于jquery获取input的value问题                $("")是一个jquery对象,而不是一个dom element value是dom element的属性 ...
  • jpr1990
  • jpr1990
  • 2011年10月25日 17:52
  • 2598

Tensorflow学习笔记(8)——input_data.py解析

这里学习一下前面用到的读取mnist数据库文件的代码。其实并没有用到Tensorlfow的东西,但是读取数据库文件是使用Tensorflow编程实现功能的基础,因此归到Tensorflow的学习笔记中...
  • BBZZ2
  • BBZZ2
  • 2017年02月22日 14:37
  • 1008

AWK学习笔记-2.5Input

Input回顾一下,awk的使用方法awk 'program ' data所以data对于awk也是很重要的一部分。 如果没有文件作为Awk的输入,它将根据标准输入读取。 egrep是一个可以对输入的...

ElasticSearch学习笔记(三)logstash安装和logstash-input-jdbc插件

logstash安装和logstash-input-jdbc插件

input子系统学习笔记七 handler处理器注册分析

input_handler 是输入子系统的主要数据结构,一般将其称为handler处理器,表示对输入事件的具体处理。input_handler 为输入设备的功能实现了一个接口,输入事件最终传递到han...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Input系统学习笔记
举报原因:
原因补充:

(最多只允许输入30个字)