上篇文章说到了Android系统截屏原理,本篇接着聊下Android快捷键配置。所谓快捷键就是检测按钮动作或者多个按钮动作即执行指定的事情

(比如牌照,截屏等等)。在讲快捷键配置之前,必须做些准备工作,就是了解下Android系统的按键输入系统是如何工作的。

Android 输入系统分析

我们知道Android是基于linux的操作系统,linux是各种设备(包括按键)的真正管理者,同时linux里的driver又是真正和设备打交道的,比如初始化按键,读取按键信息等等。在linux里,按键等小型设备都是以input类型的设备来管理的。我们可以用adb shell ls /dev/input,可以看到很多设备

root@hammerhead:/# ls /dev/input -al

crw-rw----root input 13,64 1970-03-11 11:57 event0

crw-rw----root input 13,65 1970-03-11 11:57 event1

crw-rw----root input 13,66 1970-03-11 11:57 event2

crw-rw----root input 13,67 1970-03-11 11:57 event3

crw-rw----root input 13,68 1970-03-11 11:57 event4

crw-rw---- root input13, 69 1970-03-11 11:57 event5

Android会向driver注册一个监听器,当有按键按下或者释放,它就能被通知到,然后android再对收到的事件进行翻译----截屏或者发送给应用程序。由此可以看出,按键事件要得到处理必须有几个过程---安装监听器,读取driver的按键事件,处理按键事件。

说到这,不得不提到InputManagerService,它是android管理input的核心,它是一个java层的module,但它也只是一个native层的InputManager的封装,native的代码是真正干活的,负责监听linux驱动事件,并将linux驱动层的事件(按键,触摸事件)翻译成android层规范的事件,再传递到java层逻辑。InputManagerService其实在早期版本比如4.0以前是一个叫InputManager的东东,早期版本InputManager只是被WindowManagerService用到,是它的一个内部变量,而现在InputManager需要向所有模块提供接口(registerInputDevicesChangedListener),所以它现在也是在作为一个service在运行,自然就改名为InputManagerService更合理。

输入系统的整个架构如下:

爱踢门之锤子自由截屏快捷键配置(上)_第1张图片

1 输入设备新增和删除事件监听

我们知道,输入设备很多是动态的,比如外接一个蓝牙鼠标或者键盘,因此系统肯定有机制来获取这些事件。而这类事件监听是在InputManagerService初始化的时候安装的。

初始化时序图如下:

爱踢门之锤子自由截屏快捷键配置(上)_第2张图片


public InputManagerService(Context context, Callbacks callbacks) {     mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());}public void start() {    nativeStart(mPtr);}static jint nativeInit(JNIEnv* env, jclass clazz,        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {    //创建    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,            messageQueue->getLooper());    return reinterpret_cast<jint>(im);}NativeInputManager::NativeInputManager(jobject contextObj,        jobject serviceObj, const sp<Looper>& looper) :        mLooper(looper) {    //EventHub是一个很重要的module,它是真正向linux driver挂钩子    //并等待事件然后转发的module.    sp<EventHub> eventHub = new EventHub();    //Input真正的核心InputManager露面了    mInputManager = new InputManager(eventHub, this, this);}InputManager::InputManager(        const sp<EventHubInterface>& eventHub,        const sp<InputReaderPolicyInterface>& readerPolicy,        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {    //Input的核心3大组件都出现了,InputDispatcher, InputReader,还有    //上面提到的EventHub    mDispatcher = new InputDispatcher(dispatcherPolicy);    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);    initialize();}void InputManager::initialize() {    //事件读取线程,有等待必然要有一个单独的线程Thread    mReaderThread = new InputReaderThread(mReader);    //事件分发线程,读取事件和处理事件独立为两个线程是必要的,因为事件处理线程是可能回调到java层的,这样完全避免了java和linux层有任何关联。    mDispatcherThread = new InputDispatcherThread(mDispatcher);} static void nativeStart(JNIEnv* env, jclass clazz, jint ptr) {    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);    //这个就是调用InputManager的start    status_t result = im->getInputManager()->start();}status_t InputManager::start() {    //start很简单,就是两个线程开始运行工作    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);    return OK;}//我们可以看看这些线程的优先级enum {    PRIORITY_LOWEST         = ANDROID_PRIORITY_LOWEST,    PRIORITY_BACKGROUND     = ANDROID_PRIORITY_BACKGROUND,    PRIORITY_NORMAL         = ANDROID_PRIORITY_NORMAL,    PRIORITY_FOREGROUND     = ANDROID_PRIORITY_FOREGROUND,    PRIORITY_DISPLAY        = ANDROID_PRIORITY_DISPLAY,    PRIORITY_URGENT_DISPLAY = ANDROID_PRIORITY_URGENT_DISPLAY,    PRIORITY_AUDIO          = ANDROID_PRIORITY_AUDIO,};

上面我们提到EventHub是真正向linuxdriver挂钩子的,我们接下来看看它是如何挂的。

EventHub::EventHub(void) :        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1),        mOpeningDevices(0), mClosingDevices(0),        mNeedToSendFinishedDeviceScan(false),        mNeedToReopenDevices(false), mNeedToScanDevices(true),        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {    //创建epoll文件,用来管理所有系统感兴趣的设备文件的变化    mEpollFd = epoll_create(EPOLL_SIZE_HINT);/* 向linux”文件系统变化通知系统” inotify注册钩子* 注册该钩子的目的是为了得到输入设备的创建,删除事件,因为输入设备* 创建,删除时必然会在/dev/input创建节点文件或删除相应节点文件* DEVICE_PATH就是要监听的目录,一般就是/dev/input* inotify是2.6版本后才有的功能,以前应用层要获知设备的插入和拔出事件,一般* 都通过udev模块。不知道大家在pc端安装adb驱动的时候,是不是有修改过* /etc/udev/rules.d/xxx文件,这个就是udev的配置文件。当然inotify也不是udev的* 替换版本,它也不是仅仅为了这个目的产生的,只不过此处用inotify机制很方便*/    mINotifyFd = inotify_init();    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);       //epoll该inotifyfd文件,这样我们通过mEpollFd就管理inotifyfd的变化(输入设备的新增,删除事件)    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);/*创建匿名管道,并epoll mWakeReadPipeFd文件,这样*其他module能够通过mWakeWritePipeFd唤醒该module并传递消息*//*基本原理如下:*该module读取完所有感兴趣的文件变化信息后,就通过*epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis)睡眠,*当其他module需要强制唤醒该module时,往mWakeWritePipeFd写消息即可*nWrite = write(mWakeWritePipeFd, "W", 1),这个会导致mWakeReadPipeFd文件产生*一个状态变化,由于epollFd监听了mWakeReadPipeFd,所以该module就被唤醒了,*然后通过read(mWakeReadPipeFd, buffer, sizeof(buffer))读取具体消息内容*//*当然其实也可以通过其他方式来实现通知,比如handler,message方式,但是*由于管道机制本身有文件句柄,可以通过epoll方式和其他设备文件一起作为对象被*poll文件一起管理监控,岂不更好*/    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);}

通过上面的代码我们可以得知,EventHub创建后,EventHub已经能够监听三类事件了:

1)/dev/input下设备新增事件

2)/dev/input下设备删除事件

3)mWakeWritePipeFd写数据事件

这时你可能会说,那按钮等具体输入事件是怎么获取的呢?我们知道,要想获取按钮等输入事件,首先必须得知道输入设备,然后再安装监听钩子,然后等事件通知到来后,直接读取事件。接下来就说说输入设备输入事件的监听。

2 输入设备的输入事件监听

输入设备的事件监听钩子的基础是上面的设备新增事件的监听。Linux系统中,当一个输入设备插入时,会在/dev/input目录下新增一个文件,然后就会导致inotifyfd发生变化,进而导致阻塞在epoll_wait(mEpollFd)上的EventHub被唤醒,进而读取”设备新增事件”详细信息即新增设备的详细信息,然后打开该设备.最后通过epoll该设备文件来达到监听该输入设备的事件(包括按钮,touch等类型的事件)。整个过程由InputReadThread线程发起,然后调用EventHub:: getEvents函数实现,如下图:

爱踢门之锤子自由截屏快捷键配置(上)_第3张图片

getEvents函数代码量比较大,我们可以将其分为2个大逻辑

1)事件收集

2)事件处理逻辑

1.设备新增,删除事件处理(这里会对输入设备安装监听钩子)

2.唤醒事件处理

3.设备的输入事件(按键,touch事件)封装

逻辑结构伪代码如下:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {for (;;) {    //事件处理逻辑    while (mPendingEventIndex < mPendingEventCount) {        //唤醒事件处理        //标注设备新增删除事件,只是将 mPendingINotify = true        //设备的输入事件封装,只是多添加几个信息,比如时间    }    //设备新增删除事件处理    //事件收集}}

2.1 设备事件收集

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {     for (;;) {         //事件收集逻辑         mPendingEventIndex = 0;        //等待事件        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);        //有新的事件,读取事件数量,并且事件的具体信息已经保存在mPendingEventItems        mPendingEventCount = size_t(pollResult);     }     return event - buffer;}

2.2输入设备事件处理

2.2.1事件分类处理逻辑

for (;;) {        ………………………..                // Grab the next input event.        bool deviceChanged = false;        while (mPendingEventIndex < mPendingEventCount) {            //读取某一事件            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];            //查看是否有设备新增删除事件            if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {                if (eventItem.events & EPOLLIN) {                    mPendingINotify = true;                }                continue;            }            //处理唤醒事件            if (eventItem.data.u32 == EPOLL_ID_WAKE) {                if (eventItem.events & EPOLLIN) {                    //读取唤醒事件的信息,这里就是mWakeReadPipeFd起作用的地方                    do {                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));                }                continue;            }            //剩下的就是设备的输入事件信息了,检查是否设备已经存在            ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);            if (deviceIndex < 0) {                continue;            }            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 {                    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 = now;                        event->deviceId = deviceId;                        event->type = iev.type;                        event->code = iev.code;                        event->value = iev.value;                        event += 1;                        capacity -= 1;                    }                }        }        //真正处理设备新增删除事件        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {            mPendingINotify = false;            readNotifyLocked();            deviceChanged = true;        }        //事件收集逻辑        …………………}


2.2.2 设备新增删除事件处理,这里会对输入设备安装监听钩子

status_t EventHub::readNotifyLocked() {    //读取设备新增删除事件的具体信息    res = read(mINotifyFd, event_buf, sizeof(event_buf));    strcpy(devname, DEVICE_PATH);    filename = devname + strlen(devname);    *filename++ = '/';    //遍历所有这类事件信息    while(res >= (int)sizeof(*event)) {        event = (struct inotify_event *)(event_buf + event_pos);        if(event->len) {            strcpy(filename, event->name);            if(event->mask & IN_CREATE) {                //创建新的输入设备                openDeviceLocked(devname);            } else {                //删除事件                closeDeviceByPathLocked(devname);            }        }        event_size = sizeof(*event) + event->len;        res -= event_size;        event_pos += event_size;    }    return 0;}

2.2.3新增并打开输入设备

status_t EventHub::openDeviceLocked(const char *devicePath) {    int fd = open(devicePath, O_RDWR | O_CLOEXEC);        // Get device name.    if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {    } else {        buffer[sizeof(buffer) - 1] = '\0';        identifier.name.setTo(buffer);    }    // Check to see if the device is on our excluded list    for (size_t i = 0; i < mExcludedDevices.size(); i++) {        const String8& item = mExcludedDevices.itemAt(i);        if (identifier.name == item) {            ALOGI("ignoring event id %s driver %s\n", devicePath, item.string());            close(fd);            return -1;        }    }    //读取设备的各种信息.    // 读取设备标示符    struct input_id inputId;    if(ioctl(fd, EVIOCGID, &inputId)) {        ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));        close(fd);        return -1;    }    identifier.bus = inputId.bustype;    identifier.product = inputId.product;    identifier.vendor = inputId.vendor;    identifier.version = inputId.version;    //设置设备的信息    setDescriptor(identifier);    // Make file descriptor non-blocking for use with poll().    if (fcntl(fd, F_SETFL, O_NONBLOCK)) {        ALOGE("Error %d making device file descriptor non-blocking.", errno);        close(fd);        return -1;    }    int32_t deviceId = mNextDeviceId++;    Device* device = new Device(fd, deviceId, String8(devicePath), identifier);    // Load the configuration file for the device.    loadConfigurationLocked(device);    // 读取设备配置信息    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);    ………    //检测该设备是否是一个键盘设备    bool haveKeyboardKeys = containsNonZeroByte(device->keyBitmask, 0,     sizeof_bit_array(BTN_MISC)) ||    containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),            sizeof_bit_array(KEY_MAX + 1));    bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask,     sizeof_bit_array(BTN_MISC), sizeof_bit_array(BTN_MOUSE))            || containsNonZeroByte(device->keyBitmask,     sizeof_bit_array(BTN_JOYSTICK), sizeof_bit_array(BTN_DIGI));    if (haveKeyboardKeys || haveGamepadButtons) {        device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;    }    //检测设备是否具备其他功能,下面只罗列了一种,其他的检测略去    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;    }       //如果是键盘设备,读取android键盘配置文件进行配置,这个过程很重要    status_t keyMapStatus = NAME_NOT_FOUND;    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK))     {        // 加载配置文件        keyMapStatus = loadKeyMapLocked(device);    }    // 检测设备是否是外接设备,这个标示很重要的    if (isExternalDeviceLocked(device)) {        device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;    }     //到这里为止,我们终于讲到了输入设备的监听,其逻辑如下    struct epoll_event eventItem;    memset(&eventItem, 0, sizeof(eventItem));    eventItem.events = EPOLLIN;    eventItem.data.u32 = deviceId;    //epoll输入设备文件句柄    //fd就是上面openDeviceLocked中设备的文件句柄    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {        delete device;        return -1;    }    addDeviceLocked(device);    return 0;}void EventHub::addDeviceLocked(Device* device) {    mDevices.add(device->id, device);    device->next = mOpeningDevices;    //将新设备添加到mOpeningDevices里,这样在执行getEvents时就会将这个设备上报    //给上层系统    mOpeningDevices = device;}

2.2.4向getEvents的调用者返回封装过的事件

最重要的传入参数是buffer,用来保存上报信息size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {    RawEvent* event = buffer;    for (;;) {        // 上报被删除的设备,就是将信息拷贝至传入参数event        while (mClosingDevices) {            Device* device = mClosingDevices;            mClosingDevices = device->next;            event->when = now;            event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;            event->type = DEVICE_REMOVED;            event += 1;            delete device;            mNeedToSendFinishedDeviceScan = true;            if (--capacity == 0) {                break;            }        }        // 上报新增的设备,就是将信息拷贝至传入参数event        while (mOpeningDevices != NULL) {            Device* device = mOpeningDevices;            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;            }        }        //还有上面已经提到过的输入设备的输入事件        int32_t readSize = read(device->fd, readBuffer,sizeof(struct input_event) * capacity);if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {//有错,则关闭该设备deviceChanged = true;closeDeviceLocked(device);} 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 = now;event->deviceId = deviceId;event->type = iev.type;event->code = iev.code;event->value = iev.value;event += 1;capacity -= 1;}}}

到此,我们可能还有个疑问,那就是,是谁在调用这么高大上的EventHub::getEvents()的,并处理转换过的事件。这个就是我前面提到的input 三大文件之一InputReader.cpp

3. 输入事件(Input event)预处理

Input事件翻译及预处理的核心是InputReader, 它和EventHub一样也要处理设备新增删除事件,输入设备的输入事件,只不过这些都是已经被EventHub处理并重新封装了。下面就看下它究竟做了什么。InputReader只是执行体,它在InputReaderThread中执行,在InputReaderThread->start之后就开始执行了,是在threadLoop函数里,如下图:

爱踢门之锤子自由截屏快捷键配置(上)_第4张图片

//初始化的时候reader被传递进来了InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :        Thread(/*canCallJava*/ true), mReader(reader) {}//线程执行bool InputReaderThread::threadLoop() {    mReader->loopOnce();    return true;}void InputReader::loopOnce() {    //调用了上面提到的大名鼎鼎的getEvents    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);    { // acquire lock        //处理核心processEventsLocked        if (count) {            processEventsLocked(mEventBuffer, count);        }        // processEventsLocked处理中如果有设备状态信息发生改变,可能就会导致        //mGeneration改变,那么就需要更新inputDevices        if (oldGeneration != mGeneration) {            inputDevicesChanged = true;            getInputDevicesLocked(inputDevices);        }    } // release lock    //通知到java上层    if (inputDevicesChanged) {        mPolicy->notifyInputDevicesChanged(inputDevices);    }    mQueuedListener->flush();}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;    }}


3.1输入设备新增事件处理

<span style="font-size:14px;">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* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);    device->configure(when, &mConfig, 0);    device->reset(when);    mDevices.add(deviceId, device);}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);     }    uint32_t keyboardSource = 0;    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {        keyboardSource |= AINPUT_SOURCE_KEYBOARD;    }    //下面的这个配置是非常重要的,即添加了一个KeyboardInputMapper用来处理键盘    //设备的输入事件,具体作用将在后面的输入事件处理章节讲述    if (keyboardSource != 0) {        device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));    }        //可以有多个mapper    if (classes & INPUT_DEVICE_CLASS_CURSOR) {        device->addMapper(new CursorInputMapper(device));    }    …………    return device;}


3.2 输入设备的输入事件预处理

void InputReader::processEventsForDeviceLocked(int32_t deviceId,        const RawEvent* rawEvents, size_t count) {    InputDevice* device = mDevices.valueAt(deviceIndex);    device->process(rawEvents, count);}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++) {                //每个设备可以有多个功能,所以也是可以有多个mapper的                //但是每个mapper自己逻辑只处理自己支持的事件,比如按键事件估计                //最后还是由刚刚提到的KeyboardInputMapper来处理                InputMapper* mapper = mMappers[i];                mapper->process(rawEvent);            }        }    }}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;            //这个map很重要,是用来将linux层的input event转化为Android的输入事            //件核心,其实这里就可以用来实现锤子系统中的BACK和MENU按键互换            if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {                keyCode = AKEYCODE_UNKNOWN;                flags = 0;            }            processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);        }        break;}}status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,        int32_t* outKeycode, uint32_t* outFlags) const {    AutoMutex _l(mLock);    Device* device = getDeviceLocked(deviceId);    if (device) {        //下面逻辑的核心是利用/system/usr/keylayout目录下对应的设备配置文件进行        //key键值的mapping        //这个mapping表其实是一个ASCII码换成另外一个ASCII码(Android对应的键盘值)        //具体这个map表从哪里读取,怎么生效的,下面的章节会详细讲解        sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();        if (kcm != NULL) {            if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {                *outFlags = 0;                return NO_ERROR;            }        }        // Check the key layout next.        if (device->keyMap.haveKeyLayout()) {            if (!device->keyMap.keyLayoutMap->mapKey(                    scanCode, usageCode, outKeycode, outFlags)) {                return NO_ERROR;            }        }    }    *outKeycode = 0;    *outFlags = 0;    return NAME_NOT_FOUND;}void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,        int32_t scanCode, uint32_t policyFlags) {    if (down) {         //按键按下        // Rotate key codes according to orientation if needed.        if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {            keyCode = rotateKeyCode(keyCode, mOrientation);        }        // 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            keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;        } else {            //保存按键            mKeyDowns.push();            KeyDown& keyDown = mKeyDowns.editTop();            keyDown.keyCode = keyCode;            keyDown.scanCode = scanCode;        }        mDownTime = when;    } else {        // 按键弹起了        ssize_t keyDownIndex = findKeyDown(scanCode);        if (keyDownIndex >= 0) {            // key up, be sure to use same keycode as before in case of rotation            keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;            mKeyDowns.removeAt(size_t(keyDownIndex));        } else {            return;        }    }        //到此为止,linux 按键值到Android的按键值翻译过程全部完成,接下来就是将按键    //分发出去,下面参数重要的是keyCode和scanCode    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);    // 下面代码就会调用InputDispatcher-> notifyKey即到了3大文件之核心InputDispatcher    getListener()->notifyKey(&args);}



3.3 输入设备的输入事件映射

输入事件映射是由配置文件决定的:

3.3.1 键盘设备配置文件加载

status_t EventHub::openDeviceLocked(const char *devicePath) {    //如果是键盘设备,读取android键盘配置文件进行配置,这个过程很重要    status_t keyMapStatus = NAME_NOT_FOUND;    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK))     {         // 加载配置文件        keyMapStatus = loadKeyMapLocked(device);    }}status_t EventHub::loadKeyMapLocked(Device* device) {    return device->keyMap.load(device->identifier, device->configuration);}status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,        const PropertyMap* deviceConfiguration) {    if (deviceConfiguration) {        //首先根据property加载配置文件        String8 keyLayoutName;        //加载keyboard.layout配置文件        if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),                keyLayoutName)) {            status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);        }        String8 keyCharacterMapName;        //首先加载keyboard.characterMap配置文件        if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),                keyCharacterMapName)) {            status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);            if (status == NAME_NOT_FOUND) {        }        if (isComplete()) {            //已经找到直接返回            return OK;        }    }    //如果根据property没有找到匹配的配置文件,传递空的名字,这样系统使用默认规则    //来搜索,默认规则是根据device的设备信息来加载配置文件,具体规则见下面的分析    if (probeKeyMap(deviceIdenfifier, String8::empty())) {        return OK;    }    //还是没加载到,则使用默认的配置文件generic    if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {        return OK;    }    //实在不行使用虚拟的    if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {        return OK;    }    return NAME_NOT_FOUND;}bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,        const String8& keyMapName) {    //加载两种配置文件    if (!haveKeyLayout()) {        loadKeyLayout(deviceIdentifier, keyMapName);    }    if (!haveKeyCharacterMap()) {        loadKeyCharacterMap(deviceIdentifier, keyMapName);    }    return isComplete();}status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,        const String8& name) {    String8 path(getPath(deviceIdentifier, name,            INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));    if (path.isEmpty()) {        return NAME_NOT_FOUND;    }    status_t status = KeyLayoutMap::load(path, &keyLayoutMap);    if (status) {        return status;    }    keyLayoutFile.setTo(path);    return OK;}String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,        const String8& name, InputDeviceConfigurationFileType type) {    return name.isEmpty()            ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)            : getInputDeviceConfigurationFilePathByName(name, type);}//我们以name为空这种case来分析配置文件搜索规则String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(        const InputDeviceIdentifier& deviceIdentifier,        InputDeviceConfigurationFileType type) {        if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {//有设备详细信息,则格式为vender_xx _product_xx.kl        String8 productPath(getInputDeviceConfigurationFilePathByName(                String8::format("Vendor_%04x_Product_%04x",                        deviceIdentifier.vendor, deviceIdentifier.product),                type));        if (!productPath.isEmpty()) {            return productPath;        }    }    // 没有,则直接使用device name来查找    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);}String8 getInputDeviceConfigurationFilePathByName(        const String8& name, InputDeviceConfigurationFileType type) {    // Search system repository.    String8 path;    path.setTo(getenv("ANDROID_ROOT"));    path.append("/usr/");    //配置文件的根目录是/system/usr,然后append类型keychars, idc, keylayout    appendInputDeviceConfigurationFileRelativePath(path, name, type);    if (!access(path.string(), R_OK)) {        return path;    }    // Search user repository.    // TODO Should only look here if not in safe mode.    path.setTo(getenv("ANDROID_DATA"));    path.append("/system/devices/");    appendInputDeviceConfigurationFileRelativePath(path, name, type);    if (!access(path.string(), R_OK)) {        return path;    }    return String8();}//找到配置文件后,肯定就是读取并翻译成keymap的方式保存status_t KeyCharacterMap::load(const String8& filename,        Format format, sp<KeyCharacterMap>* outMap) {    Tokenizer* tokenizer;    status_t status = Tokenizer::open(filename, &tokenizer);    if (status) {    } else {        status = load(tokenizer, format, outMap);        delete tokenizer;    }    return status;}status_t KeyCharacterMap::load(Tokenizer* tokenizer,        Format format, sp<KeyCharacterMap>* outMap) {    status_t status = OK;    sp<KeyCharacterMap> map = new KeyCharacterMap();    if (!map.get()) {    } else {        Parser parser(map.get(), tokenizer, format);        status = parser.parse();        if (!status) {            *outMap = map;        }    }    return status;}


我最开始的时候一直很好奇,为啥有两个配置文件,两个配置文件各是什么作用的呢?


3.3.2输入设备配置文件

输入设备有三种常用的类型.idc, .kl, .kcm.

1) .idc:全程InputDevice Configuration,所有input device都可以有。

比如:adb shell cat /system/usr/idc/qwerty.idc

//设备类型touch.deviceType = touchScreentouch.orientationAware = 1//内部设备device.internal = 1//键盘布局配置,这个就是上面提到的键盘配置文件名字的一种keyboard.layout = qwerty//键盘字符映射,这个就是上面提到的键盘配置文件名字的另外一种keyboard.characterMap = qwertykeyboard.orientationAware = 1keyboard.builtIn = 1cursor.mode = navigationcursor.orientationAware = 1

2 ) .kl,.kcm两种是针对keyboard这种输入设备的。

.kl是一种字符对Android按键值别名的键盘映射配置方式

比如默认的kl文件/system/usr/Generic.kl:

key 114   VOLUME_DOWNkey 115   VOLUME_UPkey 116   POWER             WAKEkey 127   MENU              WAKE_DROPPEDkey 158   BACK               WAKE_DROPPEDkey 171   MUSICkey 172   HOME

第二列为linux驱动传递上来的按键值即上面的scanCode,叫它raw data可能更形象,第三列对应的是Android系统的按键的别名,相当于PC键盘上的一些键的别名比如F4,F5,只不过Android又定义了另外一套键盘名字系统,因为Android系统有很多原来PC没有的键,比如HOME,CALL, ENDCALL,POWER

第四列是按键的属性:比如上面的WAKE属性,是指当对应的按键按下时,系统会被唤醒

别名和属性对应的数值都在KeycodeLabel.h文件中定义

static const KeycodeLabel KEYCODES[] = {    { "HOME", 3 },    { "BACK", 4 },    { "CALL", 5 },{ "ENDCALL", 6 },…………….{ "POWER", 26 },………    { NULL, 0 }};static const KeycodeLabel FLAGS[] = {    { "WAKE", 0x00000001 },    { "WAKE_DROPPED", 0x00000002 },    { "VIRTUAL", 0x00000100 },    { NULL, 0 }};

而.kcm是一种组合字符对应一个字符的配置方式.比如一般的键盘是只有小写键,这样linux驱动发送上来的字符也只是小写键的ASCII值,那如何实现大写输入呢?我们在PC上的键盘是通过SHIFT+小写实现的,.kcm就是用来解决类似组合键映射问题的。

key A {    label:                              'A'    base:                               'a'    shift, capslock:                    'A'}key R {    label:                              'R'    number:                             '7'    base:                               'r'    shift, capslock:                    'R'    alt:                                '3'    shift+alt, capslock+alt:            '\u20ac'}

来自底层的输入事件经过加载的配置文件的影响后转化为Android层的标准事件,然后就开始Android层的事件分发了。这个将在中篇接着分析。


/********************************

* 本文来自博客 “爱踢门”

* 转载请标明出处:http://blog.csdn.net/itleaks

******************************************/


更多相关文章

  1. Ubuntu上adb找不到设备问题小结
  2. Android Touch事件传递机制解析
  3. Android EventBus使用,粘性事件postSticky使用
  4. Android点击监听事件
  5. Android DownloadManager下载完成事件监听(系列4)
  6. Android 上层应用读写底层设备节点(Android M)

随机推荐

  1. Android手机信号提取
  2. Android零碎知识总结
  3. android:screenOrientation属性详解
  4. Android需要提升权限的操作方法
  5. android下前端开发诡异bug记录&解决方法
  6. Android中ExpandableListView的用法
  7. Spinner(下拉列表)
  8. Android(安卓)OpenGL ES 分析与实践(4)
  9. NDK学习笔记(十二) 原生图形api,使用AVILi
  10. android wifiservice enable流程