【实习周记】Android getevent.c源码分析

【实习周记】Android getevent.c源码分析

一.概述

getevent和sendevent是Android系统下的两个工具
在cmd 命令行下输入adb shell进入Android设备的shell
输入getevent ,获取当前设备的事件
输入sendevent ,模拟设备产生事件
这两个命令的源码在Android系统system/core/toolbox/目录下,
名字分别为getevent.c和sendevent.c,属于Android底层代码

二.Android输入系统

1.输入设备和输入事件

提到输入设备,我们能想到的有触摸屏和键盘,实际上Android所支持的输入设备不仅这两种,包括鼠标,手柄和一些传感器都属于输入设备。
当输入设备可用时,Linux内核会在/dev/input/目录下为设备创建名为event0~n或其他名称的设备节点。当输入设备不可用时,则会将对应的节点删除。在用户空间可以通过ioctl()函数从这些设备节点中获取其对应的输入设备的类型、厂商、描述等信息。

例如:进入adb shell 输入getevent
将打印如下相关设备的信息
add device 1: /dev/input/event4
name: “proximity_sensor” //距离传感器
add device 2: /dev/input/event3
name: “accelerometer_sensor” //加速度传感器
add device 3: /dev/input/event1
name: “meta_event” //
add device 4: /dev/input/event0
name: “qpnp_pon” //power按键
could not get driver version for /dev/input/mice, Not a typewriter
add device 5: /dev/input/event5
name: “gpio-keys” //键盘
add device 6: /dev/input/event2
name: “sec_touchkey” //虚拟按键
could not get driver version for /dev/input/mouse0, Not a typewriter
add device 7: /dev/input/event6
name: “sec_touchscreen” //触摸屏

Android输入事件的产生:当用户操作输入设备时,Linux内核接收到相应的硬件中断,并将中断加工成原始的输入事件数据,写入对应的设备节点中,在用户空间可以通过read()函数将事件读出。
Android输入系统的工作原理:监控/dev/input/下的所有设备节点,当某个节点有数据可读时,将数据读出并进行相应的翻译加工,在所有的窗口中寻找合适的事件接收者,并派发给它。

2.Android输入系统的工作流程

在这里插入图片描述

(1).组成&功能:

Linux内核:接收输入设备中断,将原始事件数据写入设备节点。

设备节点:连接内核和IMS

InputManagerService(IMS):Android系统服务,分为Java层和Native层两部分。Java层负责与WMS的通信。Native层是InputReader和InputDispatcher两个输入系统关键组件的运行容器。

EventHub:直接访问所有的设备节点,通过名为getEvents()的函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。

InputReader:IMS中的关键组件之一。它运行于一个独立的线程中,负责管理输入设备的列表与配置,同时对输入事件进行加工处理。它通过其线程循环不断地通getEvents()函数从EventHub中将事件取出并进行处理。对于设备节点的增删事件,它会更新输入设备列表与配置。对于原始输入事件,InputReader对其进行翻译、组装、封装为包含了更多信息、更具可读性的输入事件,然后交给InputDispatcher进行派发。

InputReaderPolicy:为InputReader的事件加工处理提供一些策略配置。

InputDispatcher:IMS中的另一个关键组件。它也运行于一个独立的线程中。
InputDispatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。

InputDispatcherPolicy:为InputDispatcher的派发过程提供策略控制。例如截取某些特定的输入事件用作特殊用途,或者阻止将某些事件派发给目标窗口。eg:home键被InputDispatcherPolicy截取到PhoneWindowManager中进行处理,并阻止窗口收到HOME键按下的事件。

WindowManagerSevice(WMS):当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。WMS将所有窗口的信息,包括窗口的可点击区域,焦点窗口等,实时地更新到IMS的InputDispatcher中,使InputDispatcher可以正确地将事件派发到指定的窗口。

ViewRootImpl:应用层事件分发的入口,将窗口所接收到的输入事件沿着控件树派发给相应的控件。

(2).工作流程:

内核将原始的事件写入设备节点中,InputReader通过EventHub将原始事件取出来并翻译加工成Android输入事件,然后交给InputDispatcherInputDispatcher根据WMS提供的窗口信息将事件分发给合适的窗口。窗口对应的ViewRootImpl对象沿着控件树将事件分发给相应控件。最终,控件对接收的事件进行响应。

三.getevent.c源码分析

1.基础知识

(1).INotify

INotify是一个Linux内核所提供的一种文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的新建、删除、读写等。INotify机制有两个基本对象,分别为
inotify对象watch对象,都使用文件描述符表示。

inotify对象对应了一个队列,应用程序可以向inotify对象添加多个监听。当被监听的事
件发生时,可以通过read()函数从inotify对象中将事件信息读取出来。

inotify对象的创建int inotifyFd = notify_init();

watch对象用来描述文件系统的变化事件的监听。它是一个二元组,包括监听目标和事件
掩码两个元素。监听目标是文件系统的一个路径,可以是文件也可以是文件夹。而事件掩
码则表示了需要需要监听的事件类型,掩码中的每一位代表一种事件。可以监听的事件种
类很多,其中就包括文件的创建(IN_CREATE)与删除(IN_DELETE)。

watch对象的创建
int wd = inotify_add_watch(inotifyFd,”/dev/input”,IN_CREATE | IN_DELETE);
创建完上面的watch后,当/dev/input/下的设备节点发生创建和删除操作时,都会将相应
的事件写入notifyFd所描述的inotify对象中。

事件信息的读取size_t len = read(inotifyFd,events_buf,BUF_LEN);
events_buf是inotify_event的数组指针,读取事件的数量取决于数组的长度,BUF_LEN
为events_buf的长度。
struct inotify_event{
__s32 wd; //事件对应watch对象描述符
__u32 mask; //事件类型 eg:IN_CREATE
__u32 cookie;
__u32 len; //name字段的长度
char name[0]; //存储产生此事件的文件路径
}
通过INotify机制避免了轮询文件系统的麻烦,但是还有一个问题,INotify机制并不是通
过回调的方式通知事件,而需要使用者主动从inotify对象中进行事件读取。

(2).poll

poll是监控文件是否可读的一种机制,poll机制会判断fds中的文件是否可读,如果可读
则会立即返回,返回的值就是可读fd的数量,如果不可读,那么就进程就会休眠timeout
这么长的时间,然后再来判断是否有文件可读,如果有,返回fd的数量,如果没有,则
返回0。
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数说明:
typedef struct pollfd {
int fd; /* 需要被检测或选择的文件描述符*/
short events; /* 对文件描述符fd上感兴趣的事件 /
short revents; /
文件描述符fd上当前实际发生的事件*/
} ;
事件类型:
POLLIN 有数据可读
POLLRDNORM 有普通数据可读
POLLRDBAND 有优先数据可读
POLLPRI 有紧迫数据可读
POLLOUT 写数据不会导致阻塞
POLLWRNORM 写普通数据不会导致阻塞
POLLWRBAND 写优先数据不会导致阻塞
POLLMSG SIGPOLL消息可用
POLLER 指定的文件描述符发生错误
POLLHUP 指定的文件描述符挂起事件
POLLNVAL 指定的文件描述符非法

typedef unsigned long nfds_t;/* fds中的结构体元素的总数量*/

timeout:poll函数调用阻塞的事件,单位毫秒,小于0时一直阻塞。

返回值说明:

0:fds中准备好读,写或出错的描述符的数量
=0:fds中没有准备好读,写或出错的描述符的数量,此时poll超时,超时事件为timeout
=-1:poll函数调用失败,自动设置全局变量errno。

(2).Ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。
int ioctl(int fd, int cmd, …);
参数说明:
fd:用户程序打开设备时使用open函数返回的文件标示符,
cmd:是用户程序对设备的控制命令,
            Linux核心定义的命令码(cmd):
            设备类型:8bit 序列号:8bit方向:2bit 数据尺寸:8~14bit
…:补充参数,一般最多一个,这个参数的有无和cmd的意义相关。

2.getevent指令

在adb shell下输入getevent,会为我们返回未加工过的事件信息。
格式:事件产生设备(device)    事件类型(type)    事件代码(code)    事件值(value)
             type,code,value都为十六进制。
例如:/dev/input/event3: 0002 0001 ffffff5b

3.getevent.c源码分析

(1).程序执行入口:getevent_main(int argc, char *argv[]);
(2).通过getopt(argc, argv, "tns:Sv::dpilqc:rh")对参数进行解析。
(3).创建inotify对象,返回的操作符存储在ufds中设置ufds中监听的事件为有数据可读。
     ufds[0].fd = inotify_init();
     ufds[0].events = POLLIN;
(4). 为inotify对象设置watch对象,监听/dev/input目录下文件的创建和删除
     res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE);
(5).在while循环中调用poll函数阻塞等待事件的产生
     poll(ufds, nfds, -1);
(6).当实际发生的事件为有数据可读时,调用read_notify()函数
     if(ufds[0].revents & POLLIN) {
        read_notify(device_path, ufds[0].fd, print_flags);
     }
read_notify()函数内部调用 open_device()函数对输入设备device进行增加或者调用close_device()函数对输入设备device进行删除,open_device()函数和close_device()函数内部通过ioctl()函数来对输入设备进行控制。
(7).若实际发生的事件为有数据可读,读取,存入input_event的对象event中。
     res = read(ufds[i].fd, &event, sizeof(event));
(8).调用print_event()函数打印event
     print_event(event.type, event.code, event.value, print_flags);

发布了10 篇原创文章 · 获赞 5 · 访问量 589

猜你喜欢

转载自blog.csdn.net/LeeDuoZuiShuai/article/details/97536678
今日推荐