Android输入系统(三)InputReader的加工类型和InputDispatcher的分发过程

  • Android框架层
  • Android输入系统
  • Android框架层

本文首发于微信公众号「刘望舒」

前言

在上一篇文章中,我们学习了输入事件的处理,输入事件会交由InputDispatcher进行分发,那么InputDispatcher是如何进行分发的?这篇文章会给你答案。

1.InputReader的加工类型

Android输入系统(二)IMS的启动过程和输入事件的处理这篇文章中,我们知道InputReader会对原始输入事件进行加工,如果事件的类型为按键类型的事件,就会调用如下一段代码。 frameworks/native/services/inputflinger/InputDispatcher.cpp

1void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { 2 ... 3 bool needWake; 4 { 5 ... 6 } // release lock 7 if (needWake) { 8 mLooper->wake(); 9 } 10}

InputDispatcher的notifyKey方法用于唤醒InputDispatcherThread,它的参数NotifyKeyArgs是InputReader对按键类型的事件加工后得到的。 frameworks/native/services/inputflinger/InputListener.h

1struct NotifyKeyArgs : public NotifyArgs { 2 nsecs_t eventTime; 3 int32_t deviceId; 4 uint32_t source; 5 uint32_t policyFlags; 6 int32_t action; 7 int32_t flags; 8 int32_t keyCode; 9 int32_t scanCode; 10 int32_t metaState; 11 nsecs_t downTime; 12 inline NotifyKeyArgs() { } 13 NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags, 14 int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, 15 int32_t metaState, nsecs_t downTime); 16 NotifyKeyArgs(const NotifyKeyArgs& other); 17 virtual ~NotifyKeyArgs() { } 18 virtual void notify(const sp<InputListenerInterface>& listener) const; 19};

可以看到,NotifyKeyArgs结构体继承自NotifyArgs结构体,如下图所示。 Bykcj.png NotifyArgs有三个子类,分别是NotifyKeyArgs、NotifyMotionArgs和NotifySwichArgs,这说明InputReader对原始输入事件加工后,最终会得出三种事件类型,分别是key事件、Motion事件和Swich事件,这些事件会交由InputDispatcher来进行分发,如下图所示。 ByBn5.png

2.InputDispatcher的分发过程

不同的事件类型有着不同的分发过程,其中Swich事件的处理是没有派发过程的,在InputDispatcher的notifySwitch函数中会将Swich事件交由InputDispatcherPolicy来处理。本系列文章一直讲解key事件相关,这次换一下,以Motion事件的分发过程来进行举例,对key事件分发事件有兴趣的可以自行去看源码,本质上都差不多。

2.1 唤醒InputDispatcherThread

InputDispatcher的notifyMotion函数用来唤醒InputDispatcherThread。 frameworks/native/services/inputflinger/InputDispatcher.cpp

1void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { 2#if DEBUG_INBOUND_EVENT_DETAILS 3... 4#endif 5 //检查Motion事件的参数是否有效 6 if (!validateMotionEvent(args->action, args->actionButton, 7 args->pointerCount, args->pointerProperties)) {//1 8 return; 9 } 10 uint32_t policyFlags = args->policyFlags; 11 policyFlags |= POLICY_FLAG_TRUSTED; 12 mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags); 13 bool needWake; 14 { // acquire lock 15 mLock.lock(); 16 //Motion事件是否需要交由InputFilter过滤 17 if (shouldSendMotionToInputFilterLocked(args)) {//2 18 mLock.unlock(); 19 MotionEvent event; 20 //初始化MotionEvent,将NotifyMotionArgs中的参数信息赋值给MotionEvent中的参数 21 event.initialize(args->deviceId, args->source, args->action, args->actionButton, 22 args->flags, args->edgeFlags, args->metaState, args->buttonState, 23 0, 0, args->xPrecision, args->yPrecision, 24 args->downTime, args->eventTime, 25 args->pointerCount, args->pointerProperties, args->pointerCoords); 26 //表示已经过滤了 27 policyFlags |= POLICY_FLAG_FILTERED; 28 //开始过滤,如果返回值为false,就会直接return,这次事件不再进行分发 29 if (!mPolicy->filterInputEvent(&event, policyFlags)) {//3 30 return; // event was consumed by the filter 31 } 32 mLock.lock(); 33 } 34 /** 35 * 4 36 */ 37 MotionEntry* newEntry = new MotionEntry(args->eventTime, 38 args->deviceId, args->source, policyFlags, 39 args->action, args->actionButton, args->flags, 40 args->metaState, args->buttonState, 41 args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime, 42 args->displayId, 43 args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0); 44 needWake = enqueueInboundEventLocked(newEntry);//5 45 mLock.unlock(); 46 } // release lock 47 if (needWake) { 48 mLooper->wake();//6 49 } 50}

注释1处用于检查Motion事件的参数是否有效,其内部会检查触控点的数量pointerCount是否在合理范围内(小于1或者大于16都是不合理的),以及触控点的ID是否在合理范围内(小于0或者大于31都是不合理的)。 注释2处如果Motion事件需要交由InputFilter过滤,就会初始化MotionEvent,其作用就是用NotifyMotionArgs中的事件参数信息构造一个MotionEvent,接着MotionEven会交给注释3处的方法进行过滤,如果返回值为false,这次Motion事件就会被忽略掉。 注释4处,用NotifyMotionArgs中的事件参数信息构造一个MotionEntry对象。注释5处将MotionEntry传入到enqueueInboundEventLocked函数中,其内部会将MotionEntry添加到InputDispatcher的mInboundQueue队列的末尾,并返回一个值needWake,代表InputDispatcherThread是否需要唤醒,如果需要唤醒就调用注释6处的代码来唤醒InputDispatcherThread。

2.2 InputDispatcher进行分发

InputDispatcherThread被唤醒后,会执行InputDispatcherThread的threadLoop函数: frameworks/native/services/inputflinger/InputDispatcher.cpp

1bool InputDispatcherThread::threadLoop() { 2 mDispatcher->dispatchOnce(); 3 return true; 4}

threadLoop函数中只调用了InputDispatcher的dispatchOnce函数: frameworks/native/services/inputflinger/InputDispatcher.cpp

1void InputDispatcher::dispatchOnce() { 2 nsecs_t nextWakeupTime = LONG_LONG_MAX; 3 { // acquire lock 4 AutoMutex _l(mLock); 5 mDispatcherIsAliveCondition.broadcast(); 6 if (!haveCommandsLocked()) {//1 7 dispatchOnceInnerLocked(&nextWakeupTime);//2 8 } 9 if (runCommandsLockedInterruptible()) { 10 nextWakeupTime = LONG_LONG_MIN; 11 } 12 } // release lock 13 nsecs_t currentTime = now();//3 14 int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//4 15 mLooper->pollOnce(timeoutMillis); 16}

注释1处用于检查InputDispatcher的缓存队列中是否有等待处理的命令,如果没有就会执行注释2处的dispatchOnceInnerLocked函数,用来将输入事件分发给合适的。注释3处获取当前的时间,结合注释4处,得出InputDispatcherThread需要睡眠的时间为timeoutMillis。最后调用Looper的pollOnce函数使InputDispatcherThread进入睡眠状态,并将它的最长的睡眠的时间设置为timeoutMillis。当有输入事件产生时,InputReader就会将睡眠状态的InputDispatcherThread 唤醒,InputDispatcher会重新开始分发输入事件。查看注释2处的dispatchOnceInnerLocked函数是如何进行事件分发的。 frameworks/native/services/inputflinger/InputDispatcher.cpp

1void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { 2 ... 3 // 如果InputDispatcher被冻结,则不进行派发操作 4 if (mDispatchFrozen) { 5#if DEBUG_FOCUS 6 ALOGD("Dispatch frozen. Waiting some more."); 7#endif 8 return; 9 } 10 //如果isAppSwitchDue为true,说明没有及时响应HOME键等操作 11 bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;//1 12 if (mAppSwitchDueTime < *nextWakeupTime) {//2 13 *nextWakeupTime = mAppSwitchDueTime; 14 } 15 //如果还没有待分发的事件,去mInboundQueue中取出一个事件 16 if (! mPendingEvent) { 17 //如果mInboundQueue为空,并且没有待分发的事件,就return 18 if (mInboundQueue.isEmpty()) { 19 ... 20 if (!mPendingEvent) { 21 return; 22 } 23 } else { 24 //如果mInboundQueue不为空,取队列头部的EventEntry赋值给mPendingEvent 25 mPendingEvent = mInboundQueue.dequeueAtHead(); 26 traceInboundQueueLengthLocked(); 27 } 28 if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) { 29 pokeUserActivityLocked(mPendingEvent); 30 } 31 resetANRTimeoutsLocked(); 32 } 33 ALOG_ASSERT(mPendingEvent != NULL); 34 bool done = false; 35 DropReason dropReason = DROP_REASON_NOT_DROPPED;//3 36 ... 37 switch (mPendingEvent->type) {//4 38 ... 39 case EventEntry::TYPE_MOTION: { 40 MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent); 41 //如果没有及时响应窗口切换操作 42 if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) { 43 dropReason = DROP_REASON_APP_SWITCH; 44 } 45 //事件过期 46 if (dropReason == DROP_REASON_NOT_DROPPED 47 && isStaleEventLocked(currentTime, typedEntry)) { 48 dropReason = DROP_REASON_STALE; 49 } 50 //阻碍其他窗口获取事件 51 if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) { 52 dropReason = DROP_REASON_BLOCKED; 53 } 54 done = dispatchMotionLocked(currentTime, typedEntry, 55 &dropReason, nextWakeupTime);//5 56 break; 57 } 58 default: 59 ALOG_ASSERT(false); 60 break; 61 } 62 63 if (done) { 64 if (dropReason != DROP_REASON_NOT_DROPPED) { 65 dropInboundEventLocked(mPendingEvent, dropReason); 66 } 67 mLastDropReason = dropReason; 68 //释放本次事件处理的对象 69 releasePendingEventLocked();//6 70 //使得InputDispatcher能够快速处理下一个分发事件 71 *nextWakeupTime = LONG_LONG_MIN;//7 72}

InputDispatcher的dispatchOnceInnerLocked函数的代码比较长,这里截取了和Motion事件的分发相关的主要源码。主要做了以下几件事。

  1. InputDispatcher的冻结处理 如果当前InputDispatcher被冻结,则不进行派发操作,InputDispatcher有三种状态,分别是正常状态、冻结状态和禁用状态,可以通过InputDispatcher的setInputDispatchMode函数来设置。
  2. 窗口切换操作处理 注释1处的mAppSwitchDueTime ,代表了App最近发生窗口切换操作时(比如按下Home键、挂断电话),该操作事件最迟的分发时间。如果这个时候,mAppSwitchDueTime小于等于当前系统时间,说明没有及时响应窗口切换操作,则isAppSwitchDue的值设置为true。 注释2处,如果mAppSwitchDueTime小于nextWakeupTime(下一次InputDispatcherThread醒来的时间),就将mAppSwitchDueTime赋值给nextWakeupTime,这样当InputDispatcher处理完分发事件后,会第一时间处理窗口切换操作。
  3. 取出事件 如果没有待分发的事件,就从mInboundQueue中取出一个事件,如果mInboundQueue为空,并且没有待分发的事件,就return,如果mInboundQueue不为空,取队列头部的EventEntry赋值给mPendingEvent,mPendingEvent的类型为EventEntry对象指针。
  4. 事件丢弃 注释3处的dropReason代表了事件丢弃的原因,它的默认值为DROP_REASON_NOT_DROPPED,代表事件不被丢弃。 注释4处根据mPendingEvent的type做区分处理,这里主要截取了对Motion类型的处理。经过过滤,会调用注释5处的dispatchMotionLocked函数为这个事件寻找合适的窗口。
  5. 后续处理 如果注释5处的事件分发成功,则会在注释6处调用releasePendingEventLocked函数,其内部会将mPendingEvent的值设置为Null,并将mPendingEvent指向的对象内存释放掉。注释7处将nextWakeupTime的值设置为LONG_LONG_MIN,这是为了让InputDispatcher能够快速处理下一个分发事件。

后记

本文讲解了InputReader的加工类型和InputDispatcher的分发过程,由于文章篇幅的原因,InputDispatcher的分发过程还有一部分没有讲解,这一部分就是事件分发到目标窗口的过程,会在本系列的下一篇文章进行讲解。

点赞
收藏

评论区

加载中...

相关推荐

Android系统启动流程(四)Launcher启动过程与系统启动流程

Android框架层Android系统启动categories:Android框架层本文首发于微信公众号「刘望舒」前言此前的文章我们学习了init进程、Zygote进程和SyetemServer进程的启动过程,这一篇文章我们就来学习Android系统启动流程的最后一步:Launcher的启动流程,并结合本系列的前三

Android系统启动流程(三)解析SyetemServer进程启动过程

上一篇我们学习了Zygote进程,并且知道Zygote进程启动了SyetemServer进程,那么这一篇我们就来学习Android7.0版本的SyetemServer进程的启动过程。"tag:Android框架层Android系统启动categories:Android框架层本文首发于微信公众号「刘望舒」前言上一

Android解析WindowManager(三)Window的添加过程

Android框架层Android系统服务WindowManagercategories:Android框架层本文首发于微信公众号「刘望舒」前言在此前的系列文章中我们学习了WindowManager体系和Window的属性,这一篇我们接着来讲Window的添加过程。建议阅读此篇文章前先阅读本系列的前两篇文章。<!more1.概述WindowMana

Android系统启动流程(二)解析Zygote进程启动过程

上一篇文章我们分析了init进程的启动过程,启动过程中主要做了三件事,其中一件就是创建了Zygote进程,那么Zygote进程是什么,它做了哪些事呢?这篇文章会给你这些问题的答案。"tag:Android框架层Android系统启动categories:Android框架层本文首发于微信公众号「刘望舒」前言上一篇

Android包管理机制(二)PackageInstaller安装APK

Android框架层Android包管理机制Android框架层本文首发于微信公众号「刘望舒」前言在本系列上一篇文章中我们学习了PackageInstaller是如何初始化的,这一篇文章我们接着学习PackageInstaller是如何安装APK的。本系列文章的源码基于Android8.0。1.PackageInstaller中的处理紧接着上一篇的内

Android输入系统(二)IMS的启动过程和输入事件的处理

Android框架层Android输入系统Android框架层本文首发于微信公众号「刘望舒」基于Android8.1前言在上一篇文章中,我们学习了IMS的诞生(创建),IMS创建后还会进行启动,这篇文章我们来学习IMS的启动过程和输入事件的处理。1.IMS的启动过程IMS的创建在SystemServer的startOtherServices方法中,