Android输入系统源码分析
2017-09-09 16:16
441 查看
参考:《深入理解Android 卷III》
输入事件的源头是位于/dev/input/下的设备节点,而输入系统的终点是由WMS管理的某个窗口。最初的输入事件为内核生成的原始事件,而最终交付给窗口的则是KeyEvent或MotionEvent对象。因此Android输入系统的主要工作是读取设备节点中的原始事件,将其加工封装,然后派发给一个特定的窗口以及窗口中的控件。这个过程由InputManagerService系统服务为核心的多个参与者共同完成。输入系统的总体流程和参与者如图所示。
Linux内核,接受输入设备的中断,并将原始事件的数据写入到设备节点中。
设备节点,作为内核与IMS的桥梁,它将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件。
InputManagerService,一个Android系统服务,它分为Java层和Native层两部分。Java层负责与WMS的通信。而Native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器。
EventHub,直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为getEvents()的函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。
InputReader,是IMS中的关键组件之一。它运行于一个独立的线程Reader线程中,通过其线程循环不断地通过getEvents()函数从EventHub中将事件取出并进行处理。然后交给InputDispatcher进行派发。
InputDispatcher,是IMS中的另一个关键组件。它也运行于一个独立的线程中Dispatcher线程中。InputDispatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。
WMS,虽说不是输入系统中的一员,但是它却对InputDispatcher的正常工作起到了至关重要的作用。当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。另外,WMS还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时地更新到IMS的InputDispatcher中,使得InputDispatcher可以正确地将事件派发到指定的窗口。
以下是代码分析,建议用sourceinsight进行代码跟踪。
输入系统会创建Reader线程和Dispatcher线程
至此,IMS的创建完成了。InputManager的创建过程分别为InputReader与InputDispatcher创建了承载它们运行的线程,此时start()函数的功能就是启动这两个线程,使得InputReader于InputDispatcher开始工作。
EventHub中使用RawEvent结构体是对input_event结构体的扩展,根据类型和源码可知,EventHub使用inotify检测/dev/input目录下设备的插入或者拔出,使用epoll检测/dev/input/eventx有无数据。
具体处理
分析到此结束,有误的地方还请提出宝贵的意见!
最后再次感谢《深入理解Android 卷III》这本书,通过看这本书我学到了很多东西,同时,书中有很多语言组织的非常好,我就照搬过来了。
输入事件的源头是位于/dev/input/下的设备节点,而输入系统的终点是由WMS管理的某个窗口。最初的输入事件为内核生成的原始事件,而最终交付给窗口的则是KeyEvent或MotionEvent对象。因此Android输入系统的主要工作是读取设备节点中的原始事件,将其加工封装,然后派发给一个特定的窗口以及窗口中的控件。这个过程由InputManagerService系统服务为核心的多个参与者共同完成。输入系统的总体流程和参与者如图所示。
Linux内核,接受输入设备的中断,并将原始事件的数据写入到设备节点中。
设备节点,作为内核与IMS的桥梁,它将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件。
InputManagerService,一个Android系统服务,它分为Java层和Native层两部分。Java层负责与WMS的通信。而Native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器。
EventHub,直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为getEvents()的函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。
InputReader,是IMS中的关键组件之一。它运行于一个独立的线程Reader线程中,通过其线程循环不断地通过getEvents()函数从EventHub中将事件取出并进行处理。然后交给InputDispatcher进行派发。
InputDispatcher,是IMS中的另一个关键组件。它也运行于一个独立的线程中Dispatcher线程中。InputDispatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。
WMS,虽说不是输入系统中的一员,但是它却对InputDispatcher的正常工作起到了至关重要的作用。当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。另外,WMS还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时地更新到IMS的InputDispatcher中,使得InputDispatcher可以正确地将事件派发到指定的窗口。
以下是代码分析,建议用sourceinsight进行代码跟踪。
输入系统会创建Reader线程和Dispatcher线程
Reader线程和Dispatcher线程创建及启动过程源码分析:
SystemServer所有服务被创建的源头,同其他系统服务一样,IMS在SystemServer中被启动。SystemServer.java (frameworks\base\services\java\com\android\server) private void startOtherServices() { inputManager = new InputManagerService(context); //创建InputManagerService inputManager.start(); //启动这个服务 }
InputManagerService.java (frameworks\base\services\core\java\com\android\server\input) public InputManagerService(Context context) { mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); //调用本地方法进行初始化 } public void start() { nativeStart(mPtr); //调用本地nativeStart函数 }
调用native方法,创建Reader线程和Dispatcher线程并启动
com_android_server_input_InputManagerService.cpp (frameworks\base\services\core\jni) static jlong nativeInit { /* 创建了一个NativeInputManager对象,此对象将是Native层组件与Java层IMS进行通信的桥梁 */ NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, messageQueue->getLooper()); } NativeInputManager::NativeInputManager { //NativeInputManager创建了EventHub,EventHub复杂的构造函数使其在创建后便拥有了监听设备节点的能力(Inotify和epoll机制) sp<EventHub> eventHub = new EventHub(); // 接着创建了Native层的InputManager mInputManager = new InputManager(eventHub, this, this); } 被InputManagerService的start函数所调用来启动线程。 static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); status_t result = im->getInputManager()->start(); }
InputManager统一创建并管理mReader、mReaderThread、mDispatcher、mDispatcherThread这四个实例化对象。
InputManager.cpp (frameworks\native\services\inputflinger) class InputManager { sp<InputReaderInterface> mReader; sp<InputReaderThread> mReaderThread; sp<InputDispatcherInterface> mDispatcher; sp<InputDispatcherThread> mDispatcherThread; } /* 在InputManager中创建InputDispatcher和InputReader实例化对象 */ InputManager::InputManager { mDispatcher = new InputDispatcher(dispatcherPolicy); mReader = new InputReader(eventHub, readerPolicy, mDispatcher); initialize(); } void InputManager::initialize() { //创建Reader线程和Dispatcher线程 mReaderThread = new InputReaderThread(mReader); mDispatcherThread = new InputDispatcherThread(mDispatcher); } status_t InputManager::start() { /* 启动ReaderThread和DispatcherThread */ status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); }
mDispatcherThread->run和mReaderThread->run分别会执行InputReaderThread和InputDispatcherThread线程的线程循环体。
InputReader.cpp (frameworks\native\services\inputflinger) /* 创建InputReaderThread线程,实现循环操作 */ bool InputReaderThread::threadLoop() { mReader->loopOnce(); // 执行InputReader的loopOnce()函数 return true; } InputReaderThread启动后,其线程循环将不断地执行InputReader.loopOnce()函数。因此这个loopOnce()函数作为线程循环的循环体包含了InputReader的所有工作。 InputDispatcher.cpp (frameworks\native\services\inputflinger) bool InputDispatcherThread::threadLoop() { mDispatcher->dispatchOnce(); return true; }
至此,IMS的创建完成了。InputManager的创建过程分别为InputReader与InputDispatcher创建了承载它们运行的线程,此时start()函数的功能就是启动这两个线程,使得InputReader于InputDispatcher开始工作。
EventHub分析(Reader线程使用EventHub读取事件):
getEvents读取的输入事件是一个RawEvent结构体 * Linux驱动上报的输入事件是input_event结构体 struct RawEvent { * struct input_event { nsecs_t when; * struct timeval time; int32_t deviceId; * __u16 type; int32_t type; * __u16 code; int32_t code; * __s32 value; int32_t value; * }; }; * * type: * type: DEVICE_ADDED //输入设备插入 * EV_KEY DEVICE_REMOVED //输入设备拔掉 * EV_ABS FINISHED_DEVICE_SCAN //有设备上报了输入事件 * EV_REL EV_KEY * EV_ABS * EV_REL *
EventHub中使用RawEvent结构体是对input_event结构体的扩展,根据类型和源码可知,EventHub使用inotify检测/dev/input目录下设备的插入或者拔出,使用epoll检测/dev/input/eventx有无数据。
EventHub.cpp (frameworks\native\services\inputflinger) 在EventHub的构造函数中使用inotify和epoll机制检测/dev/input目录下设备的插入或者拔出 EventHub::EventHub(void){ /* 初始化一个inotify句柄 */ mINotifyFd = inotify_init(); /* 将/dev/input目录添加到句柄中 */ int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE); //static const char *DEVICE_PATH = "/dev/input"; mEpollFd = epoll_create(EPOLL_SIZE_HINT); /* 将inotify句柄添加到epoll的检测列表中,开始检测 */ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem); }
获取输入事件 – getEvent函数
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { /* 这个循环是getEvents()函数的主体。在这个循环中,会先将可用事件放入到buffer中并返回。 如果没有可用事件,则进入epoll_wait()等待事件的到来,epoll_wait()返回后会重新循环将可用 将新事件放入buffer */ for (;;) { /* 扫描目录下的所有设备看是否有设备插入或者删除 */ scanDevicesLocked(); status_t res = scanDirLocked(DEVICE_PATH); dir = opendir(dirname); while((de = readdir(dir))) openDeviceLocked(devname) { /* 打开/dev/input/eventx */ int fd = open(devicePath, O_RDWR | O_CLOEXEC); /* 获取版本号,GID等等 */ ioctl(fd, EVIOCGVERSION, &driverVersion) ioctl(fd, EVIOCGID, &inputId) .... /* 将打开的文件的fd添加到epoll检测列表中,检测是否有数据产生 */ epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem) } /* 如果EPOLL_ID_INOTIFY,说明/dev/input目录有变化(增加或者删除设备),mPendingEventIndex指定尚未 处理的epoll_event的索引 */ while (mPendingEventIndex < mPendingEventCount) { const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++]; if (eventItem.data.u32 == EPOLL_ID_INOTIFY) { mPendingINotify = true; //更改标记位为true } /* 如果EPOLLIN,说明有输入事件产生,则去读取输入事件 */ if (eventItem.events & EPOLLIN) { int32_t readSize = read(device->fd, readBuffer,sizeof(struct input_event) * capacity); /* 读取的输入数据是input_event结构体 */ /* 将读取到的input_event结构体转换为EventHub中对应的RawEvent结构体 */ size_t count = size_t(readSize) / sizeof(struct input_event); for (size_t i = 0; i < count; i++) { event->type = iev.type; //RawEvent* event; event->code = iev.code; event->value = iev.value; event += 1; } } } /* 如果标记位为true,则去读取notify */ if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) { mPendingINotify = false; readNotifyLocked(); { //如果是插入了新的设备,那么就调用openDeviceLocked去打开这个设备,然后获取各种信息,并添加到 //检测列表(上面分析过) if(event->mask & IN_CREATE) openDeviceLocked(devname); } deviceChanged = true; } // Report added or removed devices immediately. //设备节点增删操作发生时,则重新执行循环体,以便将设备变化的事件放入buffer中 if (deviceChanged) { continue; } // 如果此次getEvents()调用成功获取了一些事件,或者要求唤醒InputReader,则退出循环并 // 结束getEvents()的调用,使InputReader可以立刻对事件进行处理 if(event != buffer || awoken) { break; } /* 不断的循环检测/dev/input下的所有设备epoll_wait()所取出的epoll_event存储在mPendingEventItems 中,mPendingEventCount指定了mPendingEventItems数组所存储的事件个数。*/ int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis); } //end of for(;;) // All done, return the number of events we read. return event - buffer; }
深入分析Reader线程
/*通过EventHub获取事件列表。读取的结果存储在参数mEventBuffer中,返回值表示事件的个数 当EventHub中无事件可以抽取时,此函数的调用将会阻塞直到事件到来或者超时 */ void InputReader::loopOnce() { /* 获取输入事件,参照下文EventHub分析 */ size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //如果有输入事件发生,则调用processEventsLocked()函数对事件进行加工处理 if (count) { processEventsLocked(mEventBuffer, count); } /*发布事件。 processEventsLocked()函数在对事件进行加工处理之后,便将处理后的事件存储在 mQueuedListener中。在循环的最后,通过调用flush()函数将所有事件交付给InputDispatcher */ mQueuedListener->flush(); }
对事件进行加工处理:
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { for (const RawEvent* rawEvent = rawEvents; count;) { /* 当有真正的输入事件,则调用processEventsForDeviceLocked进行处理 */ processEventsForDeviceLocked(deviceId, rawEvent, batchSize); /* 把所有的事件(插入新的输入设备/拔掉了一个输入设备/输入设备有输入事件产生)都抽取出来,进行判断 */ switch (rawEvent->type) { case EventHubInterface::DEVICE_ADDED: /* 对于增加新设备的时间,会在InputReader里面构造一下input_device结构体进行记录 */ 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; } } }
具体处理
processEventsForDeviceLocked(deviceId, rawEvent, batchSize); { InputDevice* device = mDevices.valueAt(deviceIndex); device->process(rawEvents, count); { InputMapper* mapper = mMappers[i]; mapper->process(rawEvent); { /* 对于键盘,调用Keyboard的process */ void KeyboardInputMapper::process(const RawEvent* rawEvent) { /* 映射:scanCode是驱动程序上报的扫描码 转换为Android系统中的keyCode */ getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags) {//eventhub.cpp /* 映射的时候会先使用kl文件映射,如果失败再使用kcm文件映射 */ status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t* outKeycode, uint32_t* outFlags) } /* 映射成功,则进行处理 */ processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags); { /* 根据参数构造args:down/up,keyCode scanCode newMetaState(是否有shift按下) */ NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime); /* 然后通知listener来进行处理,把args发给dispatch线程 */ getListener()->notifyKey(&args); } } } } }
总结:Reader线程对输入事件的处理很简单,就是把驱动上报的scancode转换为Android的AKEYCODE码,然后构造成args参数,最后传递给dispatcher线程。
Reader线程到Dispatcher线程间数据的传递:
在分析InputManager.cpp的时候分析过,InputManager管理四个对象mReader mReaderThread mDispatcher mDispatcherThread在InputManager的构造函数中创建mReader对象,其中第三个参数是一个listener: /* InputReader::InputReader(const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& policy, const sp<InputListenerInterface>& listener) */ mReader = new InputReader(eventHub, readerPolicy, mDispatcher); 所以getListener()->notifyKey(&args);中getListener()获取的是一个mDispatcher对象,notifyKey是Dispatcher中的notifyKey方法.
Dispatcher线程分析:
接收数据
InputDispatcher.cpp (frameworks\native\services\inputflinger) void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { /* 将从Reader线程传进来的args参数构造成一个KeyEvent结构体 */ KeyEvent event; event.initialize(args->deviceId, args->source, args->action, flags, keyCode, args->scanCode, metaState, 0, args->downTime, args->eventTime); /* 然后对传入的输入事件简单处理 */ mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); {//NativeInputManager::interceptKeyBeforeQueueing com_android_server_input_InputManagerService.cpp /* 调用phonewindowmanager.java中的同名函数,返回一个wmActions */ wmActions = env->CallIntMethod(mServiceObj, gServiceClassInfo.interceptKeyBeforeQueueing, keyEventObj, policyFlags); {//interceptKeyBeforeQueueing PhoneWindowManager.java (frameworks\base\policy\src\com\android\internal\policy\impl) /* 如果是global按键,则直接返回ACTION_PASS_TO_USER */ if (mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) return result; //result = ACTION_PASS_TO_USER; /* 对于systemkey 分类处理 */ 可以直接处理的话,处理它,设置返回值 !ACTION_PASS_TO_USER,不要传给用户否则ACTION_PASS_TO_USER } /* 根据返回值,设置policyFlags:POLICY_FLAG_PASS_TO_USER */ handleInterceptActions(wmActions, when, /*byref*/ policyFlags); if (wmActions & WM_ACTION_PASS_TO_USER) { policyFlags |= POLICY_FLAG_PASS_TO_USER; } /* 然后放入inbound队列中 */ needWake = enqueueInboundEventLocked(newEntry); if (needWake) { mLooper->wake(); //唤醒dispatch线程 } }
dispatch线程循环体分析
threadLoop() InputDispatcher.cpp mDispatcher->dispatchOnce(); { if (!haveCommandsLocked()) //没有命令则从inbound队列中取出事件,生成命令 dispatchOnceInnerLocked(&nextWakeupTime); { mPendingEvent = mInboundQueue.dequeueAtHead(); //从inbound队列中取出事件 DropReason dropReason = DROP_REASON_NOT_DROPPED; //默认不丢弃 对于:!POLICY_FLAG_PASS_TO_USER if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) dropReason = DROP_REASON_POLICY; switch (mPendingEvent->type) case EventEntry::TYPE_KEY: dispatchKeyLocked { // Clean up if dropping the event. if (*dropReason != DROP_REASON_NOT_DROPPED) return true; //返回后会不休眠继续执行dispatchOnce,处理下一个输入事件 } 对于:POLICY_FLAG_PASS_TO_USER switch (mPendingEvent->type) case EventEntry::TYPE_KEY: dispatchKeyLocked { if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { //构造命令放入队列 CommandEntry* commandEntry = postCommandLocked( &InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible); if (mFocusedWindowHandle != NULL) { commandEntry->inputWindowHandle = mFocusedWindowHandle; } commandEntry->keyEntry = entry; entry->refCount += 1; return false; // wait for the command to run } } /* 如果解析的结果是INTERCEPT_KEY_RESULT_CONTINUE,则寻找目标应用程序 */ int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime); // Dispatch the key. 然后把事件发给应用程序 dispatchEventLocked(currentTime, entry, inputTargets); } } if (runCommandsLockedInterruptible()) //运行命令 { /* 取出命令,然后执行命令 */ CommandEntry* commandEntry = mCommandQueue.dequeueAtHead(); //命令即doInterceptKeyBeforeDispatchingLockedInterruptible Command command = commandEntry->command; {//doInterceptKeyBeforeDispatchingLockedInterruptible /* 执行phonewindowmanager.java中的同名函数 */ nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle, &event, entry->policyFlags); {//interceptKeyBeforeDispatching PhoneWindowManager.java if (mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {//根据global_key.xml发送广播给某个组件 ComponentName component = mKeyMapping.get(keyCode); context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); } return -1; } /* 根据返回值来设置解析的结果 */ if (delay < 0) //返回-1, 不上传给app -- global key system key entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP; else if (!delay) //return 0,表示让app处理 -- user key entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; else entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER; entry->interceptKeyWakeupTime = now() + delay; } } nextWakeupTime = LONG_LONG_MIN; }
分析到此结束,有误的地方还请提出宝贵的意见!
最后再次感谢《深入理解Android 卷III》这本书,通过看这本书我学到了很多东西,同时,书中有很多语言组织的非常好,我就照搬过来了。
相关文章推荐
- 源码角度分析Android的事件输入系统(input system)及ANR原理分析
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android核心分析(15)--------Android输入系统之输入路径详解
- 【北京讲座】Android系统Framework层源码分析(2011.09.24)
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- 关于Ogre自带输入系统OIS(Object-Oriented Input System)的源码分析
- Android核心分析(15)--------Android输入系统之输入路径详解
- Android输入事件流程中的EventHub分析及源码演示
- Android核心分析(15)--------Android输入系统之输入路径详解k
- Android核心分析(14)------ Android GWES之输入系统
- Android核心分析(13)------ Android GWES之输入系统 .
- Android磁盘管理-系统源码分析(1)
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- 对“Android输入事件流程中的EventHub分析及源码演示”的补充
- Android核心分析(14)--------Android输入系统之输入路径详解 .
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android磁盘管理系列之系统源码分析(1)
- Android系统原理与源码分析