Android Framework-Input-1 基础梳理

634 阅读6分钟

Input系列算是我对 Framework 入门的一个模块,涉及服务启动,数据转换,入列,出列,等待,事件消费,分发,移除消息,ANR处理。希望大佬们多多斧正。

常用命令

adb shell  getevent -lrt

image-20240530212352127.png

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并 设置 eventsPOLLIN ,有数据我就读。  然后 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:

epollLinux独有的一种更高效的I/O多路复用方式,它在2.6及更高版本的内核中可用。epoll通过维护一个内部的准备就绪描述符集合,实现了事件驱动的通知机制。有如下几个优点。

  • 效率: epoll采用的是事件驱动,只有当文件描述符状态发生变化时才会通知应用程序,因此在高并发连接下效率极高,复杂度接近O(1)
  • 可扩展性: 它没有明确的文件描述符数量限制,能够更好地应对大规模并发连接。
  • 内存高效: epoll使用更少的复制操作,如首次调用epoll_ctl添加文件描述符时进行一次拷贝,之后的epoll_wait调用不再需要频繁地拷贝文件描述符集合。

当然缺点是epoll仅适用于Linux系统,不具有selectpoll那样的跨平台性。

为什么 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