“
Android 输入系统系列文章:
在前面的文章中我们分析过 IMS 在 native 层的 InputManager 中,事件的传递流程:
InputReader -> UnwantedInteractionBlocker -> InputClassifier -> InputDispatcher
最开始接触事件的角色是 InputReader ,它启动了一个 InputThread 来执行 loopOnce 函数:
void InputReader::loopOnce() {
// ...
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); // 【1】获取事件数量
{ // acquire lock
std::scoped_lock _l(mLock);
mReaderIsAliveCondition.notify_all();
if (count) {
processEventsLocked(mEventBuffer, count); // 【2】处理事件
}
// timeout and input device changed process ...
} // release lock
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices); // 输入设备发生变化,发出消息
}
// 将排队的事件刷新到侦听器。
mQueuedListener.flush();
}
在这个函数中描述的事件的获取以及将其进行处理的流程:
-
【1】通过 mEventHub->getEvents 函数读取事件数量,实际上也是在这里将事件序列保存到了 mEventBuffer 指针中。 -
【2】调用自己的 processEventsLocked 函数去加锁处理事件。
可以看出这里有一些重点信息:
-
mEventHub -
mEventBuffer -
mEventHub 的 getEvents 函数 -
InputReader 的 processEventsLocked 函数
最后一个 processEventsLocked 函数在上一篇文章中分析过,剩余的三个接下来逐个进行分析。
mEventHub
mEventHub 的定义如下:
// in InputReader.h
// 这可能是 unique_ptr,但由于 InputReader 测试的编写方式,它在这里成为 shared_ptr。 在测试中,测试保留 EventHub 引用,同时将其传递给 InputReader。
std::shared_ptr<EventHubInterface> mEventHub;
mEventHub 本意是一个指针,它的赋值是在 InputReader 的构造函数中:
InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
const sp<InputReaderPolicyInterface>& policy,
InputListenerInterface& listener)
: mContext(this),
mEventHub(eventHub), // 【here】
mPolicy(policy),
mQueuedListener(listener),
mGlobalMetaState(AMETA_NONE),
mLedMetaState(AMETA_NONE),
mGeneration(1),
mNextInputDeviceId(END_RESERVED_ID),
mDisableVirtualKeysTimeout(LLONG_MIN),
mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
refreshConfigurationLocked(0);
updateGlobalMetaStateLocked();
}
在 InputReader 的创建流程中,InputManager 的构造函数中通过 createInputReader 函数创建了 mReader 指针:
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
// ...
mReader = createInputReader(readerPolicy, *mBlocker);
}
createInputReader 方法中,创建了 eventHub 实例:
std::unique_ptr<InputReaderInterface> createInputReader(
const sp<InputReaderPolicyInterface>& policy, InputListenerInterface& listener) {
return std::make_unique<InputReader>(std::make_unique<EventHub>(), policy, listener);
}
但准备工作此刻还没有结束,我们顺便看看 EventHub 的构造函数中做了些什么:
EventHub::EventHub(void)
: mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
mNextDeviceId(1),
mControllerNumbers(),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false),
mNeedToScanDevices(true),
mPendingEventCount(0),
mPendingEventIndex(0),
mPendingINotify(false) {
ensureProcessCanBlockSuspend(); // 确保处理可以阻塞中断
mEpollFd = epoll_create1(EPOLL_CLOEXEC); // 创建一个系统的轮询,mEpollFd (文件描述符)代表结果
mINotifyFd = inotify_init1(IN_CLOEXEC);
std::error_code errorCode;
bool isDeviceInotifyAdded = false;
if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) {
addDeviceInputInotify(); // 添加设备输入通知
} else {
addDeviceInotify(); // 添加设备通知
isDeviceInotifyAdded = true;
if (errorCode) {
ALOGW("Could not run filesystem::exists() due to error %d : %s.", errorCode.value(), errorCode.message().c_str());
}
}
// ...
}
在 EventHub 的构造方法中,重点的部分是根据 DEVICE_INPUT_PATH 输入设备路径是否存在,添加了不同的 Inotify 监听, addDeviceInputInotify 和 addDeviceInotify ,它们都是监控某一目录下的通知:
/**
* 在没有任何输入设备的设备上(如某些开发板), /dev/input 目录将不存在。 然而,用户稍后仍可插入输入设备。
* 仅当 /dev/input 出现时添加监视 /dev/input 的内容。
*/
void EventHub::addDeviceInputInotify() {
mDeviceInputWd = inotify_add_watch(mINotifyFd, DEVICE_INPUT_PATH, IN_DELETE | IN_CREATE);
LOG_ALWAYS_FATAL_IF(mDeviceInputWd < 0, "Could not register INotify for %s: %s",
DEVICE_INPUT_PATH, strerror(errno));
}
void EventHub::addDeviceInotify() {
mDeviceWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
LOG_ALWAYS_FATAL_IF(mDeviceWd < 0, "Could not register INotify for %s: %s", DEVICE_PATH,
strerror(errno));
}
这两个函数的区别是路径参数不同:
static const char* DEVICE_INPUT_PATH = "/dev/input";
// v4l2 devices go directly into /dev
static const char* DEVICE_PATH = "/dev";
这里是通过 Linux 的 inotify 机制(notify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知) 告知系统输入设备的变化。这里只监听了添加和删除。
mEventBuffer
mEventBuffer 十分重要,实际上它就是保存读取到的事件的队列。它的定义是:
// The event queue.
static const int EVENT_BUFFER_SIZE = 256;
RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock);
这是一个 RawEvent 类型的数组,容量是 256 。RawEvent 是从 EventHub 获取到的真实事件的结构体:
struct RawEvent {
// Time when the event happened
nsecs_t when;
// EventHub 读取事件的时间。 仅为输入事件填充。
// 对于其他事件(设备添加/删除/等),此值未定义且不应读取。
nsecs_t readTime;
int32_t deviceId;
int32_t type;
int32_t code;
int32_t value;
};
mEventBuffer 的引用只有两个位置:

前者接下来的 getEvents 函数中会进行分析;后者在 processEventsLocked 函数可以参考 【2】Native 层输入事件的处理机制 中的相关讲解。
整体逻辑是:在 getEvents 函数中,将读取到的事件保存在 mEventBuffer 数组中,然后交给 processEventsLocked 进行处理。
EventHub 的 getEvents 函数
InputReader 在 InputThread 线程中循环执行 loopOnce 方法直到 InputThread 被销毁,而在 loopOnce 中最重要的在于 EventHub 的 getEvents 函数。
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
std::scoped_lock _l(mLock);
struct input_event readBuffer[bufferSize];
RawEvent* event = buffer;
size_t capacity = bufferSize;
bool awoken = false;
// 【核心逻辑】
for (;;) {
// ...
}
// All done, return the number of events we read.
return event - buffer;
}
上面的代码是 getEvents 函数的大致逻辑,核心部分是中间的死循环中的代码。
在启动死循环之前,创建了一个锁、一个保存输入事件的 readBuffer ,这里的 bufferSize 是 EVENT_BUFFER_SIZE (256);将参数 buffer 用一个新的指针 event 标记,这里的 buffer 就是 mEventBuffer 。
最后的返回逻辑,是 event 指针在读取消息进行保存后的内存地址减去最开始的 mEventBuffer 指针指向的地址,也就是两者的差值即是此次 getEvents 方法调用读取到的事件数量。
核心循环部分
在这一部分逻辑比较多,下面描述一下整体的逻辑:
-
检查是否需要重新打开输入设备; -
待移除的设备生成对应的事件,加入事件队列; -
如有需要重新扫描设备; -
待添加的设备生成对应的事件,加入事件队列; -
设备扫描完成后生成对应的事件,加入事件队列; -
处理待执行的事件,这里是核心的一步; -
更新设备信息并生成设备变化事件,加入事件队列; -
如果收集到事件或被唤醒,跳出循环; -
若没有收集到事件或被唤醒,通过 epoll 机制轮询待执行的事件,保存到待执行的数组中。
1. 检查是否需要重新打开设备
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
// Reopen input devices if needed.
if (mNeedToReopenDevices) {
mNeedToReopenDevices = false;
ALOGI("Reopening all input devices due to a configuration change.");
// 关闭所有设备
closeAllDevicesLocked();
mNeedToScanDevices = true;
break; // 在实际重新扫描设备前返回给调用者
}
如果需要重新打开设备,在这一步中先将所有的设备关闭,然后将 mNeedToScanDevices 置为 true,在下次循环的第三步中去重新扫描设备。
2. 生成设备移除事件
for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
std::unique_ptr<Device> device = std::move(*it);
event->when = now;
event->deviceId = (device->id == mBuiltInKeyboardId)
? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID
: device->id;
event->type = DEVICE_REMOVED;
event += 1;
it = mClosingDevices.erase(it);
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
启动了一个对 mClosingDevices 的遍历,也就是说,如果 mClosingDevices 中存在内容,将会对每个元素产生一个类型是 DEVICE_REMOVED 的 event,event += 1;
代表 mEventBuffer 中的元素在增加,mNeedToSendFinishedDeviceScan 变更为 true ,此标志位用于执行后续对于设备扫描完成后的处理工作。直到 mEventBuffer 数组中容纳不下新数据,跳出循环。
3. 如有需要重新扫描设备
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
在步骤 1 中变更的 mNeedToScanDevices ,在这里起到了判断的作用,执行 scanDevicesLocked 进行扫描,然后更新 mNeedToSendFinishedDeviceScan ,在扫描完成后执行下一步操作。
4. 生成设备添加事件
while (!mOpeningDevices.empty()) {
std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
mOpeningDevices.pop_back();
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
event->type = DEVICE_ADDED;
event += 1;
// 尝试通过比较设备名称来找到匹配的 videoDevice
for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end(); it++) {
std::unique_ptr<TouchVideoDevice>& videoDevice = *it;
if (tryAddVideoDeviceLocked(*device, videoDevice)) {
// videoDevice was transferred to 'device'
it = mUnattachedVideoDevices.erase(it);
break;
}
}
// 添加或进行替换
auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
if (!inserted) {
ALOGW("Device id %d exists, replaced.", device->id);
}
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
这里和第 2 步一样,对所有正在开启的设备生成事件保存到事件队列。
5. 生成设备扫描完成后的事件
// 在报告了最近一次扫描中添加/删除的所有设备时发送消息。该事件总是至少发送一次。
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
event->when = now;
event->type = FINISHED_DEVICE_SCAN;
event += 1;
if (--capacity == 0) {
break;
}
}
6. 处理待执行的事件
在这一个步骤中开始读取事件进行处理,主要是结构如下:
根据 mPendingEventIndex 与 mPendingEventCount 遍历 mPendingEventItems。
mPendingEventItems 是待处理的 epoll 事件数组;mPendingEventIndex 是下一个要处理的事件的索引。
struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
size_t mPendingEventCount;
size_t mPendingEventIndex;
“
这里用到了 linux 的 Inotify 特性,它监控文件系统操作,比如读取、写入和创建。Inotify 反应灵敏,用法非常简单。
来自 epoll 的事件
首先是两个针对事件来源的判断,当待处理事件的 eventItem.data.fd
等于 mINotifyFd 时:
// 来自 inotify
if (eventItem.data.fd == mINotifyFd) {
if (eventItem.events & EPOLLIN) { // 事件可读
mPendingINotify = true;
} else {
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
}
continue;
}
“
EPOLLIN :表示对应的文件描述符可以读; EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读; EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET:表示对应的文件描述符有事件发生;
在确认事件可读后,将 mPendingINotify 置为 true ,否则表示事件无法处理, 并在此进行下一次循环。
唤醒事件
if (eventItem.data.fd == mWakeReadPipeFd) {
if (eventItem.events & EPOLLIN) { // 事件可读
ALOGV("awoken after wake()");
awoken = true;
char wakeReadBuffer[16];
ssize_t nRead;
do {
nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer)); // 读取事件
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer)); // EINTR指Interrupted system call, 表示由于中断而错误返回.
} else {
ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.", eventItem.events);
}
continue;
}
Device 检查
// 设备处理
Device* device = getDeviceByFdLocked(eventItem.data.fd);
if (device == nullptr) {
ALOGE("Received unexpected epoll event 0x%08x for unknown fd %d.", eventItem.events, eventItem.data.fd);
ALOG_ASSERT(!DEBUG);
continue;
}
// 对于 videoDevice 相关处理,这里忽略
在这一步检查 Device ,确保事件来自设备的输入。
输入事件
在完成上述步骤后,就开始处理输入事件了,下面的逻辑描述了整体的处理规则:

事件可读的情况下,根据 read 函数的返回值进行判断,检查事件是否能够正常处理:
-
结果等于 0,或不等于 0 ,errno 代表尚未分配到具体设备的情况下,关闭设备重启; -
结果小于 0 ,表示出现错误,不做任何处理; -
对结果数量进行检查,不对不做任何处理; -
执行处理输入逻辑。
在最后一步处理事件的逻辑:
// todo 处理事件的位置
int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
// 挨个事件塞进 mEventBuffer
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 = processEventTimestamp(iev);
event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);
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;
}
这里 event 指针是参数 buffer ,也就是 mEventBuffer ,也就是事件保存到了 mEventBuffer 中,后续逻辑在 InputReader.loopOnce 方法中,通过 processEventsLocked(mEventBuffer, count); 去处理事件。
7. 更新设备信息并进行生成设备变化事件
将修改设备列表,因此这必须在处理所有其他事件之后完成,以确保我们在关闭设备之前读取所有剩余事件。
// readNotify() will modify the list of devices so this must be done after
// processing all other events to ensure that we read all remaining events
// before closing the devices.
// readNotify() 将修改设备列表,因此这必须在处理所有其他事件之后完成,以确保我们在关闭设备之前读取所有剩余事件。
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
mPendingINotify = false;
readNotifyLocked();
deviceChanged = true;
}
// Report added or removed devices immediately.
if (deviceChanged) {
continue;
}
8. 如果收集到了事件或被唤醒,结束循环
// 如果我们收集了任何事件或者我们被明确唤醒,请立即返回。
if (event != buffer || awoken) {
break;
}
9. 若没有收集到事件或唤醒,通过 epoll 机制轮询事件
mPendingEventIndex = 0;
mLock.unlock(); // 轮询前释放锁
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mLock.lock(); // 轮询后重新加锁
// 轮询结果等于 0
if (pollResult == 0) {
// Timed out.
mPendingEventCount = 0;
break;
}
// 小于 0 出现错误
if (pollResult < 0) {
// An error occurred.
mPendingEventCount = 0;
// 出错后休眠以避免锁定系统。
// 希望错误是暂时的。
if (errno != EINTR) {
ALOGW("poll failed (errno=%d)n", errno);
usleep(100000);
}
} else {
// Some events occurred. 事件出现
mPendingEventCount = size_t(pollResult);
}
这里的 epoll_wait 函数是 Linux 的 epoll 机制的方法之一:
int epoll_wait (int epfd , struct epoll_event * events,
int maxevents,int timeout);
“
events 指向了事件缓冲区,每当 interest list 中fd(文件描述符)触发事件加入到ready list 后,会将其返回到事件缓冲区中。最大返回 maxevents 个,因此 maxevents 至少大于 0,返回的 event 顺序写入缓冲区。 timeout 参数指定 epoll_wait 将阻塞的毫秒数。
epoll_wait(…) 将会一直阻塞直到:fd产生事件 / 被信号处理函数打断 / 超时。
可以看出,epoll_wait 函数会将数据写入到第二个参数 mPendingEventItems 中,并根据 epoll_wait 函数的返回值判断是否出现错误或超时的情况。正常情况更新等待执行的数量。
总结
最后回顾整个 EventHub 的 getEvents 函数,再来看整体的事件传递流程:
-
从 Linux 内核的 epoll 机制读取事件保存到 mPendingEventItems; -
将 mPendingEventItems 中的事件和其他设备增删事件等添加到 mEventBuffer; -
通过 mEventBuffer 传递给 InputReader 传递给下一层。
最后再梳理一下 EventHub 的 getEvents 函数中的流程:
-
定义一些在函数中用到的成员变量;
-
开启死循环
-
检查是否需要重新打开输入设备 -
待移除的设备生成对应的事件,加入事件队列 -
如有需要重新扫描设备 -
待添加的设备生成对应的事件,加入事件队列 -
设备扫描完成后生成对应的事件,加入事件队列 -
处理待执行的事件,这里是核心的一步 -
更新设备信息并生成设备变化事件,加入事件队列 -
如果收集到事件或被唤醒,跳出循环 -
若没有收集到事件或被唤醒,通过 epoll 机制轮询待执行的事件,保存到待执行的数组中 -
全部完成,返回我们读取到的事件数量(通过读取事件前后的指针的内存地址差值得出)。
一直
原文始发于微信公众号(八千里路山与海):Android 输入系统【3】输入事件的起点
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84996.html