爱踢门之锤子自由截屏快捷键配置(上)
上篇文章说到了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 输入设备新增和删除事件监听
我们知道,输入设备很多是动态的,比如外接一个蓝牙鼠标或者键盘,因此系统肯定有机制来获取这些事件。而这类事件监听是在InputManagerService初始化的时候安装的。
初始化时序图如下:
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函数实现,如下图:
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函数里,如下图:
//初始化的时候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
******************************************/
更多相关文章
- android切换输入法工具类
- Ubuntu上adb找不到设备问题小结
- Android(安卓)Touch事件传递机制解析
- Android(安卓)无障碍辅助功能AccessibilityService(1)
- Android触摸事件的传递(七)-ViewGroup
- 如何检索Android设备的唯一ID
- android 触摸(Touch)事件、点击(Click)事件的区别(详细解析)
- Android(安卓)适配问题分享和总结
- Android投屏电脑反向控制软件QtScrcpy使用方法