input事件的获取

input事件的获取loop线程已经运行起来了,如果不出意外,它是不会终止的;不妨以此为起点,再开始一段新的旅程,我要去探索input事件的获取。一EventHub构造函数EventHub是所有输入事件的中央处理站,凡是与输入事件有关的事它都管。上帝创造万事万物都是有原因的,看看构造它是出于什么目的。EventHub::EventHub(void):mBuiltInKeybo

大家好,又见面了,我是你们的朋友全栈君。

loop线程已经运行起来了,如果不出意外,它是不会终止的;不妨以此为起点,再开始一段新的旅程,我要去探索input事件的获取。

一 EventHub构造函数


EventHub是所有输入事件的中央处理站,凡是与输入事件有关的事它都管。上帝创造万事万物都是有原因的,看看构造它是出于什么目的。

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s.  errno=%d",
            DEVICE_PATH, errno);

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);

    int wakeFds[2];
    result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
            errno);

    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
            errno);

    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
}

前面一堆类似mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD)初始化成员变量的就比较简单了,向下看需要补充点知识,epoll机制和inotify机制。

1 epoll_create()生成一个epoll专用的描述符mEpollFd。

mINotifyFd = inotify_init();

2 添加一个epoll事件,监测mINotifyFd文件描述符可读,eventItem.events = EPOLLIN表示可读。

eventItem.data.u32 = EPOLL_ID_INOTIFY;

result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);

3 创建一个inotify实例,返回一个文件描述符。

mINotifyFd = inotify_init();

4 添加一个watch,监测DEVICE_PATH的创建和删除。

static const char *DEVICE_PATH = “/dev/input”;

IN_DELETE | IN_CREATE表示添加和删除。

int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

至此,要知道的是有个inotify的watch一直监视着”/dev/input”的创建和删除;有个epoll可以查询,要使用epoll_wait查询imINotifyFd的变化是否可读)。

5 貌似出现了一个系统调用,它是pipe(),于是我们得到了唤醒读pipe和唤醒写pipe,如果向mWakeWritePipeFd写,那么mWakeReadPipeFd就会有变化。

mWakeReadPipeFd = wakeFds[0];

  mWakeWritePipeFd = wakeFds[1];

6 用fcntl()将读写pipe都设置为非阻塞方式,避免读空pipe、写满pipe时的阻塞。

result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);

result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);

7 又添加了一个epoll事件,这次是为了查询读pipe可读。

eventItem.data.u32 = EPOLL_ID_WAKE;

result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);

8 下面的用于epoll通知,与device无关,后面会用到。

eventItem.data.u32 = EPOLL_ID_INOTIFY;

eventItem.data.u32 = EPOLL_ID_WAKE;

二 EventHub::getEvents


说好InputReader::loopOnce是起点的,该回来集合了。

class InputReader : public InputReaderInterface {
......
  static const int EVENT_BUFFER_SIZE = 256;
  RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
}
void InputReader::loopOnce() {
......
  size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
......
}

EventHub::getEvents()要做的事情太多了,一点一点分析吧。

1 RawEvent

从EventHub中取出的原始事件。

struct RawEvent {
    nsecs_t when; //时间
    int32_t deviceId; //device ID,如果是内嵌键盘mBuiltInKeyboardId为0
    int32_t type; //device操作,添加,移除或者事件类型
    int32_t code; //事件编码
    int32_t value; //值
};

2 input_event

这是kernel里完全对应的一个事件结构

struct input_event {
 struct timeval time;
 __u16 type;
 __u16 code;
 __s32 value;
};

3 mNeedToReopenDevices是说需要重复打开,构造EventHub的时候,它肯定是false的;还不知道什么时候需要这个东东,先放一放。

4 mClosingDevices是说有device added/removed了,初始化的时候它是0,又飘过。

5 mNeedToScanDevices是说需要扫描设备,它是true不能再飘了。

void EventHub::scanDevicesLocked() {
    status_t res = scanDirLocked(DEVICE_PATH);
    if(res < 0) {
        ALOGE("scan dir failed for %s\n", DEVICE_PATH);
    }
    if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
        createVirtualKeyboardLocked();
    }
}
status_t EventHub::scanDirLocked(const char *dirname)
{
    char devname[PATH_MAX];
    char *filename;
    DIR *dir;
    struct dirent *de;
    dir = opendir(dirname); //打开目录"/dev/input"
    if(dir == NULL)
        return -1;
    strcpy(devname, dirname); //devname = "/dev/input"
    filename = devname + strlen(devname);//filename就是devname上的一个游标,此时游到了strlen(devname)处
    *filename++ = '/';//devname = "/dev/input/",filename又游了一格
    while((de = readdir(dir))) {//返回目录中下一个文件的文件名,文件名以在文件系统中的排序返回。

        if(de->d_name[0] == '.' &&//一个点表示当前目录
           (de->d_name[1] == '
status_t EventHub::scanDirLocked(const char *dirname)
{
char devname[PATH_MAX];
char *filename;
DIR *dir;
struct dirent *de;
dir = opendir(dirname); //打开目录"/dev/input"
if(dir == NULL)
return -1;
strcpy(devname, dirname); //devname = "/dev/input"
filename = devname + strlen(devname);//filename就是devname上的一个游标,此时游到了strlen(devname)处
*filename++ = '/';//devname = "/dev/input/",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);//假设找到为event0,则devname = "/dev/input/event0"
openDeviceLocked(devname);
/*openDeviceLocked创建device,并初始化device->configuration(IDC配置文件),device->KeyMap->keyLayoutMap(*kl按键布局文件)、device->KeyMap->keyCharacterMap(按键字符映射文件)。还初始化了device->classes输入设备类别,比如device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT。创建device->id和device的映射。
*/
}//通过while循环创建/dev/input目录文件对应的所以device
closedir(dir);
return 0;
}
' ||//两个点表示上一级目录 (de->d_name[1] == '.' && de->d_name[2] == '
status_t EventHub::scanDirLocked(const char *dirname)
{
char devname[PATH_MAX];
char *filename;
DIR *dir;
struct dirent *de;
dir = opendir(dirname); //打开目录"/dev/input"
if(dir == NULL)
return -1;
strcpy(devname, dirname); //devname = "/dev/input"
filename = devname + strlen(devname);//filename就是devname上的一个游标,此时游到了strlen(devname)处
*filename++ = '/';//devname = "/dev/input/",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);//假设找到为event0,则devname = "/dev/input/event0"
openDeviceLocked(devname);
/*openDeviceLocked创建device,并初始化device->configuration(IDC配置文件),device->KeyMap->keyLayoutMap(*kl按键布局文件)、device->KeyMap->keyCharacterMap(按键字符映射文件)。还初始化了device->classes输入设备类别,比如device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT。创建device->id和device的映射。
*/
}//通过while循环创建/dev/input目录文件对应的所以device
closedir(dir);
return 0;
}
')))//这些都不是想要的 continue; strcpy(filename, de->d_name);//假设找到为event0,则devname = "/dev/input/event0" openDeviceLocked(devname); /*openDeviceLocked创建device,并初始化device->configuration(IDC配置文件),device->KeyMap->keyLayoutMap(*kl按键布局文件)、device->KeyMap->keyCharacterMap(按键字符映射文件)。还初始化了device->classes输入设备类别,比如device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT。创建device->id和device的映射。 */ }//通过while循环创建/dev/input目录文件对应的所以device closedir(dir); return 0; }

status_t EventHub::openDeviceLocked(const char *devicePath)

scanDevicesLocked()中如果没有找到device->id为VIRTUAL_KEYBOARD_ID(-1)的device,则创建一个device为-1的虚拟键盘device。之所以能这样找就是因为openDeviceLocked()创建了device->id和device的映射。

扫描完device要设置标志mNeedToSendFinishedDeviceScan = true。

6 while (mOpeningDevices != NULL)一直到open的device处理完为止,event就是InputReader的mEventBuffer[EVENT_BUFFER_SIZE],capacity是EVENT_BUFFER_SIZE(256),也就是说目前支持同时处理256个device。这些device都是需要add的。

7 FINISHED_DEVICE_SCAN是个什么事件?这是event最后一次一定会发送的事件,会上报所有添加/删除设备事件中最后一次扫描到的事件。

8 mPendingEventIndex和mPendingEventCount构造的时候是0,所以第一次for循环不会进来,所以mPendingINotify为false,所以deviceChanged也为false,而event != buffer,这个for就退出来了。返回到loopOnce(),进入处理流程。

       if (count) {
            processEventsLocked(mEventBuffer, count);
        }

InputReader::processEventsLocked()中根据rawEvent->type进行事件处理。到下一次进入getEvents()时,event != buffer就不会成立了,就可以epoll_wait()来查询前面设置的几个事件是否发生,有几个?一个是mINotifyFd,一个是mWakeReadPipeFd,一个是我们open的input device。

9 到下下一次进入getEvents()时,mPendingEventIndex < mPendingEventCount就满足了,接着分类处理epoll_event。

(1) eventItem.data.u32 == EPOLL_ID_INOTIFY,mPendingINotify = true。在后面,

readNotify()将会改变deives列表,所以必须在处理了所有event之后执行,确保关闭device之前,我们读完了所以剩余事件。可见,这个notify机制是监测是否有device移除的。

InputReader::processEventsLocked()中根据rawEvent->type进行事件处理。到下一次进入getEvents()时,event != buffer就不会成立了,就可以epoll_wait()来查询前面设置的几个事件是否发生,有几个?一个是mINotifyFd,一个是mWakeReadPipeFd,一个是我们open的input device。

9 到下下一次进入getEvents()时,mPendingEventIndex < mPendingEventCount就满足了,接着分类处理epoll_event。

(1) eventItem.data.u32 == EPOLL_ID_INOTIFY,mPendingINotify = true。在后面,

readNotify()将会改变deives列表,所以必须在处理了所有event之后执行,确保关闭device之前,我们读完了所以剩余事件。可见,这个notify机制是监测是否有device移除的。

        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
            mPendingINotify = false;
            readNotifyLocked();
            deviceChanged = true;
        }
status_t EventHub::readNotifyLocked() {
    int res;
    char devname[PATH_MAX];
    char *filename;
    char event_buf[512];
    int event_size;
    int event_pos = 0;
    struct inotify_event *event;

    ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd);
    res = read(mINotifyFd, event_buf, sizeof(event_buf));//读取notify的事件,就是dev/input有没有增加或者删除
    if(res < (int)sizeof(*event)) {//没读到事件
        if(errno == EINTR)
            return 0;
        ALOGW("could not get event, %s\n", strerror(errno));
        return -1;
    }
    //printf("got %d bytes of event information\n", res);

    strcpy(devname, DEVICE_PATH);
    filename = devname + strlen(devname);
    *filename++ = '/';//dev/input/

    while(res >= (int)sizeof(*event)) {//读到了事件
        event = (struct inotify_event *)(event_buf + event_pos);
        //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
        if(event->len) {
            strcpy(filename, event->name);
            if(event->mask & IN_CREATE) {//如果事件掩码是创建新文件
                openDeviceLocked(devname);//这个函数专门一篇文章说了一下
            } else {
                ALOGI("Removing device '%s' due to inotify event\n", devname);
                closeDeviceByPathLocked(devname);
/*
mOpeningDevices标记的刚刚open的第一个device,当所有RawEvent的DEVICE_ADDED事件都处理完后,mOpeningDevices为NULL。所以close的时候,先看一下通过mOpeningDevices能不能找到要close的device,如果能,分情况:
要删除的device是mOpeningDevices链中的一个,那么找到要删除的前一个pred,pred->next = device->next;然后delete device。
要删除的device是mOpeningDevices,那就没有前一个了mOpeningDevices = device->next;然后delete device。
如果不能,现在就不能删除了,万一还有事件没有处理完,它的client还在呢,得通知它。现在只做标记:
        device->next = mClosingDevices;
        mClosingDevices = device;
显然在下一次getEvents()中会处理。
*/
            }
        }
        event_size = sizeof(*event) + event->len;
        res -= event_size;
        event_pos += event_size;
    }
    return 0;
}

(2) eventItem.data.u32 == EPOLL_ID_WAKE,awoken = true。还要读mWakeReadPipeFd,一直读到没有东西可读为止。为什么能读到,说明有写mWakeWritePipeFd阿。

void EventHub::wake() {
    ALOGV("wake() called");

    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite != 1 && errno != EAGAIN) {
        ALOGW("Could not write wake signal, errno=%d", errno);
    }
}

那什么时候需要wake()呢?比如requestRefreshConfiguration,需要重新load配置文件的时候,我们就不能继续处理epoll_wait()查询到的事件了,要break出for循环,更新了配置文件后再来处理epoll_wait()查询到的事件。

(3) 最后就到了input event了。eventItem.data.u32就是device->id,如果存在,就能找到对应的device。如果device不存在了,执行close动作。读错了给出警告。再继续就是正确的动作了。

一个input事件确实产生的时候,与内核进入evdev所有事件的简单时间戳相比,有些input外设可能有更好的时间概念。这是Android定制的input协议扩展,主要用于基于device drivers的虚拟input设备。iev.type == EV_MSC表示事件类型是重写时间戳。iev.code == MSC_ANDROID_TIME_SEC是秒,iev.code == MSC_ANDROID_TIME_USEC是微妙。接下来重要的是copy事件。

#else
                        event->when = now;
#endif
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;

每copy一个事件event += 1;事件buffer加1,capacity -= 1;buffer长度减一。capacity == 0表示buffer已经满了,只能下一次循环再把事件读到buffer里了,先break出处理epoll事件的while,去loopOnce()里处理下满的buffer;别忘了事件指针mPendingEventIndex -= 1,不然下次不读了。

eventItem.events & EPOLLHUP表示有事件删除,需要close input设备。

上述(1)、(4)和mPendingINotify && mPendingEventIndex >= mPendingEventCount满足时涉及到/dev/input device增加和减少的都会设置deviceChanged = true.

10 如果deviceChanged = true立即处理device的add和remove,用了个continue,返回到for的开始了,有需要close的设备就执行while (mClosingDevices),增加一个DEVICE_REMOVED事件;需要add的设备,readNotifyLocked()时,mOpeningDevices就不为NULL了,再添加DEVICE_ADDED,最后还要添加FINISHED_DEVICE_SCAN。这里就很疑问,如果deviceChanged = true和buffer满了,同时出现就有问题,要立即处理deviceChanged,event会溢出;仔细看,同时出现的情况是不存在的。

11 至此,还有一个mNeedToReopenDevices的标志没有说,什么时候用到这个标志?例如刷新了config文件,refreshConfigurationLocked()->(mEventHub->requestReopenDevices())->mNeedToReopenDevices = true。

        // Reopen input devices if needed.
        if (mNeedToReopenDevices) {
            mNeedToReopenDevices = false;

            ALOGI("Reopening all input devices due to a configuration change.");

            closeAllDevicesLocked();
            mNeedToScanDevices = true;
            break; // return to the caller before we actually rescan
        }

这很简单了,先关闭所有device,设置重新扫描标志,break出while,就进入loopOnce()处理了;再回来的时候就重新扫描了。

写完这些input事件就获取到了,会保存在RawEvent mEventBuffer[EVENT_BUFFER_SIZE]中。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/132897.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 各种关闭eslint方法总结[通俗易懂]

    各种关闭eslint方法总结[通俗易懂]1、package.json关闭eslint直接注释掉package.json文件中eslint的配置”eslintConfig”:{“root”:true,////此项是用来告诉eslint找当前配置文件不能往父级查找”env”:{“node”:true//此项指定环境的全局变量,下面的配置指定为node环境},”extends”:[//此项是用来配置vue.js风格,就是说写代码的时候要规范的写,如果你使用vs-code我

  • 基于Android开发的天气预报app(源码下载)「建议收藏」

    基于Android开发的天气预报app(源码下载)「建议收藏」基于AndroidStudio环境开发的天气app-系统总体介绍:本天气app使用AndroidStudio这个IDE工具在Windows10系统下进行开发。主要实现了:1、定位城市天气显示;2、城市编辑功能(增、删、改、查)以及对应天气显示信息的改变;3、天气信息的Widget窗口显示(城市的编辑功能可以远程的更新Widget窗口信息的显示)4、下拉刷新、天气显示界面左右滑动、城市拖拽等小模…

  • arduino中Keypad 库函数介绍

    arduino中Keypad 库函数介绍原文:https://playground.arduino.cc/Code/Keypad/Creation构造函数:Keypad(makeKeymap(userKeymap),row[],col[],rows,cols)constbyterows=4;//fourrowsconstbytecols=3;//threecolumnscharkeys[rows][cols]={{‘1′,’2′,’3’},{‘4′,’5′,’6’},{‘

  • paddle深度学习基础之训练调试与优化

    paddle深度学习基础之训练调试与优化上一节咱们讨论了四种不同的优化算法,这一节,咱们讨论训练过程中的优化问题。本次代码修改模型全是在卷积神经网络

  • mysql数据库日志存储位置_MySQL数据库之mysql日志文件在哪 如何修改MySQL日志文件位置…「建议收藏」

    mysql数据库日志存储位置_MySQL数据库之mysql日志文件在哪 如何修改MySQL日志文件位置…「建议收藏」本文主要向大家介绍了MySQL数据库之mysql日志文件在哪如何修改MySQL日志文件位置,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助。MySQL日志文件相信大家都有很多的了解,MySQL日志文件一般在:/var/log/mysqld.log,下面就教您修改MySQL日志文件位置的方法,供您参考。今天需要改MySQL日志文件的位置,发现在/etc/my.cnf中怎么也改不…

    2022年10月14日
  • 【Unity3D开发小游戏】《文字冒险游戏》Unity开发教程

    【Unity3D开发小游戏】《文字冒险游戏》Unity开发教程基本程序设计(故事卡)游戏会为玩家呈现一个“故事卡”。故事卡上包含一些文字,其中一部分是用于描述玩家当前的状态,另外一部分是在当前情况下玩家可以做出的一系列选择。根据玩家的不同选择,剧情也会按照不同的分支向前发展,并持续出现新的卡片与选择,直到最终的卡片不再有新的选择,则游戏结束。制作一张“故事卡”很简单。根据上诉需求,我们新建StoryCard脚本,脚本代码如下:StoryCard在…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号