Android输入系统(一)事件读取

简述

输入系统用于接收和响应外界输入,如触摸屏幕,按下按键,滑动鼠标等。其中屏幕,鼠标等称为输入设备 ,设备作为硬件受到不同的外界刺激比如触摸产生相应反应称为输入事件

我们知道Linux万物皆文件,输入设备在Linux上也体现为文件,存在相应的输入设备时/dev/input目录下便会生成相应的文件,输入设备产生相应事件时相应的设备文件就会发生变化。EventHub这个类监听/dev/input目录下的变化就可以了解输入设备的插拔,监听相应的设备文件就可以了解该设备的输入事件变化。下面贴下输入系统的流程简图辅助理解。

EventHub

EventHub基于INotifyEpoll机制实现对输入设备和输入事件的监听。基本逻辑是在EventHub的构造函数内初始化INotify和Epoll,然后在getEvents函数内阻塞监听输入事件,而getEvents由InputReader循环调用。

首先看下构造函数源码

C++ 复制代码
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);   //初始化Epoll对象,返回相应文件描述符
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    mINotifyFd = inotify_init1(IN_CLOEXEC);  //初始化INotify对象,返回相应文件描述符

    std::error_code errorCode;
    bool isDeviceInotifyAdded = false;
    if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) {
        addDeviceInputInotify(); //将 /dev/input 目录添加进Inotify监听
    } else {
        addDeviceInotify(); // 当/dev/input 目录不存在时,将 /dev 目录添加进Inotify监听
        isDeviceInotifyAdded = true;
        if (errorCode) {
            ALOGW("Could not run filesystem::exists() due to error %d : %s.", errorCode.value(),
                  errorCode.message().c_str());
        }
    }

    if (isV4lScanningEnabled() && !isDeviceInotifyAdded) {
        addDeviceInotify();  //如果开启V4l扫描则无论是否存在 /dev/input 目录都要监听 /dev 目录
    } else {
        ALOGI("Video device scanning disabled");
    }

    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);  // 将上面的INotify添加到Epoll监听
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);

    int wakeFds[2];
    result = pipe2(wakeFds, O_CLOEXEC);  //创建匿名管道
    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.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);  // 将管道读端添加到Epoll监听
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
                        errno);
}

构造函数做的事结合注释看下源码就知道了,简单讲下注释没有写的东西。

  1. xx_CLOEXEC标志的作用都是一样的。在子进程调用exec命令后自动关闭文件描述符,具体的自行了解close-on-exec,随便贴一个博客供参考。
  2. 如果存在/dev/input目录则说明至少存在一个输入设备,系统直接监听/dev/input目录就可以知道设备的插拔和输入事件。如果没有/dev/input目录则监听/dev目录,这样当第一个输入设备插入时内核创建/dev/input目录系统能收到通知。
  3. 因为v4l设备比较特殊,它是直接创建在/dev目录下的。所以如果开启了v4l扫描,则无论/dev/input目录是否存在都要监听/dev目录。
  4. 管道的作用是中断Epoll的监听。如果在Epoll阻塞等待期间想中断阻塞去处理一些事情,那就往管道里写点东西,Epoll监听到管道可读自然就会结束阻塞返回函数了。

在看getEvents函数之前需要了解几个概念。

  1. 从设备文件内读取出来的是原始设备输入事件input_event,由Linux输入子系统定义,具体的type和code代表的意义参考文档
  2. 经过封装后向InputReader暴露的是RawEvent,包含设备(增删)事件和设备原始输入事件,在EventHub.h内定义。

接下来我们看下EventHub的getEvents函数。

该函数传入三个参数,超时时间、存放事件的Buffer、Buffer的大小,返回读取到的事件个数。主要流程如下(下面的流程是包裹在for循环里面的):

  1. 如果需要重新打开输入设备,即mNeedToReopenDevices值为true时,则关闭所有输入设备并直接返回getEvents方法(下次调用再重新扫描并打开输入设备)。
  2. 如果有已经打开的设备被关闭,即mClosingDevices不为空,则为每个被关闭的设备填充关闭设备事件(如果关闭设备事件达到Buffer上限则打断mClosingDevices遍历。感觉这种处理有点问题,因为只是打断mClosingDevices遍历并没有直接返回,后面还是会继续填充Buffer,会导致数组越界的。这个后面有时间再研究确认一下)
  3. mNeedToScanDevices值为true时重新扫描加载设备(第一次或者关闭所有设备后会重新扫描加载所有设备)
  4. 如果有新增设备,即mOpeningDevices不为空,则为每个新增设备填充新增设备事件(如果事件达到Buffer上限则打断mOpeningDevices遍历。)
  5. mNeedToSendFinishedDeviceScan值为true时,填充设备扫描完成事件(关闭,打开,重新扫描设备都会使该值为true)
  6. 遍历处理下面epoll_wait读取到的结果
    1. epoll事件的fd是mINotifyFd代表是设备增删事件,设置mPendingINotify为true,继续遍历下一个epoll事件。
    2. epoll事件的fd是mWakeReadPipeFd代表是唤醒中断事件,设置awoken为true并丢弃管道内的数据,继续遍历下一个epoll事件。
    3. 如果根据fd查找不到对应设备,则继续遍历下一个epoll事件。
    4. 如果是是输入设备的videoDevice的事件,则根据情况调用videoDevice的readAndQueueFrames函数或关闭该设备的videoDevice,继续遍历下一个epoll事件。
    5. 走到这一步说明是正常的设备原始输入事件,则根据情况读取设备原始输入事件(input_event)转换成RawEvent填充进事件Buffer中或者关闭设备。需要注意的是,如果事件Buffer已经满了则打断epoll事件的遍历走下一步从而返回getEvents方法,否则继续遍历下一个epoll事件。
  7. 如果有设备增删等待处理(mPendingINotify为true)并且其他epoll事件已经处理完毕(设备的输入事件必须在设备关闭事件之前,所以先将输入事件处理完(填充进事件Buffer中)再去处理设备增删的epoll事件),则调用readNotifyLocked()函数读取发生变化的目录,从而打开或者关闭相应的设备。
  8. 如果有已经处理的设备增删(deviceChanged为true),则调用continue重新从第一步开始,目的是将设备增删事件填充进事件Buffer中。
  9. 如果事件Buffer不为空(event!=buffer)或者awoken为true,则break中断循环,返回getEvents方法。
  10. epoll_wait监听等待,等阻塞回调后如果结果是0代表超时,结果小于0代表出错,结果大于0代表epoll事件个数,用mPendingEventCount记录下epoll事件个数后重新循环。

结合注释和上面的流程说明过一遍下面的源码:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);

    std::scoped_lock _l(mLock);

    struct input_event readBuffer[bufferSize];

    RawEvent* event = buffer;
    size_t capacity = bufferSize;
    bool awoken = false;
    for (;;) {
        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; // 1. 如果需要重新打开设备那先关闭所有设备后直接返回,下次调用再真正重新扫描打开
        }

        // 2. 填充关闭设备事件
        for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
            std::unique_ptr<Device> device = std::move(*it);
            ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str());
            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;
            }
        }
        // 3. 扫描并打开所有设备
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
        // 4. 填充设备新增事件
        while (!mOpeningDevices.empty()) {
            std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
            mOpeningDevices.pop_back();
            ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            event->type = DEVICE_ADDED;
            event += 1;

            // Try to find a matching video device by comparing device names
            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;
            }
        }
        // 5. 填充设备扫描完成事件
        if (mNeedToSendFinishedDeviceScan) {
            mNeedToSendFinishedDeviceScan = false;
            event->when = now;
            event->type = FINISHED_DEVICE_SCAN;
            event += 1;
            if (--capacity == 0) {
                break;
            }
        }

        // 6. 遍历处理epoll_wait事件
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) {
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
            if (eventItem.data.fd == mINotifyFd) {
                if (eventItem.events & EPOLLIN) {
                    mPendingINotify = true;
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
                }
                continue;
            }

            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));
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
                          eventItem.events);
                }
                continue;
            }

            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;
            }
            if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) {
                if (eventItem.events & EPOLLIN) {
                    size_t numFrames = device->videoDevice->readAndQueueFrames();
                    if (numFrames == 0) {
                        ALOGE("Received epoll event for video device %s, but could not read frame",
                              device->videoDevice->getName().c_str());
                    }
                } else if (eventItem.events & EPOLLHUP) {
                    // TODO(b/121395353) - consider adding EPOLLRDHUP
                    ALOGI("Removing video device %s due to epoll hang-up event.",
                          device->videoDevice->getName().c_str());
                    unregisterVideoDeviceFromEpollLocked(*device->videoDevice);
                    device->videoDevice = nullptr;
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
                          device->videoDevice->getName().c_str());
                    ALOG_ASSERT(!DEBUG);
                }
                continue;
            }
            // This must be an input event
            if (eventItem.events & EPOLLIN) {
                int32_t readSize =
                        read(device->fd, readBuffer, sizeof(struct input_event) * capacity);
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                    // Device was removed before INotify noticed.
                    ALOGW("could not get event, removed? (fd: %d size: %" PRId32
                          " bufferSize: %zu capacity: %zu errno: %d)\n",
                          device->fd, readSize, bufferSize, capacity, errno);
                    deviceChanged = true;
                    closeDeviceLocked(*device);
                } else if (readSize < 0) {
                    if (errno != EAGAIN && errno != EINTR) {
                        ALOGW("could not get event (errno=%d)", errno);
                    }
                } else if ((readSize % sizeof(struct input_event)) != 0) {
                    ALOGE("could not get event (wrong size: %d)", readSize);
                } 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 = 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) {
                        // The result buffer is full.  Reset the pending event index
                        // so we will try to read the device again on the next iteration.
                        mPendingEventIndex -= 1;
                        break;
                    }
                }
            } else if (eventItem.events & EPOLLHUP) {
                ALOGI("Removing device %s due to epoll hang-up event.",
                      device->identifier.name.c_str());
                deviceChanged = true;
                closeDeviceLocked(*device);
            } else {
                ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
                      device->identifier.name.c_str());
            }
        }

        // 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.
        //7. 如果有设备增删等待处理(`mPendingINotify`为true)并且其他epoll事件已经处理完毕,则调用`readNotifyLocked()`函数读取发生变化的目录,从而打开或者关闭相应的设备。
        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
            mPendingINotify = false;
            readNotifyLocked();
            deviceChanged = true;
        }

        // Report added or removed devices immediately.
        // 8. 如果有已经处理的设备增删(deviceChanged为true),则调用continue重新从第一步开始,目的是将设备增删事件填充进事件Buffer中。
        if (deviceChanged) {
            continue;
        }

        // 9. 如果事件Buffer不为空(event!=buffer)或者`awoken`为true,则break中断循环,返回getEvents方法。
        if (event != buffer || awoken) {
            break;
        }

        // Poll for events.
        // When a device driver has pending (unread) events, it acquires
        // a kernel wake lock.  Once the last pending event has been read, the device
        // driver will release the kernel wake lock, but the epoll will hold the wakelock,
        // since we are using EPOLLWAKEUP. The wakelock is released by the epoll when epoll_wait
        // is called again for the same fd that produced the event.
        // Thus the system can only sleep if there are no events pending or
        // currently being processed.
        //
        // The timeout is advisory only.  If the device is asleep, it will not wake just to
        // service the timeout.
        mPendingEventIndex = 0;

        mLock.unlock(); // release lock before poll

        //10. epoll_wait监听等待,等阻塞回调后如果结果是0代表超时,结果小于0代表出错,结果大于0代表epoll事件个数,用`mPendingEventCount`记录下epoll事件个数后重新循环。
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

        mLock.lock(); // reacquire lock after poll

        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.
            if (errno != EINTR) {
                ALOGW("poll failed (errno=%d)\n", errno);
                usleep(100000);
            }
        } else {
            // Some events occurred.
            mPendingEventCount = size_t(pollResult);
        }
    }

    // All done, return the number of events we read.
    return event - buffer;
}

看完上面的代码后EventHub最核心的功能已经基本了解了,相信事件的产生和监听到EventHub这层已经有了比较明确的概念,接下来看下InputReader怎么调用EventHub的getEvents()函数和通过InputDispatcher分发监听到的事件。

相关推荐
氤氲息2 小时前
Android 底部tab,使用recycleview实现
android
Clockwiseee2 小时前
PHP之伪协议
android·开发语言·php
小林爱2 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
小何开发4 小时前
Android Studio 安装教程
android·ide·android studio
开发者阿伟4 小时前
Android Jetpack LiveData源码解析
android·android jetpack
weixin_438150994 小时前
广州大彩串口屏安卓/linux触摸屏四路CVBS输入实现同时显示!
android·单片机
CheungChunChiu5 小时前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd
木头没有瓜5 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
键盘侠0075 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp
江上清风山间明月6 小时前
flutter bottomSheet 控件详解
android·flutter·底部导航·bottomsheet