Android Framework 输入子系统(04)InputReader解读

系列文章解读&说明:

Android Framework 输入子系统 的 分析主要分为以下部分:

(01)核心机制 inotify和epoll

(02)核心机制 双向通信(socketpair+binder)

(03)输入系统框架

(04)InputReader解读

(05)InputDispatcher解读

(06)APP建立联系

(07)activity window decor view

(08)InputStage简介

(09)Input系统常见工具(getevent & sendevent & input)

本模块分享的内容:InputReader解读

本章关键点总结 & 说明:

本章节只需要关注➕读取 & 流程,同时对于代码分析,横向表示纵向思维,一层层递进调用,纵向表示步骤,是并列的执行顺序。下面这张图是本模块放大的部分,如下所示:

这样就会清晰好多。

1  宏观分析InputReader

这里从InputManager的start函数,延续上一章节的分析,代码如下:

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    //...
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    //...
    return OK;
}

InputManager启动了一个InputReaderThread和InputDispatcherThread来读取和分发输入消息,调用它们的run方法后,会进入threadLoop函数(只要threadLoop函数返回true,该函数就会循环执行)。因此接下来直接分析InputReaderThread的threadLoop函数,实现如下:

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

这里继续分析loopOnce,实现如下:

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    //...
    //关键点1 通过EventHub获取事件列表
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();
        if (count) {
            //关键点2 对事件进行 加工处理
            processEventsLocked(mEventBuffer, count);
        }
        //...
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }
    //关键点3 发布事件到InputDispatcher中
    mQueuedListener->flush();
}

因此对于读取数据的环节,我们需要注重的就是3个关键点:

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

接下来从这3个关键点出发,详细分析Inputreader的流程。

2 读取原始输入事件

2.1 EventHub的构造器

这里首先从EventHub的构造器代码开始分析,看初始化了些什么?代码如下:

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
	//这里直接参考IMS分析的第一章节:inofify和epoll机制即可。
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    //创建了名为wakeFds的匿名管道,并将管道读取端的描述符的可读事件注册到epoll对象中。
    int wakeFds[2];
    result = pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    //...
}

这里说明下:为什么使用管道?

因为InputReader在执行getEvents()时会因无事件而导致其线程阻塞在epoll_wait()的调用里,然而有时希望能够立刻唤醒InputReader线程使其处理一些请求。此时只需向wakeFds管道的写入端写入任意数据,此时读取端有数据可读,使得epoll_wait()得以返回,从而达到唤醒InputReader线程的目的。

2.2 EventHub->getEvents获取事件

接下来看EventHub->getEvents(),它的代码实现如下:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
	//...
    struct input_event readBuffer[bufferSize];
    RawEvent* event = buffer;//这是元事件指针,可以指向一系列的事件,这些事件按照数组的方式存放的
    size_t capacity = bufferSize;
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //mNeedToReopenDevices = false; mClosingDevices = 0; 
		//mNeedToSendFinishedDeviceScan = false; mOpeningDevices = 0
        //mNeedToScanDevices = true
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
     //...
        // Grab the next input event.
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) {
            //...这里省略对于其他的epoll类型的处理。
            //如果是EPOLLIN类型的事件,意味着epoll监视的文件描述符中有写入事件,这类事件是输入事件
            Device* device = mDevices.valueAt(deviceIndex);
            if (eventItem.events & EPOLLIN) {
                int32_t readSize = read(device->fd, readBuffer,
                        sizeof(struct input_event) * capacity);
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                    deviceChanged = true;
                    closeDeviceLocked(device);
                } else if //...
                } 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->deviceId = deviceId;
                        //...其他属性赋值
                        event += 1;
                        capacity -= 1;
                    }
                    if (capacity == 0) {
                        mPendingEventIndex -= 1;
                        break;
                    }
                }
            } else 
            //...
        }
        //...
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        //pollResult处理
    }
    return event - buffer;
}

总结一下,EventHub负责打开/dev/input/目录下的所有设备,然后为每一个设备创建一个Device,并把这个Device放入EventHub所定义的数组们Device中。之后把这个设备纳入监视范围。接下来等待事件发生,一旦有事件发生,就从产生input_event事件的设备中读取出这些设备,把这些事件转化为RawEvent类型放入InputReader提供的事件数组中,之后返回。同时这里简单说明下RawEvent结构体,它的定义如下:

struct RawEvent {
    nsecs_twhen;    /* 发生事件时的时间戳 */
    int32_tdeviceId;/* 产生事件的设备Id,由EventHub自行分配,InputReader根据它从EventHub中获取此设备的详细信息 */
    int32_ttype;    /* 事件的类型 */
    int32_tcode;    /* 事件代码 */
    int32_tvalue;   /* 事件值 */
};

特殊说明:RawEvent也用来表示设备增删事件,为此EventHub定义了三个特殊的事件类型DEVICE_ADD、DEVICE_REMOVED以及FINISHED_DEVICE_SCAN,用以与原始输入事件进行区别。同时这里列出 Android支持的设备类型:

getEvent函数是通过inotify和epoll机制对/dev/input/下的事件 进行获取并封装成一个个RawEvent事件。

接下来重点分析扫描设备 scanDevicesLocked 和epoll_wait两个关键点。

2.2.1 扫描设备

关键方法scanDevicesLocked实现如下:

void EventHub::scanDevicesLocked() {
    status_t res = scanDirLocked(DEVICE_PATH);
    if(res < 0) {
        ALOGE("scan dir failed for %s\n", DEVICE_PATH);
    }
    if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
        createVirtualKeyboardLocked();
    }
}

继续分析scanDirLocked,代码实现如下:

status_t EventHub::scanDirLocked(const char *dirname)
{
    char devname[PATH_MAX];
    char *filename;
    DIR *dir;
    struct dirent *de;
    dir = opendir(dirname);
    if(dir == NULL)
        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;
}

函数调用readdir函数扫描/dev/input下的文件,然后调用openDeviceLocked打开这些设备文件。代码如下:

status_t EventHub::openDeviceLocked(const char *devicePath) {
     ...
     InputDeviceIdentifier identifier;
 
     // 获取设备的名字,如果成功获取到设备的名字,把它存入InputDeviceIdentifier中
     if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
         //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
     } else {
         buffer[sizeof(buffer) - 1] = '\0';
         identifier.name.setTo(buffer);
     }
     ...
 
     //构造EventHub所需要的对象Device,这里的fd是刚刚打开的设备的文件描述符
     int32_t deviceId = mNextDeviceId++;//从这里可以看出,deviceId是与设备无关的,和打开顺序有关
     Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
 
     // 测试设备能够产生的事件的类型,这里就是Android支持的事件类型,是Kernel的一个子集
     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
     if (test_bit(BTN_MOUSE, device->keyBitmask)
             && test_bit(REL_X, device->relBitmask)
             && test_bit(REL_Y, device->relBitmask)) {
         device->classes |= INPUT_DEVICE_CLASS_CURSOR;
     }
     ...
    // 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.
        //加载kl和kcm文件
        keyMapStatus = loadKeyMapLocked(device);
    }
    ...
    //将前面打开的设备的文件描述符加入到epoll监听中
    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
        ALOGE("Could not add device fd to epoll instance.  errno=%d", errno);
        delete device;
        return -1;
    }

该函数打开扫描到的/dev/input下的设备文件,然后获取该设备文件的name,bus,product,vendor,version,location, uniqueId, descriptor以及设备类型,如是否是键盘、虚拟按键等。如果是INPUT_DEVICE_CLASS_KEYBOARD 这个类型,则调用loadKeyMapLocked来加载kl文件。

这里继续分析loadKeyMapLocked,代码如下:

status_t EventHub::loadKeyMapLocked(Device* device) {
    return device->keyMap.load(device->identifier, device->configuration);
}

继续分析load实现,代码如下:

status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
        const PropertyMap* deviceConfiguration) {
    // Use the configured key layout if available.
    if (deviceConfiguration) {
        String8 keyLayoutName;
        if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
                keyLayoutName)) {
            status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
            //...
        }

        String8 keyCharacterMapName;
        if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
                keyCharacterMapName)) {
            status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
            //...
        }
		//...
    }
	//...
    // Try searching by device identifier.
    if (probeKeyMap(deviceIdenfifier, String8::empty())) { return OK;}
    if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {return OK;}
    // Try the Virtual key map as a last resort.
    if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {return OK;}
    return NAME_NOT_FOUND;
}

这里说明下查找逻辑(以上代码 递进分析):

  1. configuration不为空,即存在idc文件,则根据idc文件中指定的keyboard.layout和keyboard.characterMap来加载指定的kl和kcm文件 
  2. configuration为空,则根据deviceIdenfifier来加载kl和kcm文件(这个会根据vendor,product,version三个属性来决定加载哪个文件) 
  3. deviceIdenfifier无法找到配置文件,则根据名字Generic来加载文件,即Generic.kl和Generic.kcm文件 
  4. Generic.kl和Generic.kcm无法找到,则根据名字Virtual来加载文件,即Virtual.kl和Virtual.kcm文件

这里对于按键类设备,驱动层有一组 对按键的定义,而android在IMS中又有一组对按键值的定义,这是为什么呢?

这是因为Android 系统是建立在Linux内核基础上的,但如果Linux内核中 输入事件的定义值发生了变化,android系统也要跟着改,而这样就很被动,因此android系统采用了配置文件*.kl 和*.kcm,这样只要配置就可以了。而接下来谈谈kl和kcm文件的差异:

kl文件仅仅是值的映射,比如:

# This is an example of a key layout file for basic system controls,
# such as volume and power keys which are typically implemented as GPIO pins
# the device decodes into key presses.

key 114   VOLUME_DOWN
key 115   VOLUME_UP
key 116   POWER

像前面114、115这些数字都是在kernel*/include/uapi/linux/input.h中定义的,kernel报上来的键值就是114或115,而kl文件是对这个键值的映射,在android系统中代表的是VOLUMEDOWN、VOLUMEUP。

kcm文件则有规则限制,比如:

key A {
    label:                              'A'
    base:                               'a'
    shift, capslock:                    'A'
    ctrl, alt, meta:                    none
}

在上面的例子中,label 属性被分配了 'A' 行为(驱动中对应行为)。同样ctrlalt 和 meta 属性同时被分配了 none 行为。

按键声明包括关键字 key,后跟 Android 按键代码名称、左大括号、一组属性和行为以及一个右大括号。每个按键属性都会建立从按键到行为的映射。为了使按键字符映射文件更加紧凑,可以将多个属性(用逗号分隔)映射到同一个行为。

2.2.2 epoll_wait等待事件简要说明

从epoll_wait()中得到新的事件后会重新循环,对新事件进行处理,返回本次getEvents()调用所读取的事件数量,之后会调用processEventsLocked来处理RawEvent。

2.3 总结

获取输入事件的目的:从驱动中将input事件独取出来转换成 RawEvent事件,同时对于输入设备,抽象成一个EventHub中的Device结构体并存储。

3 处理事件

processEventsLocked的代码实现如下:

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;
            }
            //关键点1:有输入设备产生的事件,在这个方法中处理
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            switch (rawEvent->type) {//设备添加类的事件在这里处理
            case EventHubInterface::DEVICE_ADDED:
                //这个方法中创建了InputReader所必须的一些数据结构
                //添加设备
                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;
    }
}

3.1 processEventsForDeviceLocked处理设备的输入事件,代码如下:

void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    //...
    InputDevice* device = mDevices.valueAt(deviceIndex);
    //...
    device->process(rawEvents, count);
}

这里根据deviceId获取到InputDevice,然后调用InputDevice的process函数

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    size_t numMappers = mMappers.size();
    for (const RawEvent* rawEvent = rawEvents; count--; 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().string());
            mDropUntilNextSync = true;
            reset(rawEvent->when);
        } else {
            for (size_t i = 0; i < numMappers; i++) {
                InputMapper* mapper = mMappers[i];
                mapper->process(rawEvent);
            }
        }
    }
}

这里的mMappers成员变量保存了一系列输入设备事件处理对象,是在InputReader类的成员函数createDevice中创建的。这里查询每一个InputMapper对象是否要对当前发生的事件进行处理。这里后面以按键处理为例,发生键盘事件,真正会对该事件进行处理的只有KeyboardKeyMapper对象。接下来对KeyboardKeyMapper的process进行分析:

void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY: {
        int32_t scanCode = rawEvent->code;
        int32_t usageCode = mCurrentHidUsage;
        mCurrentHidUsage = 0;

        if (isKeyboardOrGamepadKey(scanCode)) {
            int32_t keyCode;
            uint32_t flags;
            if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
                keyCode = AKEYCODE_UNKNOWN;
                flags = 0;
            }
            processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
        }
        break;
    }
	//...
    }
}

继续分析processKey,代码实现如下:

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
    if (down) {
        // 根据屏幕方向的不同调整键盘码
        // Add key down.
        ssize_t keyDownIndex = findKeyDown(scanCode);
        if (keyDownIndex >= 0) {
            // key repeat, be sure to use same keycode as before in case of rotation
        } else {
            // key down
        }
        mDownTime = when;
    } else {
        // Remove key down.
        if (keyDownIndex >= 0) {
            // key up, be sure to use same keycode as before in case of rotation
        } else {
            return;
        }
    }
	//...
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
	//说明:这里getListener是InputReader初始化时传入的对象,即InputDispatcher
    getListener()->notifyKey(&args);
}

通过对getListener()的分析了解到,这里返回的是mQueuedListener对象 。(在构造器中定义 mQueuedListener = new QueuedInputListener(listener); 这句话中定义,同时这里传递的listerner便是InputDispatcher。notifyKey代码实现如下所示:

void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
    mArgsQueue.push(new NotifyKeyArgs(*args));
}

3.2 添加/删除设备

3.2.1 addDeviceLocked添加设备


void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    //...
    //获取厂商信息和设备类型
    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
    int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);

    //创建InputDevice
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    //使用inputReader中保存的配置信息对新Inputdevice进行策略配置,执行reset进行设备重制
    device->configure(when, &mConfig, 0);
    device->reset(when);
    //...
    //添加到列表mDevices中
    mDevices.add(deviceId, device);//
    bumpGenerationLocked();
}

这里继续分析createDeviceLocked,代码实现如下:

InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);
    // External devices.
    if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
        device->setExternal(true);
    }
	//...
    // Keyboard-like devices.
    uint32_t keyboardSource = 0;
    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
    }
    //...
    // Joystick-like devices.
    if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
        device->addMapper(new JoystickInputMapper(device));
    }
    return device;
}

这里主要创建InputDevice设备,并根据class给device添加了各种能够支持的Mapper。最后添加到mDevices的Vector容器中。

3.2.2 removeDeviceLocked删除设备

void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
    InputDevice* device = NULL;
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    //...
    device = mDevices.valueAt(deviceIndex);
    mDevices.removeItemsAt(deviceIndex, 1);
    bumpGenerationLocked();
    //...
    device->reset(when);
    delete device;
}

根据传递进来的deviceId找到对应的deviceIndex,然后从mDevices的Vector容器中删除该device。(说明:这里的mDevices是InputDevice类型的,和之前在EventHub中的Device结构体并不相同,EventHub中的Device结构体主要是记录。这里的InputDevice主要是封装了处理事件各种Mapper,便于对输入事件的处理)。

4 将事件交付给InputDispatcher

mQueuedListener.flush的代码实现如下:

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

之前处理事件中,执行了mArgsQueue.push操作将NotifyKeyArgs放进Vector中。在flush时直接取出来执行NotifyKeyArgs的notify方法,这里代码如下:

void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyKey(this);
}

这里调用传递进来的listener执行notifyKey,实际上就是调用了InputDispatcher的notifyKey函数。

代码实现如下:

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    //validateKeyEvent来判断是否是有效按键事件,按键则为Up/Down
    //构造KeyEvent对象
    KeyEvent event;
    event.initialize(args->deviceId, args->source, args->action,
            flags, keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);
    //最后会调用到java层的PhoneWindowManagerService函数
    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
    bool needWake;
    { // acquire lock
        mLock.lock();
        if (shouldSendKeyToInputFilterLocked(args)) {
            mLock.unlock();
            policyFlags |= POLICY_FLAG_FILTERED;
            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
                return; // event was consumed by the filter
            }
            mLock.lock();
        }
        int32_t repeatCount = 0;
        //构造KeyEntry对象
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);
		将输入事件加入队列,如果返回true,则调用后面mLooper.wake函数
        needWake = enqueueInboundEventLocked(newEntry);
        mLock.unlock();
    } // release lock
    if (needWake) {
        //唤醒等待的InputDispatcher,进行输入事件分发。
        mLooper->wake();
    }
}

这里有2个关键点:一个是interceptKeyBeforeQueueing,这里最终会调用到java层的PhoneWindowManager,而另一个是enqueueInboundEventLocked,将时间队列加入到mInboundQueue中。

4.1 分析 interceptKeyBeforeQueueing

这里看实现接口,最终找到NativeInputManager::interceptKeyBeforeQueueing,代码实现如下:

void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
        uint32_t& policyFlags) {
    if (mInteractive) {
        policyFlags |= POLICY_FLAG_INTERACTIVE;
    }
    if ((policyFlags & POLICY_FLAG_TRUSTED)) {
        nsecs_t when = keyEvent->getEventTime();
        JNIEnv* env = jniEnv();
        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
        jint wmActions;
        if (keyEventObj) {
            wmActions = env->CallIntMethod(mServiceObj,
                    gServiceClassInfo.interceptKeyBeforeQueueing,
                    keyEventObj, policyFlags);
            if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
                wmActions = 0;
            }
            android_view_KeyEvent_recycle(env, keyEventObj);
            env->DeleteLocalRef(keyEventObj);
        } else {
            ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
            wmActions = 0;
        }

        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
    } else {
        if (mInteractive) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;
        }
    }
}

这里会通过JNI机制(对应com_android_server_input_InputManagerService)文件中回调 java层的PhoneWindowManager里的同名方法interceptKeyBeforeQueueing,实现如下:

public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    //...
    int result;
    boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
            || event.isWakeKey();
	//...
    // Handle special keys.
    switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_DOWN:
        case KeyEvent.KEYCODE_VOLUME_UP:
        case KeyEvent.KEYCODE_VOLUME_MUTE: {
            //...
            if (down) {
                TelecomManager telecomManager = getTelecommService();
                if (telecomManager != null) {
                    if (telecomManager.isRinging()) {
                        telecomManager.silenceRinger();
                        result &= ~ACTION_PASS_TO_USER;
                        break;
                    }
                    //...
                }
            }
            break;
        }
		//...
    }
	//...
    return result;
}

这里interceptKeyBeforeQueueing中以打电话 按power键为例,这里直接截断并将result标志位置为非,即不发给用户层。实际上这里把三类按键(system Key | Global Key | User Key)中前两项都进行了相应的处理。

4.2 分析enqueueInboundEventLocked

enqueueInboundEventLocked的代码实现如下:

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.isEmpty();
    mInboundQueue.enqueueAtTail(entry);

    switch (entry->type) {
    case EventEntry::TYPE_KEY: {
        KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
        if (isAppSwitchKeyEventLocked(keyEntry)) {
            if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
                mAppSwitchSawKeyDown = true;
            } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
                if (mAppSwitchSawKeyDown) {
                    mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
                    mAppSwitchSawKeyDown = false;
                    needWake = true;
                }
            }
        }
        break;
    }
	//...这里忽略Motion,因为情况类似
    }
    return needWake;
}

这里依然以按键事件为例,将EventEntry加入到mInboundQueue中,该函数两种情况下会返回true:

  1. 加入该键盘事件到mInboundQueue队列之前,mInboundQueue为空,这说明InputDispatcherThread线程在睡眠,等待InputReaderThread线程的唤醒,因此,它返回true表示要唤醒InputDispatcherThread线程;
  2. 加入该键盘事件到mInboundQueue队列之前,mInboundQueue不为空,但此时用户按下Home键等需要切换APP的按键,在切换App时,新的App会把它的键盘消息接收通道注册到InputDispatcher中去,并且会等待InputReader的唤醒,因此,在这种情况下,也需要返回true,表示要唤醒InputDispatcherThread线程。

如果不是这两种情况,那么就说明InputDispatcherThread线程现在正在处理前面的键盘事件,不需要被唤醒。

猜你喜欢

转载自blog.csdn.net/vviccc/article/details/91414573