Input系统——InputReader流程分析(一)

1,126 阅读12分钟

基于android-10.0.0_r47版本分析

一、InputReader流程简介

上一次讲到,Input系统的大体流程,介绍IMS服务的启动过程中会创建两个native线程,分别是InputReaderInputDispatcher,接下来从InputReader线程的执行过程从threadLoop为起点开始分析。

InputReaderInputManager创建,并运行于InputReaderThread线程中。那InputReader如何在InputReaderThread中运行呢?

InputReaderThread继承自C的Thread类,Thread类封装了pthread线程工具,提供了与JavaThread类相似的APIC++Thread类提供了一个名为threadLoop()的纯虚函数,当线程开始运行后,将会在内建的线程循环中不断地调用threadLoop(),直到此函数返回false,则退出线程循环,从而结束线程。

InputReaderThread仅仅重写了threadLoop()函数,其线程循环中不断地执行InputReader.loopOnce()函数,并且loopOnce()中包含了InputReader的所有工作。

接下来分析一下loopOnce()中做了什么:

// frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::loopOnce() {
    ...
    /* ①通过EventHub读取事件列表。读取的结果存储在参数mEventBuffer中,返回值表示事件的个数,
    当EventHub中五事件可以读取时,此函数的调用将会阻塞直到事件到来或者超时 */
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    ...
    { // acquire lock
        AutoMutex _l(mLock);
        ...
        if (count) {
            // ②如果有读取事件,则调用processEventsLocked()函数对事件进行加工处理
            processEventsLocked(mEventBuffer, count);
        }
        ...
    }
    ...
    /* ③发布事件。processEventsLocked()函数在对事件进行加工处理之后,便将处理后的事件存储在
    mQueueListener中。在循环的最后,通过调用flush()函数将所有事件交付给InputDispatcher */
    mQueuedListener->flush();        
}

根据上面精简的代码注释,可以看出,InputReader的一次线程循环的工作思路非常清晰,一共三步:

  1. 首先从EventHub中读取未处理的事件列表。这些事件分为两类,
    • 一类是从设备节点中读取的原始输入事件。
    • 另一类则是输入设备可用性变化事件,简称为设备事件。
  2. 通过processEventsLocked()对事件进行处理。对于设备事件,此函数对根据设备的可用性加载或移除设备对应的配置信息。对于原始输入事件,则再进行转译、封装与加工后将结果暂存到mQueueListener中。
  3. 所有事件处理完毕后,调用mQueueListener.flush()将所有暂存的输入事件一次性地交付给InputDispatcher

这便是InputReader的总体工作流程,接下来将详细讨论这三步的实现。

二、从EventHub中读取事件

InputReader在线程循环中的一个工作便是从EventHub中读取一批未处理的事件。

EventHub就跟我们使用的USB Hub一样,接入不同的输入端集中交给PC,而这里它将所有的输入事件通过一个接口(getEvents)把从多个输入设备节点中读取的事件交给InputReader,它是输入系统最底层的一个组件。而它主要是由INotifyEpoll两套机制组成。

EventHub的构造函数初始化了Epoll对象和INotify对象,分别监听原始输入事件与设备节点增删事件。同时将INotify对象的可读性事件也注册到Epoll中,因为EventHub可以像处理原始输入事件一样监听设备节点增删事件。

首先看一下EventHub的构造


EventHub::EventHub(void) :
mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
mOpeningDevices(nullptr), mClosingDevices(nullptr),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false), mNeedToScanDevices(true),
mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

    mEpollFd = epoll_create1(EPOLL_CLOEXEC);

    mINotifyFd = inotify_init();
    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);

    int wakeFds[2];
    result = pipe(wakeFds);
    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);

    int major, minor;
    getLinuxRelease(&major, &minor);
    // EPOLLWAKEUP was introduced in kernel 3.5
    mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);

    char buf[PROPERTY_VALUE_MAX] = {0};
    property_get("persist.sys.input.log", buf, "false");
    if(!strcmp(buf,"true")){
        gEventHubLog = true;
        ALOGD("EventHub debug log is enabled");
    } else if (!strcmp(buf, "false")) {
        gEventHubLog = false;
        ALOGD("EventHub debug log is disabled");
    }
}

InputReader::loopOnce()函数调用EventHub::getEvents()函数获取事件列表,所以这个getEvents()EventHub运行的动力所在,几乎包含了EventHub的所有工作内容,因此首先要将getEvents()函数的工作方式搞清楚

getEvents()函数的签名如下:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize)

此函数将尽可能多地读取设备增删事件与原始输入事件,将它们封装为RawEvent结构体,并放入buffer中供InputReader进行处理。RawEvent结构体的定义如下:

/*
 * A raw event as retrieved from the EventHub.
 */
struct RawEvent {
    nsecs_t when; /* 发生事件时的时间戳 */
    int32_t deviceId; /* 产生事件的设备Id,它是由EventHub自行分配的,InputReader可以根据它从EventHub中获取次设备的详细信息 */
    int32_t type; /* 事件类型 */
    int32_t code; /* 事件代码 */
    int32_t value; /* 事件值 */
};

RawEvent结构体与getevent工具的输出十分一致,包含了原始输入事件的4个基本元素。RawEvent同时也用来表示设备增删事件,EventHub定义了三个特殊的事件类型DEVICE_ADDDEVICE_REMOVEDFINISHED_DEVICE_SCAN,用来与原始输入数据进行区分。

EventHub作为直接操作设备节点的输入系统组件,隐藏了INotifyEpoll以及设备节点读取等底层操作,通过一个简单的接口getEvents()向使用者提供了读取设备事件与原始输入事件的功能。

getEvents()函数的本质是通过epoll_wait()获取Epoll事件到事件池,并对事件池中的事件进行消费的过程。从epoll_wait()的调用开始到事件池中最后一个事件被消费完毕的过程称为EventHub的一个监听周期。

三、原始事件的加工

前面已经从EventHub中取到了事件,接下来是调用processEventsLocked()来处理列表中的所有事件

frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        // 处理原始事件部分
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            // 处理设备增删事件
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}

可以看到该函数还是比较清晰的各位两部分,遍历所有事件,根据type一部分处理原始输入事件,另一部分处理设备增删事件,这里主要关注如何对原始输入事件进行处理的

对于原始输入事件,由于EventHub会将属于同一输入设备的原始输入事件放在一起,因为processEventsLocked()可以使processEventsDeviceLocked()同时处理来自同一输入设备的一批事件。

接下来看下processEventsDeviceLocked()函数的实现:

frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    if (deviceIndex < 0) {
        ALOGW("Discarding event for unknown deviceId %d.", deviceId);
        return;
    }

    InputDevice* device = mDevices.valueAt(deviceIndex);
    if (device->isIgnored()) {
        //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return;
    }

    device->process(rawEvents, count);
}

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
        if (mDropUntilNextSync) {
            // 处理事件同步错误
            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                mDropUntilNextSync = false;
            }
            ...
        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
            ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
            mDropUntilNextSync = true;
            reset(rawEvent->when);
        } else {
            // InputDevice中有一个InputMapper对象的列表,可以看出实际的输入事件处理位于InputMapper的process函数中
            for (InputMapper* mapper : mMappers) {
                mapper->process(rawEvent);
            }
        }
        --count;
    }
}

mDevices的添加是在addDeviceLocked()添加设备时存储进去的,相当于添加的一个deviceId对应InputDevice的一个map

  • InputDevice是一个用来存储输入设备信息的类,它描述了一个输入设备,并且以设备idkey保存在mDevices容器中
  • InputMapperInputReader中实际进行原始输入事件加工的场所,它有一系列的子类,分别用于加工不同类型的原始输入事件

(一) InputDevice的创建

看一下InputDevice创建的流程,这也牵扯到它关联的InputMapper,进行后续的分发

inputDevice既然用来表示一个输入设备,不难想象到,应该是设备添加时创建的,与EventHub的设备增删事件有关

  1. addDevicesLocked()中从EventHub中获取厂商信息与设备类别
  2. 通过createDeviceLocked()函数创建InputDevice对象
  3. 使用InputReader中保存的策略配置信息对新建的InputDevice进行策略配置,并通过reset()进行设备重置
  4. InputDevice放入mDevices容器中

(二) InputMapper的分配

如前面所述,InputMapper完成了原始输入事件的加工处理,而InputMapper的分配依据是根据设备类型,这个设备类型来自EventHubDevice结构体,在EventHubopenDeviceLocked()通过事件位掩码确定了设备可以上报的事件类型后,便可以据此确定设备的类型了。AndroidEventHub.h中定义以下设备类型(可能版本不同有细微变化):

  • INPUT_DEVICE_CLASS_KEYBOARD:可以上报鼠标按键以外的EV_KEY类型事件的设备。如键盘、机身按钮
  • INPUT_DEVICE_CLASS_ALPHAKEY:可以上报字符按键的设备,例如键盘。此类型的设备必定同时属于KEYBOARD
  • INPUT_DEVICE_CLASS_TOUCH:可以上报EV_ABS类型事件的设备都属于此类,如触摸屏和触控板
  • INPUT_DEVICE_CLASS_CURSOR:可以上报EV_REL类型的事件,并且可以上报BTN_MOUSE子类的EV_KEY事件的设备属于此类,例如鼠标和轨迹球
  • INPUT_DEVICE_CLASS_TOUCH_MT:可以上报EV_ABS类型事件,并且其事件位掩码指示其支持多点事件的设备属于此类。例如多点触摸屏,这类设备同时也属于TOUCH类型
  • INPUT_DEVICE_CLASS_DPAD:可以上报方向键的设备,如键盘,手机导航等,这类设备同时也属于KEYBOARD
  • INPUT_DEVICE_CLASS_GAMEPAD:可以上报游戏按键的设备,如游戏手柄。同时也属于KEYBOARD
  • INPUT_DEVICE_CLASS_SWITCH:可以上报EV_SW类型事件的设备
  • INPUT_DEVICE_CLASS_JOYSTICK:属于GAMEPAD类型,并且属于TOUCH类型的设备
  • INPUT_DEVICE_CLASS_VIBRATOR:支持力反馈的设备
  • INPUT_DEVICE_CLASS_MIC:有麦克风
  • INPUT_DEVICE_CLASS_EXTERNAL_STYLUS:输入设备是外部手写笔
  • INPUT_DEVICE_CLASS_ROTARY_ENCODER:具有旋转编码器的设备
  • INPUT_DEVICE_CLASS_VIRTUAL:虚拟设备
  • INPUT_DEVICE_CLASS_EXTERNAL:外部设备,即非内建设备。如外接鼠标、键盘、游戏手柄等

确定设备类型后,InputReadercreateDeviceLocked()InputDevice分配InputMapper

最常见的是Touch事件和Key事件,后面以Touch事件进行分析

(三) Touch类型事件的加工处理

(1) Touch 类型事件的信息与原始事件的组织方式

Touch类型事件描述了用户的一次点击操作,对应于EV_ABS类型的原始输入事件。点击的对象可以是触摸屏、触控板等,我们可以统称其为传感器,而发生点击动作的可以是手指,也可以是触控笔,可以称为触摸工具。

Android输入系统对一次点击操作的描述信息的支持非常多,对一次点击操作的完整描述信息包括以下内容:

  • 点击坐标,描述点击操作的坐标位置。和点击坐标有关的传感器属性包括坐标精度(传感器的密集程度)、坐标范围(传感器总面积)以及噪音漂移(传感器的抗噪能力)等。
  • 点击区域,无论是手指还是触控笔,落在传感器上不可能是一个点,而是一个区域。如果支持点击区域的识别,输人设备使用一个长轴(Major Axis)及短轴(Minor Axis)表示的椭圆来近似地描述触摸工具落在传感器上的区域。
  • 触摸工具到传感器的距离,有些高级的传感器可以识别悬停在其上的触摸工具,因此对于这种设备,触摸工具到传感器的距离是一项必要的信息。
  • 压力,高级的传感器可以识别触摸工具施加在传感器上的压力大小。
  • 触摸工具的类型,有些传感器可以对手指及触控笔做出区分。

由于一次点击动作包含如此多的信息,因此一条原始输入事件是无法完整描述的。为了解决这个问题,Linux的输入子系统使用多个原始输入事件对一次复杂的输入事件进行描述,其中每一个事件描述一项信息,并在完整描述设备所产生的所有信息之后,使用一条特殊类型的事件标识一次输入事件上报的完成。

举例来说,在Android模拟器上点击鼠标并完成一次移动,可以通过getevent工具得到以下几条原始事件的输出:

[      46.543221] /dev/input/event1: EV_ABS       ABS_MT_TRACKING_ID   00000000         
[      46.543221] /dev/input/event1: EV_ABS       ABS_MT_TOUCH_MAJOR   00000001         
[      46.543221] /dev/input/event1: EV_ABS       ABS_MT_PRESSURE      00000081         
[      46.543221] /dev/input/event1: EV_ABS       ABS_MT_POSITION_X    00006110         
[      46.543221] /dev/input/event1: EV_ABS       ABS_MT_POSITION_Y    00002eb7         
[      46.543221] /dev/input/event1: EV_SYN       SYN_REPORT           00000000         
[      46.624896] /dev/input/event1: EV_ABS       ABS_MT_PRESSURE      00000000         
[      46.624896] /dev/input/event1: EV_ABS       ABS_MT_TRACKING_ID   ffffffff         
[      46.624896] /dev/input/event1: EV_SYN       SYN_REPORT           00000000

前几条的事件类型为0x03,即EV_ABS,表示它描述点击事件的一条信息。以及ABS_MT_POSITION_XABS_MT_POSITION_Y分别携带相当于屏幕中心的X和Y坐标的信息。最后的事件类型为EV_SYN,并且其CodeSYN_REPORT,即所谓的表示事件上报完成可以进行派发的特殊类型的事件

因此,Touch类型事件的加工处理其实就是收集所有描述点击动作的原始输入事件所包含的各种新信息,并在EV_SYN类型事件到来时,将这些信息进行整合,在进行一些处理之后,交付给InputDispatcher进行分发

事件收集和整理在下一篇讲