我一直觉得要想学习Android,我们有必要研究一下Android的输入子系统,Android手机最主要的输入是触摸屏和各种传感器。因为我的开发板是盒子,加上自己没有触摸屏的条件,所以我都是以USB键盘来调试研究Android的输入系统的。但是不管是键盘还是触摸又或者是其他的输入,其实对于Android系统来说都是一样的,区别只在于各个输入设备的驱动部分。
Android对输入事件的处理流程涉及的内容比较多,所以笔者这里分两篇文章讲解,第一篇主要侧重讲管理输入的服务是怎么从驱动哪里读取到输入事件然后怎么处理把输入事件又发送到上层的;第二篇要讲上层收到输入事件后是怎么处理的,这还涉及到输入法的代码。
Android有一个专门的服务来管理输入,这个服务就是InputManagerService,它是一个Java编写的Framework层的服务。要想了解Android的输入系统,我们就从管理各种输入的服务InputManagerService开始。
/* 代码部分笔者只粘贴笔者认为重要的需要讲解的,并不是粘贴出来的就是函数的全部代码。 */
我们来看InputManagerService的构造函数:
public InputManagerService(Context context, Handler handler) {
//先创建一个InputManagerHandler对象来进行消息处理。
this.mHandler = new InputManagerHandler(handler.getLooper());
//调用InputManagerHandler对象的getLooper()得到主线程的Looper对象,在调用getQueue()方法得到线程的消息队列MessageQueue对象。然后再以消息队列MessageQueue对象作为参数调用nativeInit()方法来完成初始化。
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
}
下面我们来分析一下nativeInit()方法的实现:
nativeInit()是一个JNI接口,它的本地实现在com_android_server_input_InputManagerService.cpp文件里。
static jint nativeInit(JNIEnv* env, jclass clazz,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
//调用android_os_MessageQueue_getMessageQueue()方法将保存在Java层的MessageQueue对象的mPtr成员变量中的值返回。
/* 我们在Java层创建MessageQueue对象时,在Native层也创建了与之对应的NativeMessageQueue对象,而NativeMessageQueue对象的指针就保存在Java层的MessageQueue对象的mPtr成员变量中。显然函数返回的就是与Java层MessageQueue对象相对应的NativeMessageQueue对象的指针,将NativeMessageQueue对象的指针保存在变量messageQueue中(NativeMessageQueue的父类是MessageQueue)。 */
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
//调用messageQueue的getLooper()函数得到它关联的Looper对象的指针,最后用这个Looper对象作为参数创建了一个NativeInputManager对象。
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
}
我们来看NativeInputManager的构造函数:
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper) {
//创建EventHub对象eventHub。
sp<EventHub> eventHub = new EventHub();
//以对象eventHub为参数创建InputManager对象。
mInputManager = new InputManager(eventHub, this, this);
}
我们来看看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) {
//创建epoll句柄。
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
//创建了一个inotify句柄。
/*
* static const char *DEVICE_PATH = "/dev/input";
* inotify是Linux中监测目录和文件变化的一种机制,这里监测的目录是/dev/input。
* 说明创建的这个inotify句柄是用来检测系统中是否有输入设备加入或者移除了。
*/
mINotifyFd = inotify_init();
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
//创建一个匿名管道。
int wakeFds[2];
result = pipe(wakeFds);
//将inotify句柄和管道的“读端”句柄都加入到epoll的监测中。
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
我们接着分析InputManager的构造函数:
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
//创建InputDispatcher对象。
mDispatcher = new InputDispatcher(dispatcherPolicy);
//创建InputReader对象。这里把NativeInputManager对象的构造函数里创建的EventHub对象eventHub当参数创建InputReader对象,说明EventHub对象虽然是在NativeInputManager对象的构造函数中创建的,但是,引用它的却是InputReader的成员变量mEventHub。
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
// initialize()函数里创建了两个线程对象,但是没有启动这两个线程。
initialize();
}
代码来到这里已经基本清楚了,InputManagerService创建的作用其实就是创建两个线程,一个线程通过mReader的mEventHub来读取输入的event事件。另外一个线程通过mDispatcher将event事件发送给上层。
InputManagerService对象是谁在哪创建的?通过搜索发现,SystemService的initAndLoop()方法创建了InputManagerService对象,同时还调用了InputManagerService对象的start()方法。我们来看看InputManagerService对象的start()方法:
public void start() {
//以mPtr为参数调用nativeStart()方法。mPtr是什么鬼上面已经说的很清楚了,这里就不重复了。
nativeStart(mPtr);
}
nativeStart()方法是JNI接口,在com_android_server_input_InputManagerService.cpp里有该函数的本地实现。
static void nativeStart(JNIEnv* env, jclass clazz, jint ptr) {
//调用了InputManager对象的start()函数。
status_t result = im->getInputManager()->start();
}
InputManager对象的start()方法的实现:
status_t InputManager::start() {
//启动了initialize()方法中创建的两个线程对象关联的线程。
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
}
我们来看看上面提到的两个线程,按照我们的逻辑来的话,我们得先从驱动哪里读到输入event事件对其进行解析后再把解析后的event事件发送给上层处理,所以我们先看看InputReaderThread线程的运行函数threadLoop()。
bool InputReaderThread::threadLoop() {
//调用了mReader的loopOnce()函数。
mReader->loopOnce();
return true;
}
mReader是InputReader类,它的loopOnce()函数是:
void InputReader::loopOnce() {
//调用EventHub的getEvent()函数来读取Event。读到的Event保存在mEventBuffer中。为了方便理解代码,我们直接跳到EventHub的getEvents()函数中去看看。
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
if (count) {
//得到的Event的数量大于0,将调用processEventsLocked()函数来处理这些Event。我们到processEventsLocked()函数里看该函数是怎么处理RawEvent的。
processEventsLocked(mEventBuffer, count);
}
}
EventHub的getEvents()函数的代码实现:
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
//如果是第一次调用该函数,将调用scanDevicesLocked()函数。
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
//这里我就不具体解析scanDevicesLocked()方法了,该方法会调用scanDirLocked()来扫描/dev/input目录。在scanDirLocked()函数里首先打开/dev/input目录,然后对目录下的每个文件都调用openDeviceLocked()函数,openDeviceLocked()函数调用open()函数打开设备,调用ioctl()来取得设备的信息,根据这些信息为设备创建一个Device对象,接着将设备对象的句柄加入到epoll的监控中,最后把Device对象添加到EventHub的mDevices的列表中。
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
//读取设备的数据。
int32_t readSize = read(device->fd, readBuffer, sizeof(struct input_event) * capacity);
//阻塞等待。
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
//event的type小于FIRST_SYNTHETIC_EVENT,说明该事件是输入设备产生的事件,会调用processEventsForDeviceLocked()函数处理。该函数就是我们将要重点讲解的,因为函数处理的RawEvent就是输入设备输入的事件,我们下面将以通过USB键盘输入按键为例来分析。
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
//发生添加设备的变化,调用addDeviceLocked()方法增加mDevices列表中的Device对象。
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED:
//发送移除设备的变化,调用removeDeviceLocked()方法删除mDevices列表中的Device对象。
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
//扫描设备结束,调用handleConfigurationChangedLocked()函数来处理。
handleConfigurationChangedLocked(rawEvent->when);
break;
}
}
}
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
const RawEvent* rawEvents, size_t count) {
//根据deviceId得到InputDevice对象。
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
InputDevice* device = mDevices.valueAt(deviceIndex);
//调用与deviceId相对应的InputDevice的process()函数。
device->process(rawEvents, count);
}
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
//该函数的代码我就不贴出来了,逻辑比较简单,就是对每个RawEvent事件都调用它的所有InputMapper对象的process()函数来处理。InputMapper对象的列表是怎么来的呢?我们回到当添加一个输入设备时,会调用addDeviceLocked()方法增加mDevices列表中的Device对象,在addDeviceLocked()方法里会根据deviceId调用createDeviceLocked()方法来创建对应的InputDevice对象,createDeviceLocked()方法中会调用创建出来的InputDevice对象的addMapper ()方法,把各种InputMapper对象都add到mMappers列表中。显然InputDevice对象的成员变量mMappers列表中有各种InputMapper对象,这些InputMapper对象的作用就是对它们各自的RawEvent进行处理,翻译成上层能识别的消息格式。
}
以键盘的按键输入为例的话,我们到KeyboardInputMapper的process函数里去看看:
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
//调用processKey()方法来处理。
processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
}
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
int32_t scanCode, uint32_t policyFlags) {
//前面做了一大堆工作,大概就是解析后得到按键值。
// getListener()函数返回的是InputReader 对象mReader的成员变量mQueuedListener的指针。QueuedInputListener 对象mQueuedListener从哪来的?我们回到InputReader的构造函数,显然QueuedInputListener对象创建时传入的参数listener是创建InputReader对象的参数listener。我们到QueuedInputListener的notifyKey()函数去看看。
getListener()->notifyKey(&args);
}
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
//只是把参数args放到了mArgsQueue中。那么处理args的地方在哪?搜索一下代码,不难找到,在InputReader的线程运行函数loopOnce()函数的结尾调用了mQueuedListener的flush()函数。
mArgsQueue.push(new NotifyKeyArgs(*args));
}
void QueuedInputListener::flush() {
//把mArgsQueue中所有NotifyArgs对象取出来,调用它们的notify()函数。我们知道在前面KeyboardInputMapper的processKey()函数里我们push进去的args是NotifyKeyArgs类型,我们直接到NotifyKeyArgs的notify()方法里去看看。
args->notify(mInnerListener);
}
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
//调用了listener的notifyKey()函数。这个listener是InputManager的构造函数里创建InputReader时传进来的参数,我们回到InputManager的构造函数,显然listener实际上指向InputDispatcher对象的指针。我们到InputDispatcher的notifyKey()函数里去看看。
listener->notifyKey(this);
}
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
//把传进来的参数参数args创建KeyEntry对象。
KeyEntry* newEntry = new KeyEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, flags, args->keyCode, args->scanCode,
metaState, repeatCount, args->downTime);
//把创建出来的KeyEntry对象加入到mInboundQueue对象中。
needWake = enqueueInboundEventLocked(newEntry);
}
代码分析到这里InputReaderThread线程已经很明白了,正如线程的命名,该线程就是从管道里读取输入事件,然后将读到的event事件解析整合成KeyEntry对象放到InputDispatcher对象的mInboundQueue里,而另外的InputDispatcherThread线程显然会从mInboundQueue里取走KeyEntry分发到上层。下面我们来分析一下InputDispatcherThread线程,到它的运行函数threadLoop()里看看:
代码就不贴出来了,threadLoop()函数只是调用了mDispatcher的dispatchOnce()方法。我们直接到InputDispatcher的dispatchOnce()函数里看看。
void InputDispatcher::dispatchOnce() {
if (!haveCommandsLocked()) {
//调用了dispatchOnceInnerLocked()函数。这里我就不把函数的代码贴出来了,dispatchOnceInnerLocked()函数里,如果mInboundQueue不为空,那么函数会把mInboundQueue里的KeyEntry提取出来,根据KeyEntry的type的不同调用不同的处理函数,其中Key Event调用dispatchKeyLocked()函数处理。dispatchKeyLocked()函数里又调用dispatchEventLocked()方法,dispatchEventLocked()里调用prepareDispatchCycleLocked()方法,prepareDispatchCycleLocked()里调用enqueueDispatchEntriesLocked()方法,enqueueDispatchEntriesLocked()里调用startDispatchCycleLocked()方法,startDispatchCycleLocked()方法里根据type不同调用不同的函数,对于我调试的按键的话,函数调用了connection的inputPublisher变量的publishKeyEvent()方法。那么connection对象是什么鬼?哪来的?它的inputPublisher成员又是什么东西?
dispatchOnceInnerLocked(&nextWakeupTime);
}
}
connection是在dispatchEventLocked()函数里调用prepareDispatchCycleLocked()方法时,当参数传进去的。在dispatchEventLocked()里明显的能看见connection是根据index号从mConnectionsByFd容器里取的,那么代码的哪里把connection往mConnectionsByFd里放的呢?搜索一下mConnectionsByFd,很快得到答案。在InputDispatcher的registerInputChannel()函数里有调用mConnectionsByFd的add()方法。registerInputChannel()是从哪调用过来的?只要一找显然是WindowManagerService.java里调用了InputManagerService对象的registerInputChannel()方法,registerInputChannel()方法调用了JNI接口nativeRegisterInputChannel(),nativeRegisterInputChannel()方法的本地实现是com_android_server_input_InputManagerService.cpp里的nativeRegisterInputChannel()。nativeRegisterInputChannel()方法调用了NativeInputManager对象的registerInputChannel()方法,registerInputChannel()调用了InputDispatcher的registerInputChannel()函数。
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
//根据传进来的参数inputChannel创建出一个Connection对象。
sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
//调用add()方法把和参数inputChannel相关的Connection对象add到mConnectionsByFd里。
int fd = inputChannel->getFd();
mConnectionsByFd.add(fd, connection);
}
这时我们在回到前面的dispatchKeyLocked()函数里看的话,就比较清晰了:
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
//得到当前拥有焦点的窗口的InputChannel信息。把得到的信息保存在参数inputTargets里,参数inputTargets是InputTarget类型,而InputTarget类型里有个重要的成员变量inputChannel,它就是我们要找的InputChannel对象。
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
//以inputTargets当参数调用dispatchEventLocked()方法。dispatchEventLocked()调用getConnectionIndexLocked()函数得到mConnectionsByFd列表中和InputChannel对象关联的Connection对象的index。有了这个index也就可以得到connection对象了。
dispatchEventLocked(currentTime, entry, inputTargets);
}
到了这里,我们已经知道前面提到的startDispatchCycleLocked()方法里的Connection对象connection其实就是和当前拥有焦点的窗口关联的对象。现在我们就可以去看看startDispatchCycleLocked()方法调用的Connection对象的变量inputPublisher的publishKeyEvent()方法了。
status_t InputPublisher::publishKeyEvent(
uint32_t seq,
int32_t deviceId,
int32_t source,
int32_t action,
int32_t flags,
int32_t keyCode,
int32_t scanCode,
int32_t metaState,
int32_t repeatCount,
nsecs_t downTime,
nsecs_t eventTime) {
//调用了mChannel的sendMessage()函数。上面已经提到过了这个mChannel就是创建Connection对象时的参数inputChannel。我们在倒回去刚才分析的WindowManagerService.java里调用InputManagerService对象的registerInputChannel()方法那里看一下参数inputChannel是怎么回事。
return mChannel->sendMessage(&msg);
}
在WindowManagerService.java的addWindow()方法里看到了调用InputManagerService对象的registerInputChannel()方法,这里把Java层的InputChannel对象当参数一路传到Native层,到了com_android_server_input_InputManagerService.cpp里的nativeRegisterInputChannel()方法里。这里我就不把代码贴出来了,代码比较简单,类似的代码我们上面有分析过,就是我们在Java层创建InputChannel对象时,在Native层也创建了与之对应的NativeInputChannel对象,而NativeInputChannel对象的指针就保存在Java层的InputChannel对象的mPtr中。也就是说mChannel其实就是与Java层的InputChannel对象相对应的Native层的NativeInputChannel对象的指针。那么调用mChannel的sendMessage()函数,其实就是调用NativeInputChannel的sendMessage()函数。
代码分析到这里已经很清楚不过了,InputDispatcherThread线程从mInboundQueue里取走KeyEntry,对取到的event事件经过一番处理后,调用与Java层的InputChannel对象相对应的Native层的NativeInputChannel对象的sendMessage()方法把event事件发送给当前拥有焦点的窗口。
显然输入event事件的发送和Java层的InputChannel对象有着千丝万缕的关系,那么Java层收到事件后是怎么处理的,而输入法在按键处理上又处于什么地位呢?笔者将会在下一篇文章中继续讲解。