上篇文章说到了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 input 13, 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 = touchScreen
touch.orientationAware = 1
//内部设备
device.internal = 1
//键盘布局配置,这个就是上面提到的键盘配置文件名字的一种
keyboard.layout = qwerty
//键盘字符映射,这个就是上面提到的键盘配置文件名字的另外一种
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1
cursor.mode = navigation
cursor.orientationAware = 1
2 ) .kl,.kcm两种是针对keyboard这种输入设备的。
.kl是一种字符对Android按键值别名的键盘映射配置方式
比如默认的kl文件/system/usr/Generic.kl:
key 114 VOLUME_DOWN
key 115 VOLUME_UP
key 116 POWER WAKE
key 127 MENU WAKE_DROPPED
key 158 BACK WAKE_DROPPED
key 171 MUSIC
key 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
******************************************/