当前位置: 首页 > news >正文

Android7 Input(三)EventHub

概述

        在Android Input框架中,EventHub主要作用就是读取输入设备上报的event事件, 并将收集的事件提交给InputReader进行处理。本文主要描述了EventHub如何管理系统中的输入设备以及系统上报输入事件的处理流程。本文并没有细节展开描述每一个EventHub类的实现方法和输入事件处理的代码细节,感兴趣的同学可以去分析一下Linux系统中input事件的获取和Linux input事件的组成等。

本文涉及的源码路径

frameworks/native/services/inputflinger/EventHub.cpp

frameworks/native/services/inputflinger/EventHub.h

frameworks/native/services/inputflinger/InputReader.cpp

frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

EventHub数据结构

        在讲解EventHub的功能之前,我们先分析一下EventHub的数据结构组成。EventHub数据结构定义有如下两个类进行管理:

1、EventHubInterface

        虚拟基类定义,主要功能就是接口定义,里面都是纯虚函数, InputReader模块会直接调用这个虚拟类定义的函数接口获取输入设备的事件(通过C++的多态,实际调用EvenHub这个子类实现),部分核心函数定义如下所示:

class EventHubInterface : public virtual RefBase {
protected:
    EventHubInterface() { }
    virtual ~EventHubInterface() { }

public:
    ......

    virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
    virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const = 0;
    virtual int32_t getDeviceControllerNumber(int32_t deviceId) const = 0;
    virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0;
    ......
    virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0;

    ......
    virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
    virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
    virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
            int32_t* outValue) const = 0;
    ......
};

2、EventHub

        EventHubInterface子类的实现,管理Android系统输入设备,主要成员如下所示:

class EventHub : public EventHubInterface
{
public:
    EventHub();

    virtual uint32_t getDeviceClasses(int32_t deviceId) const;
    virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const;
    virtual int32_t getDeviceControllerNumber(int32_t deviceId) const;
    virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const;
    ......

protected:
    virtual ~EventHub();
private:
    struct Device {
        Device* next;
        int fd; // may be -1 if device is virtual
        const int32_t id;
        const String8 path;
        const InputDeviceIdentifier identifier;
        uint32_t classes;
        uint8_t keyBitmask[(KEY_MAX + 1) / 8];
        uint8_t absBitmask[(ABS_MAX + 1) / 8];
        uint8_t relBitmask[(REL_MAX + 1) / 8];
        ......
    };
    ......
    // Input设备管理容器
    KeyedVector<int32_t, Device*> mDevices;

    Device *mOpeningDevices;
    Device *mClosingDevices;
    ......
};

EventHub类的功能:

        1、实现EventHubInterface的接口;

        2、管理系统的输入设备,并将系统中扫描到的输入设备根据设备id保存在mDevices容器中;

EventHub初始化

        1、EventHub对象的创建

                EventHub对象在InputManagerService服务的JNI方法中创建,如下所示:

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();

    /* 创建EventHub()用来获取事件获取*/
    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

继续跟踪InputManager的初始化如下所示:

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    ......
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

        在InputManager对象的初始化中,eventHub作为InputReader的输入参数并最终在初始化InputReader的时候,赋值给InputReader的mEventHub。这个地方非常重要,因为事件获取的起点在InputReader中,InputReader会调用EventHubInterface的接口(子类实现就是EventHub),读取Android系统输入设备的事件后面进行详细讲解。

2、EventHub类的初始化

        EventHub的对象的初始化,如下所示:

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    ......
    /* 创建epoll 监控描述符 */
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    /* 创建inotify ,监控/dev/input目录下文件的创建和删除 */
    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

    /* mINotifyFd文件描述符加入epoll监控 */
    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);

    /* 创建管道 */
    int wakeFds[2];
    result = pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    ......
    /* 读管道描述符加入epoll监控 */
    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    ......
}

EventHub对象的初始化,非常简单,主要完成如下的功能:

        a、创建epoll监控文件描述符, 监控EventHub中输入设备事件;

        b、使用Linux inotify机制,创建监控/dev/input/目录下的文件的创建和删除事件,并加入到epoll进行监控;

        c、创建管道,并且将读管道加入到epoll进行监控(这点地方为什么要创建管道呢,大家思考一下)

EventHub事件

        EventHub获取事件的起点位于InputReader线程,核心代码如下所示:

void InputReader::loopOnce() {
   ......
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
   ......
}

直接调用EventHub的getEvents接口,获取系统的输入事件,getEvents的参数列表如下:

@timeoutMillis:epoll监控超时时间,如果在timeoutMilli还没有等到输入事件,epoll不在等待,进入超时逻辑处理

@buffer: 输出参数,将获取的系统输入事件,都保存在buffer中;

@bufferSize: 最大可保存输入事件缓存大小;

@return: 返回获取到的事件个数

        由于getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize)接口的实现里包含的逻辑比较多,我们进行分段进行分析

设备管理

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ......
    /* 死循环 */
    for (;;) {
        // Reopen input devices if needed.
        // 判断是否需要重现打开设备
        if (mNeedToReopenDevices) {
            ......
            /* 关闭已经打开的设备,并且将待关闭的设备,挂入mClosingDevices设备链表 */
            closeAllDevicesLocked();
            mNeedToScanDevices = true;
            // 函数调用直接返回,InputReader线程再次调用该接口时,进入设备扫描逻辑
            break; // return to the caller before we actually rescan
        }
        ......
        // 关闭挂入该链表的输入设备
        while (mClosingDevices) {
            Device* device = mClosingDevices;
            ......
            mClosingDevices = device->next;
            /* 构造一个设备删除的事件 */
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
            event->type = DEVICE_REMOVED;
            /* 指向下一个保存事件的位置  */
            event += 1;
            /* 删除管理设备的内存 */
            delete device;
            mNeedToSendFinishedDeviceScan = true;
            /* 如果没有缓存空间,保存删除设备事件了,直接调用返回 */
            if (--capacity == 0) {
                break;
            }
        }
        /**
         * 是否需要扫描设备,扫描到的每一个输入设备,都会加入EventHub 设备管理容器
         */
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
        /**
         * 根据EventHub扫描的输入设备,并将每一个发现的设备构造一个event ADD事件保存在buffer中
         */
        while (mOpeningDevices != NULL) {
            Device* device = mOpeningDevices;
            mOpeningDevices = device->next;
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            event->type = DEVICE_ADDED;
            event += 1;
            mNeedToSendFinishedDeviceScan = true;
            if (--capacity == 0) {
                break;
            }
        }

        /* 构造结束扫描事件 */
        if (mNeedToSendFinishedDeviceScan) {
            mNeedToSendFinishedDeviceScan = false;
            event->when = now;
            event->type = FINISHED_DEVICE_SCAN;
            event += 1;
            if (--capacity == 0) {
                break;
            }
        }

设备的管理代码片段,主要完成如下功能:

        1、扫描/dev/input/目录,将发现的每一个输入设备加入到EventHub进行管理;

        2、在获取事件的过程中可能有新的设备加入,并将这种情况,构造event事件保存在getEvents接口提供的输出buffer中;

事件获取

事件获取的代码片段,如下所示:

  /* 等待获取输入事件 */
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

        acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
        mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock

        /* 等待超时,直接返回 */
        if (pollResult == 0) {
            // Timed out.
            mPendingEventCount = 0;
            break;
        }

        /* 等待过程中,被异常打断 */
        if (pollResult < 0) {
            // An error occurred.
            mPendingEventCount = 0;

            // Sleep after errors to avoid locking up the system.
            // Hopefully the error is transient.
            // 等待被中断,稍微sleep一会儿,继续等待事件
            if (errno != EINTR) {
                ALOGW("poll failed (errno=%d)\n", errno);
                usleep(100000);
            }
        } else {
            // Some events occurred.
            // 有input事件上报,u获取上报的事件个数,保存在mPendingEventCount中
            mPendingEventCount = size_t(pollResult);
        }

主要完成如下功能:

        1、epoll进行监听,如果监听超时直接结束死循环函数调用返回;

        2、监听过程中被系统打断,如果这种情况,休眠一下继续监听;

        3、监听到输入事件,并获取事件个数保存在mPendingEventCount;

事件保存处理

        事件保存处理的代码比较多,下面只列出核心处理代码

// 处理获取到的事件数
        while (mPendingEventIndex < mPendingEventCount) {
            // 遍历每一个系统获取的事件
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
            // /dev/input目录发生变化事件,一般是新设备的增加或者旧设备删除
            if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
                if (eventItem.events & EPOLLIN) {
                    mPendingINotify = true;
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
                }
                continue;
            }
           // 对于写入管道的事件,只消耗不处理
            if (eventItem.data.u32 == EPOLL_ID_WAKE) {
                if (eventItem.events & EPOLLIN) {
                    ALOGV("awoken after wake()");
                    awoken = true;
                    char buffer[16];
                    ssize_t nRead;
                    do {
                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
                            eventItem.events);
                }
                continue;
            }
            ......
            // 找到上报事件的设备,并读取该设备上报的事件
            Device* device = mDevices.valueAt(deviceIndex);
            if (eventItem.events & EPOLLIN) {
                int32_t readSize = read(device->fd, readBuffer,
                        sizeof(struct input_event) * capacity);
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                ......
                } else if (readSize < 0) {
                ......
                } else if ((readSize % sizeof(struct input_event)) != 0) {
                    ......
                } else {
                    int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

                    size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];
                        ... ... 
                        event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
                                + nsecs_t(iev.time.tv_usec) * 1000LL;
                       .....
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;
                        event += 1;
                        capacity -= 1;
                    }
                    // 没有缓存,进行保存了,直接结束死循环
                    if (capacity == 0) {
                        mPendingEventIndex -= 1;
                        break;
                    }
                }
            } else if (eventItem.events & EPOLLHUP) {
               ......
            } else {
                ALOGW("Received unexpected epoll event 0x%08x for device %s.",
                        eventItem.events, device->identifier.name.string());
            }
        }

对于事件保存处理代码片段,主要完成如下的功能:

        1、处理设备增加和删除事件;

        2、处理写入管道的事件;

        3、处理设备上报的事件,并将该事件保存在getEvents接口提供的输出buffer中;

事件处理收尾

当所有上报的事件处理完成后执行如下的代码片段跳出事件获取循环,代码片段如下所示:

 ...
        // 所有事件都处理完了,跳出死循环
        if (event != buffer || awoken) {
            break;
        }
        
        ......
    // All done, return the number of events we read.
    // 返回保存到buffer中的事件数
    return event - buffer;
    }

        事件处理的收尾比较简单,所有的事件处理完成后返回获取到的事件和事件数,结束本轮的事件监听。

总结

        1、本文主要描述了EventHub输入设备的管理和事件获取流程描述,并没有细节上去描述;

        2、对于输入设备上报的事件,主要有getEvents接口处理,该接口处理的逻辑比较多,读者在分析代码的时候不要顺序去阅读这个函数实现;

        3、对于输入设备的管理并没有做详细的描述,处理核心的逻辑就是系统扫描到的输入设备创建Struct Device进行管理,并且将每一个输入设备根据它支持的事件类型进行分类。这个在后续讲解的InputReader会提及为什么要将输入设备分类。

相关文章:

  • HTTP响应数据包全面解析:结构、原理与最佳实践
  • [GESP202503 C++六级题解]:P1196:环线
  • 基于Vue的低代码可视化表单设计器 FcDesigner 3.2.11更新说明
  • latex下载软件
  • 蓝桥杯准备(前缀和差分)
  • 【矩阵快速幂】P3702 [SDOI2017] 序列计数|省选-
  • C++ 新特性 | C++ 11 | 移动语义
  • 【huggingface 数据下载】ssh / https 不同的下载流程,hf 镜像下载注意事项
  • ⼆、Kafka客户端消息流转流程
  • Ubuntu环境安装
  • 【网安面经合集】42 道高频 Web 安全面试题全解析(附原理+防御+思路)
  • Java基础-25-继承-方法重写-子类构造器的特点-构造器this的调用
  • 基于langchain实现GraphRAG:基于图结构的检索增强生成系统
  • Linux(24)——系统调优
  • MySQL数据库和表的操作之数据库表操作
  • Day3 蓝桥杯省赛冲刺精炼刷题 —— 排序算法与贪心思维
  • 静态路由与BFD联动实验配置
  • TCP、HTTP、HTTPS、DNS的原理
  • 科技潮流出行新体验 方程豹全新车型钛3正式开启预售
  • linux服务器专题1------redis的安装及简单配置
  • 秦洪看盘|热门股或将退潮,短线波动难免
  • 雀巢中国回应“巴黎水”丑闻报告:在中国销售的产品均符合相关法律法规要求
  • “敌人已经够多了”,菲总统马科斯:愿与杜特尔特家族和解
  • 国家发改委:系统谋划7方面53项配套举措,推动民营经济促进法落地见效
  • 8000余万元黄金投入研发后“不知去向”,咋回事?
  • AI创业者聊大模型应用趋势:可用性和用户需求是关键