系列目录:第一篇:从硬件到应用的事件旅程 |第二篇:EventHub — 原始事件的采集者| 第三篇:InputReader — 原始事件到Android事件的转换引擎 | 第四篇:InputDispatcher — 事件分发与ANR超时机制 | 第五篇:应用侧 — InputChannel、ViewRootImpl与事件消费
一、EventHub 在整个输入系统中的位置
回顾第一篇的总览,EventHub 处于整个事件流水线的最上游:
/dev/input/eventX → EventHub → InputReader → InputDispatcher → APP ▲ 本篇聚焦EventHub 的职责一句话概括:监听所有输入设备,读取内核上报的原始事件,并把它们交给 InputReader。看似简单,但实现中涉及 Linux 的inotify、epoll、ioctl等多个系统调用,是理解 Android 输入系统底层机制的绝佳切入点。
源码位置:
frameworks/native/services/inputflinger/EventHub.cpp frameworks/native/services/inputflinger/EventHub.h二、EventHub 的核心数据结构
2.1 Device 结构体
每个输入设备在 EventHub 中都有一个对应的Device结构体来维护状态:
structDevice{intfd;// 设备文件描述符String8 path;// 设备路径,如 /dev/input/event2DeviceIdentifier identifier;// 设备唯一标识(总线类型、厂商ID、产品ID等)uint32_tclasses;// 设备类别掩码(INPUT_DEVICE_CLASS_*)KeyedVector<int,RawAbsoluteAxisInfo>absoluteAxes;// 绝对坐标轴信息String8 configurationFile;PropertyMap configuration;KeyMap keyMap;// 键盘映射boolenabled;boolignored;};其中classes是最关键的分类字段,通过一系列标志位来标识设备类型:
enum{INPUT_DEVICE_CLASS_KEYBOARD=0x00000001,// 键盘INPUT_DEVICE_CLASS_ALPHAKEY=0x00000002,// 字母键盘INPUT_DEVICE_CLASS_TOUCH=0x00000004,// 触摸屏/触摸板INPUT_DEVICE_CLASS_CURSOR=0x00000008,// 鼠标/轨迹球INPUT_DEVICE_CLASS_TOUCH_MT=0x00000010,// 多点触控屏INPUT_DEVICE_CLASS_DPAD=0x00000020,// 方向键INPUT_DEVICE_CLASS_GAMEPAD=0x00000040,// 游戏手柄INPUT_DEVICE_CLASS_SWITCH=0x00000080,// 开关类INPUT_DEVICE_CLASS_JOYSTICK=0x00000100,// 摇杆/游戏手柄INPUT_DEVICE_CLASS_VIBRATOR=0x00000200,// 振动器INPUT_DEVICE_CLASS_MIC=0x00000400,// 麦克风INPUT_DEVICE_CLASS_EXTERNAL_STYLUS=0x00000800,// 外接手写笔INPUT_DEVICE_CLASS_ROTARY_ENCODER=0x00001000,// 旋转编码器INPUT_DEVICE_CLASS_VIRTUAL=0x40000000,// 虚拟设备INPUT_DEVICE_CLASS_EXTERNAL=0x80000000,// 外部设备};2.2 RawEvent 结构体
EventHub 从内核读取input_event后,封装为RawEvent传递给 InputReader:
structRawEvent{nsecs_t when;// 事件时间戳(纳秒)int32_tdeviceId;// 设备ID(EventHub内部分配)int32_ttype;// 事件类型(EV_KEY, EV_ABS, EV_SYN 等)int32_tcode;// 事件编码int32_tvalue;// 事件值};和内核的input_event相比,RawEvent多了deviceId字段——EventHub 内部为每个设备分配的唯一 ID,后续 InputReader 用它识别事件来源。
三、EventHub 的初始化流程
EventHub在其构造函数中完成了大量初始化工作:
EventHub::EventHub(void){acquire_wake_lock(PARTIAL_WAKE_LOCK,WAKE_LOCK_ID);// 1. 创建 epoll 实例mEpollFd=epoll_create(EPOLL_SIZE_HINT);// 2. 创建 inotify 实例,监听 /dev/input/ 目录mINotifyFd=inotify_init();inotify_add_watch(mINotifyFd,"/dev/input",IN_DELETE|IN_CREATE);// 3. 将 inotify fd 加入 epoll 监控structepoll_eventeventItem;memset(&eventItem,0,sizeof(eventItem));eventItem.events=EPOLLIN;eventItem.data.u32=EPOLL_ID_INOTIFY;epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mINotifyFd,&eventItem);// 4. 创建唤醒管道(用于退出等待)intwakeFds[2];pipe(wakeFds);mWakeReadPipeFd=wakeFds[0];mWakeWritePipeFd=wakeFds[1];fcntl(mWakeReadPipeFd,F_SETFL,O_NONBLOCK);fcntl(mWakeWritePipeFd,F_SETFL,O_NONBLOCK);eventItem.data.u32=EPOLL_ID_WAKE;epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeReadPipeFd,&eventItem);// 5. 检测内核是否支持 EPOLLWAKEUP(3.5+)intmajor,minor;getLinuxRelease(&major,&minor);mUsingEpollWakeup=major>3||(major==3&&minor>=5);// 6. 扫描已有设备scanDevicesLocked();}初始化时创建了三个关键文件描述符加入 epoll 监控:
| 被监控的 fd | 用途 | epoll data 标识 |
|---|---|---|
mINotifyFd | 监听/dev/input/目录变更 | EPOLL_ID_INOTIFY |
mWakeReadPipeFd | 唤醒管道,用于退出等待 | EPOLL_ID_WAKE |
| 各设备 fd | 读取各输入设备的原始事件 | EPOLL_ID_DEVICE + deviceId |
3.1 扫描已有设备
voidEventHub::scanDevicesLocked(){DIR*dir=opendir("/dev/input");while((dirent=readdir(dir))!=NULL){if(strncmp(dirent->d_name,"event",5)==0){String8path("/dev/input/");path.append(dirent->d_name);openDeviceLocked(path);}}closedir(dir);}3.2 打开并配置设备
status_tEventHub::openDeviceLocked(constchar*devicePath){// 1. 以非阻塞方式打开设备intfd=open(devicePath,O_RDWR|O_CLOEXEC|O_NONBLOCK);// 2. 获取设备信息ioctl(fd,EVIOCGNAME(sizeof(name)),name);// 设备名称ioctl(fd,EVIOCGPHYS(sizeof(phys)),phys);// 物理路径ioctl(fd,EVIOCGBIT(0,sizeof(ev_bits)),ev_bits);// 支持的事件类型// 3. 判断设备类别uint32_tclasses=0;if(ev_bits&(1<<EV_KEY))classes|=INPUT_DEVICE_CLASS_KEYBOARD;if(ev_bits&(1<<EV_ABS)){if(test_bit(ABS_MT_POSITION_X,abs_bits))classes|=INPUT_DEVICE_CLASS_TOUCH_MT;elseclasses|=INPUT_DEVICE_CLASS_TOUCH;}if(ev_bits&(1<<EV_REL))classes|=INPUT_DEVICE_CLASS_CURSOR;// 4. 创建设备对象并加入 epollDevice*device=newDevice(fd,devicePath,identifier,classes);structepoll_eventeventItem;eventItem.events=EPOLLIN;eventItem.data.u32=EPOLL_ID_DEVICE+deviceId;// 用 u32 携带 deviceIdepoll_ctl(mEpollFd,EPOLL_CTL_ADD,fd,&eventItem);// 5. 加载键盘映射文件(.kl)device->keyMap.load(identifier,configuration);mDevices.add(deviceId,device);returnOK;}关键设计:epoll_event.data.u32被用来携带deviceId。当epoll_wait返回时,通过u32字段快速定位具体设备,避免遍历mDevices。
四、核心函数:getEvents() 详解
getEvents()是 EventHub 对外暴露的核心接口,InputReader 在无限循环中不断调用它。
4.1 整体流程
getEvents(timeoutMillis, buffer, bufferSize) │ ├── 1. 处理设备变更(热插拔) │ mPendingINotify → 打开/关闭设备 → 生成 DEVICE_ADDED/REMOVED │ ├── 2. epoll_wait() 等待事件(释放锁后阻塞等待) │ │ │ ├── inotify 事件 → 加入 mPendingINotify │ ├── wake 事件 → 退出等待 │ ├── 设备事件 → read() 读取 input_event → 封装 RawEvent │ └── 超时 → 返回 0 │ └── 3. 返回 eventCount4.2 源码逐步解读
size_tEventHub::getEvents(inttimeoutMillis,RawEvent*buffer,size_t bufferSize){size_t eventCount=0;structepoll_eventpendingEventItems[EPOLL_MAX_EVENTS];{AutoMutex_l(mLock);// ===== 步骤1: 处理待处理的设备变更事件 =====while(mPendingINotify&&eventCount<bufferSize){// 生成 DEVICE_ADDED / DEVICE_REMOVED 类型的 RawEvent...}// ===== 步骤2: epoll_wait 等待 =====if(eventCount==0){mLock.unlock();// 关键!释放锁再等待pollResult=epoll_wait(mEpollFd,pendingEventItems,EPOLL_MAX_EVENTS,timeoutMillis);mLock.lock();// 重新获取锁}}// ===== 步骤3: 处理 epoll 返回的事件 =====for(inti=0;i<pollResult;i++){conststructepoll_event&item=pendingEventItems[i];ssize_t deviceIndex=item.data.u32-EPOLL_ID_DEVICE;if(deviceIndex>=0){// --- 3a. 设备事件 ---Device*device=getDeviceByIndexLocked(deviceIndex);while(eventCount<bufferSize){structinput_eventiev;ssize_t readSize=read(device->fd,&iev,sizeof(iev));if(readSize==sizeof(iev)){RawEvent*event=&buffer[eventCount++];event->when=systemTime(SYSTEM_TIME_MONOTONIC);event->deviceId=device->id;event->type=iev.type;event->code=iev.code;event->value=iev.value;}elseif(readSize<0&&errno==EAGAIN){break;// 没有更多数据}else{closeDeviceLocked(*device);// 设备故障break;}}}elseif(item.data.u32==EPOLL_ID_INOTIFY){// --- 3b. 设备热插拔 ---readNotifyLocked();}elseif(item.data.u32==EPOLL_ID_WAKE){// --- 3c. 唤醒事件 ---charbuffer[16];read(mWakeReadPipeFd,buffer,sizeof(buffer));}}returneventCount;}4.3 关键设计细节
(1)锁的释放时机:epoll_wait调用前释放锁,这很关键。如果持锁等待,InputReader 调用getEvents时会阻塞热插拔回调。释放锁后既能及时响应设备事件,又不影响并发访问。
(2)事件批量读取:每次epoll_wait返回后,EventHub 循环read()直到EAGAIN。一次epoll_wait唤醒可能对应多个内核事件(多点触控多个手指同时移动),批量读取减少系统调用。
(3)EV_SYN 的同步语义:内核以EV_SYN / SYN_REPORT分隔一次完整触摸操作。EventHub 不解析这个语义,原样传递给 InputReader。
五、设备热插拔机制
EventHub 通过inotify实现设备即插即用。
5.1 设备添加
用户插入USB键盘 ↓ 内核创建 /dev/input/eventX ↓ inotify 上报 IN_CREATE ↓ epoll_wait 返回 → readNotifyLocked() ↓ openDeviceLocked(devicePath) ├── open() 设备节点 ├── ioctl() 获取设备能力 ├── 判断设备类别 (classes) ├── epoll_ctl(ADD) 加入监控 └── 加入 mDevices 列表 ↓ 生成 DEVICE_ADDED RawEvent → InputReader5.2 设备移除
用户拔出USB键盘 ↓ 内核删除 /dev/input/eventX ↓ inotify 上报 IN_DELETE ↓ closeDeviceLocked() ├── epoll_ctl(DEL) 移出监控 ├── close(fd) 关闭设备 └── 从 mDevices 移除 ↓ 生成 DEVICE_REMOVED RawEvent → InputReader六、唤醒管道(Wake Pipe)
EventHub 设计了一个巧妙的唤醒机制:
// 构造函数中创建管道pipe(mWakeReadPipeFd,mWakeWritePipeFd);epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeReadPipeFd,&eventItem);// 被外部调用,唤醒阻塞中的 getEventsvoidEventHub::wake(){write(mWakeWritePipeFd,"W",1);}当系统需要关闭输入系统时(如关机),InputReader 正阻塞在epoll_wait中。wake()向管道写一个字节,epoll_wait立刻返回,getEvents即可正常退出。
七、设备配置与键盘映射
7.1 .idc 文件(Input Device Configuration)
EventHub 打开设备时会查找对应的配置文件:
/system/usr/idc/Vendor_XXXX_Product_XXXX.idc /data/system/devices/idc/Vendor_XXXX_Product_XXXX.idc.idc可覆盖设备属性,例如:
device.type = touchScreen touch.orientationAware = 17.2 .kl 文件(Key Layout)
对于键盘设备,EventHub 加载.kl文件定义 Linux 键码到 Android 键码的映射:
/system/usr/keylayout/Generic.kl /system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl典型的.kl文件格式:
key 1 ESCAPE key 2 1 key 14 DEL key 29 A key 30 B key 114 VOLUME_DOWN key 115 VOLUME_UP key 116 POWER WAKEKeyMap 的加载发生在openDeviceLocked()中,InputReader 后续用它进行键码转换。
八、线程模型
EventHub 本身没有自己的线程,所有方法都在调用方线程中执行:
InputReader 线程 └── loopOnce() └── mEventHub->getEvents(timeout, buffer, count) └── epoll_wait(...) ← 在 InputReader 线程中阻塞等待九、总结
EventHub 用约 1000 行 C++ 代码,优雅地实现了:
| 能力 | 实现手段 |
|---|---|
| 设备发现 | inotify监听/dev/input/ |
| 多设备并发监听 | epoll同时监控所有设备 fd |
| 设备能力识别 | ioctl(EVIOCGBIT)获取事件类型位图 |
| 阻塞退出 | pipe+wake()通知机制 |
| 设备分类 | 位掩码INPUT_DEVICE_CLASS_* |
| 键码映射 | 加载.kl文件建立查找表 |
| 批量读取 | 循环read()直到EAGAIN |
理解 EventHub 后,接下来我们看 InputReader 如何将原始 RawEvent 加工成 Android 能理解的 KeyEvent 和 MotionEvent。