Input系列算是我对 Framework 入门的一个模块,涉及服务启动,数据转换,入列,出列,等待,事件消费,分发,移除消息,ANR处理。希望大佬们多多斧正。
- Android Framework-Input-1 基础梳理
- Android Framework-Input-2 构建和启动
- Android Framework-Input-3 数据处理
- Android Framework-Input-4 IQ到OQ
- Android Framework-Input-5 分发事件
- Android Framework-Input-6 事件分发到View
- Android Framework-Input-7 事件的移除
- Android Framework-Input-8 ANR相关
常用命令
adb shell getevent -lrt
ABS :触摸
Key :事件
为什么会响应该命令?
在调用adb shell 进入手机后, 进入system/bin 里 进入toolbox 里面有一个getevent 可以执行。
源码位置在于:system/core/toolbox/getevent.c (注意不在framework 目录在在根目录下)
int getevent_main(int argc, char *argv[]){
const char *device_path = "/dev/input";
ufds[0].fd = inotify_init();//初始化inotify主要负责监听文件夹内容变化
ufds[0].events = POLLIN;
if(device) {//除非限制监听某种类型 不然一般走下面
if(!print_flags_set)
print_flags |= PRINT_DEVICE_ERRORS;
res = open_device(device, print_flags);
if(res < 0) {
return 1;
}
} else {
//一般走下面
if(!print_flags_set)
print_flags |= PRINT_DEVICE_ERRORS | PRINT_DEVICE | PRINT_DEVICE_NAME;
print_device = 1;
// //添加inotify观察路径为device_path
res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE);
if(res < 0) {
fprintf(stderr, "could not add watch for %s, %s\n", device_path, strerror(errno));
return 1;
}
////扫描遍历所有节点,且打开节点
res = scan_dir(device_path, print_flags);
}
while(1) {
////最为核心的poll类似以前说过epoll,但是效率比epoll低
poll(ufds, nfds, -1);
//第1个fd其实是文件夹里面内容变化 比如节点增加或者减少
if(ufds[0].revents & POLLIN) {
read_notify(device_path, ufds[0].fd, print_flags);
}
for(i = 1; i < nfds; i++) {
if(ufds[i].revents) {
//读取对应内容数据
if(ufds[i].revents & POLLIN) {
res = read(ufds[i].fd, &event, sizeof(event));
if(res < (int)sizeof(event)) {
fprintf(stderr, "could not get event\n");
return 1;
}
if(get_time) {
printf("[%8ld.%06ld] ", event.time.tv_sec, event.time.tv_usec);
}
if(print_device)
printf("%s: ", device_names[i]);
//打印具体event内容
print_event(event.type, event.code, event.value, print_flags);
if(sync_rate && event.type == 0 && event.code == 0) {
int64_t now = event.time.tv_sec * 1000000LL + event.time.tv_usec;
if(last_sync_time)
printf(" rate %lld", 1000000LL / (now - last_sync_time));
last_sync_time = now;
}
printf("%s", newline);
if(event_count && --event_count == 0)
return 0;
}
}
}
}
}
static int open_device(const char *device, int print_flags)
{
int version;
int fd;
int clkid = CLOCK_MONOTONIC;
struct pollfd *new_ufds;
char **new_device_names;
char name[80];
char location[80];
char idstr[80];
struct input_id id;
//省略
fd = open(device, O_RDONLY | O_CLOEXEC);//打开当前节点
ufds[nfds].fd = fd;//赋值给静态的ufds 里面的参数
ufds[nfds].events = POLLIN;//监听有数据我就监听
device_names[nfds] = strdup(device);
nfds++;
return 0;
}
//读取/dev/input路径下面的节点内容变化
static int read_notify(const char *dirname, int nfd, int print_flags)
{
int res;
char devname[PATH_MAX];
char *filename;
char event_buf[512];
int event_size;
int event_pos = 0;
struct inotify_event *event;
res = read(nfd, event_buf, sizeof(event_buf));
if(res < (int)sizeof(*event)) {
if(errno == EINTR)
return 0;
fprintf(stderr, "could not get event, %s\n", strerror(errno));
return 1;
}
strcpy(devname, dirname);
filename = devname + strlen(devname);
*filename++ = '/';
while(res >= (int)sizeof(*event)) {
event = (struct inotify_event *)(event_buf + event_pos);
if(event->len) {
strcpy(filename, event->name);
if(event->mask & IN_CREATE) {
open_device(devname, print_flags);
}
else {
close_device(devname, print_flags);
}
}
event_size = sizeof(*event) + event->len;
res -= event_size;
event_pos += event_size;
}
return 0;
}
//读取扫描路径下的节点
static int scan_dir(const char *dirname, int print_flags)
{
char devname[PATH_MAX];
char *filename;
DIR *dir;
struct dirent *de;
dir = opendir(dirname);
if(dir == NULL)
return -1;
strcpy(devname, dirname);
filename = devname + strlen(devname);
*filename++ = '/';
while((de = readdir(dir))) {
if(de->d_name[0] == '.' &&
(de->d_name[1] == '\0' ||
(de->d_name[1] == '.' && de->d_name[2] == '\0')))
continue;
strcpy(filename, de->d_name);
open_device(devname, print_flags);
}
closedir(dir);
return 0;
}
简单总结:
先初始化ufds并 设置 events为POLLIN ,有数据我就读。
然后 inotify_add_watch 观察 /dev/input 下文增删
scan_dir 扫描/dev/input下并打开节点。 如果是dir就打开, 去读取。 然后open_device打开
然后只要/dev/input 发生了变化,就去 read_notify 去读一下 调用 print_event 打印。
poll和epoll的区别(这段我查的资料)
- poll:
poll是继select之后发展出的一种I/O多路复用技术,它改进了select的最大并发连接数限制(通常为1024)的问题。poll使用链表存储文件描述符,因此可以处理的文件描述符数量理论上只受限于系统内存大小。
尽管解决了文件描述符数量的限制,但poll在检查哪些文件描述符就绪时仍然采用轮询的方式,这意味着当监控大量文件描述符时,效率并不高,尤其是在没有就绪的文件描述符时,也需要遍历整个列表。
- epoll:
epoll是Linux独有的一种更高效的I/O多路复用方式,它在2.6及更高版本的内核中可用。epoll通过维护一个内部的准备就绪描述符集合,实现了事件驱动的通知机制。有如下几个优点。
- 效率:
epoll采用的是事件驱动,只有当文件描述符状态发生变化时才会通知应用程序,因此在高并发连接下效率极高,复杂度接近O(1)。 - 可扩展性: 它没有明确的文件描述符数量限制,能够更好地应对大规模并发连接。
- 内存高效:
epoll使用更少的复制操作,如首次调用epoll_ctl添加文件描述符时进行一次拷贝,之后的epoll_wait调用不再需要频繁地拷贝文件描述符集合。
当然缺点是epoll仅适用于Linux系统,不具有select或poll那样的跨平台性。
为什么 getevent 会执行 该方法
其实getevent只是toolbox这个二进制可执行程序的一个软连接,所以getevent命令其实就是执行toolbox这个二进制文件,所以来看toolbox.c
#define TOOL(name) int name##_main(int, char**); //这里会拼接名字 比如 getevent 变为getevent_main
int main(int argc, char** argv) {
signal(SIGPIPE, SIGPIPE_handler);
char* cmd = strrchr(argv[0], '/');
char* name = cmd ? (cmd + 1) : argv[0];//获取名字name为getevent
for (size_t i = 0; tools[i].name; i++) {
if (!strcmp(tools[i].name, name)) {
return tools[i].func(argc, argv);//这句是关键。这里就会调用到getevent_main方法
}
}
printf("%s: no such tool\n", argv[0]);
return 127;
}
多指协议
有 ab 两种协议,但是大部分都是B协议, B协议数据量精简,能定义到哪个手指头。
1.EV_SYN
同步事件完,在事件开始或完成会有
对应的code:
0004:代表一个事件开始(不必要)
0005:代表一个事件开始(不必要)
SYN_REPORT:代表一个事件的结束 (必要)
2.EV_ABS
事件的一种绝对坐标类型
对应code:
2.0 ABS_MT_SLOT
本质代表者不同手指,它的value代表手指id
2.1 ABS_MT_TRACKING_ID
类型B特有的,实际上,每个slot会和一个ID相对应,一个非负数的表示一次接触,-1表示这是一个无用的slot(或者理解为一次接触的结束) 。无论在接触的类型相对应的slot发生了改变,驱动都应该通过改变这个值来使这个slot失效。并且下一次触摸的ID值会是这次的值加1。
2.2 ABS_MT_POSITION_X,ABS_MT_POSITION_Y
相对于屏幕中心的x,y坐标。
2.3 ABS_MT_TOUCH_MAJOR
接触部分的长轴长度。相当于椭圆的长轴。
2.4 ABS_MT_TOUCH_MINOR
接触部分的短轴长度。相当于椭圆的短轴。
2.5 ABS_MT_PRESSURE
代表按下压力,有的设备不一定有
3.EV_KEY
事件的一种类型。表示是按键(不仅仅指的物理按键也包括TOUCH)事件
对应code:
3.1 BTN_TOUCH
触碰按键。其值是DOWN或者UP。
3.2 BTN_TOOL_FINGER
按键的是finger,并且其值也是DOWN或者UP