IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android input 原理分析(二) _ EventHub -> 正文阅读

[移动开发]Android input 原理分析(二) _ EventHub

《原理分析(一)》中简单分析了IMS 的启动过程,大致如下:

  • 通过IMS的构造,创建InputManager 实例,并保存到新建的实例NativeInputManager 中。InputManager实例时分别创建InputDispatcher 和InputReader;
  • 将IMS 的实例传入WSM 中;
  • 将WSM 中input 相关的callback 注册到IMS,方便后面交互;
  • 开启IMS 的启动流程,分别创建Dispatch thread 和read thread。

在分析input 的分发原理前,先来了解所有input 设备event 监听器EventHub。

1. EventHub 构造

构造函数是在create input reader时调用,并将实例作为一个shared_ptr 保存在InputReader 中。下面来详细分析EventHub 构造过程。

frameworks\native\services\inputflinger\reader\EventHub.cpp

EventHub::EventHub(void)
      : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
        mNextDeviceId(1),
        mControllerNumbers(),
        mOpeningDevices(nullptr),   //等待open 的device 默认为空
        mClosingDevices(nullptr),   //等待close 的device 默认为空
        mNeedToSendFinishedDeviceScan(false),  //标记scan 完成
        mNeedToReopenDevices(false),  //标记是否需要reopen 所有input设备
        mNeedToScanDevices(true),     //标记是否scan 所有input 设备,默认是true
        mPendingEventCount(0),        //标记监听到的event 数量
        mPendingEventIndex(0),        //标记处理到的event index
        mPendingINotify(false) {      //标记是否收到dir notify,确认是否会触发readNotifyLocked
    ensureProcessCanBlockSuspend();

    mEpollFd = epoll_create1(EPOLL_CLOEXEC);//创建一个Epoll
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    mINotifyFd = inotify_init();//创建一个inotify,用以监听目录/dev/input 下是否有设备添加/删除
    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    LOG_ALWAYS_FATAL_IF(mInputWd < 0, "Could not register INotify for %s: %s", DEVICE_PATH,
                        strerror(errno));
    ...

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

    int wakeFds[2];
    result = pipe(wakeFds); //创建pipe,用以强制唤醒getEvents,最终唤醒InputReader线程
    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);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
                        errno);
}

构造主要任务:

  • mNeedToScanDevices 置true,用以第一次getEvent 时进行扫描;
  • 创建Epoll 用以监听所有input 设备、inotity、wake pipe;
  • 创建inotify 用以添加/dev/input 下设备的create/delete,对于inotify 详细可以参考https://www.cnblogs.com/jingzhishen/p/3738637.html
  • 创建wake pipe,用以强制唤醒InputReader 线程,write 动作在EventHub::wake(),由InputReader 调用,read 动作在EventHub 的Epoll 中,用以强制结束getEvent;

2. getEvents 函数

getEvents 用以处理Epoll 所有的event,这里分段剖析。

2.1 确认是否reopen devcies

    if (mNeedToReopenDevices) {
        mNeedToReopenDevices = false;
        ...
        closeAllDevicesLocked();
        mNeedToScanDevices = true;
        break; // return to the caller before we actually rescan
    }

变量mNeedToReopenDevices 修改来自InputReader,InputReader thread 中会处理上层的请求,如果上层有发送CHANGE_MUST_REOPEN 请求,InputReader 进而会发送请求给EventHub。目前framework 代码中没有搜到上层发送 CHANGE_MUST_REOPEN 的地方。

-->

下面来详细分析 closeAllDevicesLocked()

void EventHub::closeAllDevicesLocked() {
    mUnattachedVideoDevices.clear();
    while (mDevices.size() > 0) {
        closeDeviceLocked(mDevices.valueAt(mDevices.size() - 1));
    }
}

调用closeDeviceLocked 分别关闭每个设备:

void EventHub::closeDeviceLocked(Device* device) {
    ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x", device->path.c_str(),
          device->identifier.name.c_str(), device->id, device->fd, device->classes);

    if (device->id == mBuiltInKeyboardId) {
        ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
              device->path.c_str(), mBuiltInKeyboardId);
        mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD;
    }

    unregisterDeviceFromEpollLocked(device);
    if (device->videoDevice) {
        // This must be done after the video device is removed from epoll
        mUnattachedVideoDevices.push_back(std::move(device->videoDevice));
    }

    releaseControllerNumberLocked(device);

    mDevices.removeItem(device->id);
    device->close();

    // Unlink for opening devices list if it is present.
    Device* pred = nullptr;
    bool found = false;
    for (Device* entry = mOpeningDevices; entry != nullptr;) {
        if (entry == device) {
            found = true;
            break;
        }
        pred = entry;
        entry = entry->next;
    }
    if (found) {
        // Unlink the device from the opening devices list then delete it.
        // We don't need to tell the client that the device was closed because
        // it does not even know it was opened in the first place.
        ALOGI("Device %s was immediately closed after opening.", device->path.c_str());
        if (pred) {
            pred->next = device->next;
        } else {
            mOpeningDevices = device->next;
        }
        delete device;
    } else {
        // Link into closing devices list.
        // The device will be deleted later after we have informed the client.
        device->next = mClosingDevices;
        mClosingDevices = device;
    }
}

该函数主要任务:

  • 将该device 的fd 从Epoll 中移除;
  • 将该device 从mDevices 中移除;
  • 确认该device 是否正处于mOpeingDevices 中,如果在opening 中,将其移除;
  • 将该device 添加到mClosingDevices 中,在getEvents 中会生成event 抛给InputReader;

2.2 确认是否发送DEVICE_REMOVED 消息

    while (mClosingDevices) {
        Device* device = mClosingDevices;
        ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str());
        mClosingDevices = device->next;
        event->when = now;
        event->deviceId = (device->id == mBuiltInKeyboardId)
                ? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID
                : device->id;
        event->type = DEVICE_REMOVED;
        event += 1;
        delete device;
        mNeedToSendFinishedDeviceScan = true;
        if (--capacity == 0) {
            break;
        }
    }

代码比较简单,当有设备移除的时候,会调用closeDeviceLocked(),并将移除的device 添加到mClosingDevices 中, 这里会通过DEVICE_REMOVED 通知给InputReader。

注意变量mNeedToSendFinishedDeviceScan,用以最后消息FINISHED_DEVICE_SCAN 的结尾发送。

2.3 确认是否需要重新scan 设备

    if (mNeedToScanDevices) {
        mNeedToScanDevices = false;
        scanDevicesLocked();
        mNeedToSendFinishedDeviceScan = true;
    }

有两种情况会触发这里的scan:

  • 第一次运行getEvents 时
  • 要求reopen devices 时,详细查看2.1 节

-->

下面来看下scan devices 的过程:

status_t EventHub::scanDirLocked(const char* dirname) {
    char devname[PATH_MAX];
    char* filename;
    DIR* dir;
    struct dirent* de;
    dir = opendir(dirname);
    if (dir == nullptr) return -1;
    strcpy(devname, dirname);
    filename = devname + strlen(devname);
    *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);
        openDeviceLocked(devname);
    }
    closedir(dir);
    return 0;
}
  • 通过opendir 确定目录名称,例如最开始是/dev/input,如果是目录,会在最后加上“/”,组成/dev/input/
  • 排除文件 "." 和 ".." ,剩下来的文件通过openDeviceLocked() 打开处理

-->

继续来剖析openDeviceLocked() 的处理:

status_t EventHub::openDeviceLocked(const char* devicePath) {
    char buffer[80];

    int fd = open(devicePath, O_RDWR | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) {
        ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
        return -1;
    }

    InputDeviceIdentifier identifier;

    ... //这里获取identifier 的所有属性,包括name/version/product/location/uniqueId/descriptor等等

    // Allocate device.  (The device object takes ownership of the fd at this point.)
    int32_t deviceId = mNextDeviceId++;
    Device* device = new Device(fd, deviceId, devicePath, identifier);

    ...

    // Load the configuration file for the device.
    loadConfigurationLocked(device);

    // Figure out the kinds of events the device reports.
    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
    ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
    ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
    ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
    ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
    ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
    ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);

    // 这里确认设备类型,存入device->classes中,如果device->classes 为0,delete 之前创建的device 实例
    ...
    // Load the key map.
    // We need to do this for joysticks too because the key layout may specify axes.
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
        // Load the keymap for the device.
        keyMapStatus = loadKeyMapLocked(device); //对于keyboard 或joystick 需要进行scan code与keycode 对应
    }
    ...


    if (registerDeviceForEpollLocked(device) != OK) {
        delete device;
        return -1;
    }

    configureFd(device);
    ...
    addDeviceLocked(device);
    return OK;
}

注意代码中的注释,这里处理过程:

  • 打开设备节点,获取fd;
  • 通过fd进行ioctl,确定设备的name、version、vendor、product、location、uniqueId 等信息,并存放到identifier 中,这里identifier 中存放着设备的所有信息,后期可以用来比对设备的信息,也可以根据identifier 进行Key map 的解析。
  • 通过fd、identifier 创建Device 实例;
  • 通过fd 进行ioctl 对device 进行一下初始化;
  • 确认设备类型classes,例如INPUT_DEVICE_CLASS_KEYBOARD、INPUT_DEVICE_CLASS_CURSOR、INPUT_DEVICE_CLASS_TOUCH、INPUT_DEVICE_CLASS_JOYSTICK、INPUT_DEVICE_CLASS_VIBRATOR、INPUT_DEVICE_CLASS_MIC、INPUT_DEVICE_CLASS_DPAD等等;
  • 通过registerDeviceForEpollLocked 注册设备的fd 到Epoll 进行event 监听;
  • 将device 添加到mDevices 中,并更新mOpeningDevices 以便发送消息通知上层

注意,loadKeyMapLocked 需要进行scan code 与keycode 对应,后面单独一篇分析,详细请参考 《原理分析(三)》

2.4 确认是否发送DEVICE_ADDED 消息

    while (mOpeningDevices != nullptr) {
        Device* device = mOpeningDevices;
        ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());
        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;
        }
    }

同消息DEVICE_REMOVED,当有设备新增时都会调用addDevcieLocked() 进行更新,并将消息DEVICE_ADDED 通知给InputReader。

2.5 确认是否发送消息 FINISHED_DEVICE_SCAN

    if (mNeedToSendFinishedDeviceScan) {
        mNeedToSendFinishedDeviceScan = false;
        event->when = now;
        event->type = FINISHED_DEVICE_SCAN;
        event += 1;
        if (--capacity == 0) {
            break;
        }
    }

在2.2节、2.3节、2.4节,设备发生变化时会有类似scan 的过程,都会将变量mNeedToSendFinishedDeviceScan 标记上,最后发送消息 FINISHED_DEVICE_SCAN 标记完成scan。

2.6 等待event 触发

int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

 if (pollResult < 0) {
    ...
} else {
    // Some events occurred.
    mPendingEventCount = size_t(pollResult);
}

进入epoll_wait 机制,等待epoll 的触发,触发后会将mPendingEventCount 和mPendingEventItems 带入下一次循环中进行解析。

2.7 处理event

    bool deviceChanged = false;
    while (mPendingEventIndex < mPendingEventCount) { //epoll_wait 等到了event
        const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
        if (eventItem.data.fd == mINotifyFd) { //解析到了inotify event
            if (eventItem.events & EPOLLIN) {
                mPendingINotify = true; //标记需要read inotify
            } else {
                ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
            }
            continue;
        }

        if (eventItem.data.fd == mWakeReadPipeFd) { //解析到了wake pipe的event,即InputReader thread 需要唤醒
            if (eventItem.events & EPOLLIN) {
                ALOGV("awoken after wake()");
                awoken = true; //主要是置这个变量
                char buffer[16];
                ssize_t nRead;
                do {
                    nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); //确认能读到pipe 内容,当时貌似不关心内容
                } 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 = getDeviceByFdLocked(eventItem.data.fd); //通过fd 获取到device实例
        if (!device) {
            ALOGE("Received unexpected epoll event 0x%08x for unknown fd %d.", eventItem.events,
                  eventItem.data.fd);
            ALOG_ASSERT(!DEBUG);
            continue; //如果没有这个设备或者被close了就忽略这个event
        }
        ...
        // This must be an input event
        if (eventItem.events & EPOLLIN) { //如果是input device 的event
            int32_t readSize =
                    read(device->fd, readBuffer, sizeof(struct input_event) * capacity); //读取input device 的具体信息
            if (readSize == 0 || (readSize < 0 && errno == ENODEV)) { //如果设备异常,需要close掉
                // 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 { //读取正确,将input device 的event抛回给InputReader 进行进一步解析
                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->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;
                }
            }
        }
        ...
    }
    // 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.
    if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
        mPendingINotify = false;
        readNotifyLocked();
        deviceChanged = true;
    }

    // Report added or removed devices immediately.
    if (deviceChanged) {
        continue;
    }

    // Return now if we have collected any events or if we were explicitly awoken.
    if (event != buffer || awoken) {
        break;
    }
  • 解析整个eventItem,需要全部的解析一遍,如果遇到特殊event,例如inotify 或者wake 则会进行标记,如果遇到input device 的event,则存放起来以便抛回给InputReader;
  • 如果是出现了deviceChanged 时函数不会立即break,而是continue 进行DEVICE_ADDED / DEVICE_REMOVED 消息的记录,然后陪同input event 一起返回,即使有wake event同时存在的情况;
  • 注意变量capacity,这个要求InputReader 在调用getEvents 需要确保记录event 的buffer 足够大;

至此,EventHub 分析基本完成,大致如下:

  • 通过Epoll 对不同的fd 进行监听,fd 包括input device 、inotify fd、wake pipe fd;
  • 在第一个getEvents 会对设备进行scan,后面根据inotify event 进行动态监听/dev/input 目录下设备的create/delete;
  • 收到event 后inotify 的优先处理,会直接continue 循环进行DEVICE_ADDED / DEVICE_REMOVED 的消息记录;
  • InputReader 在调用getEvents 时需要尽可能确保readBuffer 足够大;

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2021-07-25 11:48:06  更:2021-07-25 11:50:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/26 23:58:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码