【android 9】【input】【8.发送按键事件2——InputDispatcher线程】

系列文章目录

本人系列文章-CSDN博客


目录

系列文章目录

1.简介

1.1流程介绍

1.2 时序图

2.普通按键消息发送部分源码分析(按键按下事件)

2.1 开机后分发线程阻塞的地方

 2.2 InputDispatcher::dispatchOnceInnerLocked

 2.3 InputDispatcher::dispatchKeyLocked

 2.4 InputDispatcher::dispatchOnce

2.5 runCommandsLockedInterruptible

 2.6 InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible

 2.7 InputDispatcher::dispatchOnce

 2.8 InputDispatcher::dispatchKeyLocked

 2.9 InputDispatcher::findFocusedWindowTargetsLocked

2.10 dispatchEventLocked

 2.11 prepareDispatchCycleLocked

 2.12 enqueueDispatchEntriesLocked

2.13  enqueueDispatchEntryLocked

 2.14 startDispatchCycleLocked

2.15 InputPublisher::publishKeyEvent

 2.16 InputChannel::sendMessage


1.简介

从上一篇幅我们知道了,普通按键事件在inputreader线程中会将按下事件和抬起事件放入mInboundQueue队列中,然后唤醒InputDispatcher线程去进行派发。本篇我们便来介绍一下InputDispatcher线程是如何派发的。

1.1流程介绍

1.当InputDispatcher线程被唤醒时,第一次执行dispatchOnce时,会生成一个去查询按键事件派发策略的命令并放入命令队列中,然后开始第二次循环。

2.在第二次循环的时候,会从命令队列中取出命令,然后通过jni走到IMS,再走到WMS,再走到phonewindowMnager中询问此按键的派发策略。然后开始第三次循环。

3.如果询问的按键派发策略是立即派发,则会寻找焦点窗口。

4.查找到合适的目标窗口后,会从保存窗口信息的容器中取出窗口信息,窗口信息保存着socket的然后通过sokcet对,将按键消息从IMS发送到应用程序端。

此sokcet对是应用在创建的时候,会调用WMS的addView函数,此函数会创建两个socket对,其中一个fd通过binder通信返回给应用端,应用端会将此fd监听查看是否有消息,另外一个会传递给IMS,IMS也会监听其fd是否有消息。

1.2 时序图

 (图片可保存到本地放大观看)

2.普通按键消息发送部分源码分析(按键按下事件)

为了便于读者清晰的了解发送流程,此章节会删除不执行的代码,便于理解。

2.1 开机后分发线程阻塞的地方

void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LONG_LONG_MAX;//下一次唤醒该线程的时间,也可以说是下一次线程循环的执行时间点{ mDispatcherIsAliveCondition.broadcast();//分发线程处于活跃状态,进行广播if (!haveCommandsLocked()) {//此时无命令。作用:检查inputdispatcher的缓存队列中是否有还未处理的命令,//只有无命令时才会执行dispatchOnceInnerLocked方法,进行事件的分发。dispatchOnceInnerLocked(&nextWakeupTime);// 传入的nextWakeupTime决定了下次派发线程循环的执行时间点,}if (runCommandsLockedInterruptible()) {//如果此时缓存队列中有命令,则立即执行该命令,//并将下一次线程循环的执行时间点设置为LONG_LONG_MIN,将使派发线程立刻开始下次线程。//最小值定义为0,最大值是unsigned long long的最大值:1844674407370955161nextWakeupTime = LONG_LONG_MIN;}} nsecs_t currentTime = now();//获取当前时间点int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//下一次唤醒时间点-当前时间点=线程休眠时间mLooper->pollOnce(timeoutMillis);//调用pollOnce使线程休眠,等待回调,唤醒或超时。//Looper的pollOnce()的实质就是epoll_wait()。 因此派发线程的休眠在三种情况下可能被唤醒:1.调用Looper::wake()函数主动唤醒(有输入事件注入派发队列中时),2.到达nextWakeupTime的事件点时唤醒3.epoll_waitepoll_wait()监听的fd有epoll_event发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)。
}

从上一篇我们知道,当 inputreader线程向mInboundQueue中每次插入消息后,都会调用wake来唤醒分发线程。我们首先看一下按键按下的事件分发流程。

 2.2 InputDispatcher::dispatchOnceInnerLocked

此函数的主要作用是:

1.取出消息,然后通过按键的派发策略,和时间等生成对应的分发策略,默认为不丢弃。

2.然后再调用dispatchKeyLocked进行事件的派发。

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {nsecs_t currentTime = now();//如果没有待发送的事件,则从mInboundQueue队列中取出一个事件,进行分发if (! mPendingEvent) {}else {//从派发队列中将位于队首的一条EventEntry取出并保存在mPendingEvent成员变量中。 // mPendingEvent表示处于派发过程中的一个输入事件mPendingEvent = mInboundQueue.dequeueAtHead();traceInboundQueueLengthLocked();//更新一下队列长度}if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) //设备消息类型为发送给应用程序{//判断此key是否需要发送到应用程序。pokeUserActivityLocked(mPendingEvent);//用来决定是否要唤醒设备或者点亮屏幕,最终调用的是PowerManagerService。}resetANRTimeoutsLocked();//重置ANR超时时间}bool done = false;DropReason dropReason = DROP_REASON_NOT_DROPPED;//默认丢弃原因为不丢弃switch (mPendingEvent->type) {case EventEntry::TYPE_KEY: {//按键类型KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);//进一步分发按键事件,结果赋值给 done// 无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略时是falsebreak;}default:break;}if (done) {//无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略时是falsemLastDropReason = dropReason;releasePendingEventLocked();*nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately}
}

 2.3 InputDispatcher::dispatchKeyLocked

此函数的作用是:

1.生成一个commond命令,并入队命令队列,询问此按键按下事件的分发策略。注意,一共是有两个策略,一个是派发策略,派发策略主要是询问是否是系统消费,是否要派发给应用程序等,而此时的分发策略是询问是否丢弃派发,是否等会派发等(如组合按键)

2.终止此时按键事件的派发,开启下一轮循环,先询问按键的分发策略,再派发此按键按下事件。

//此次派发会生成一个commond命令,并入队。去询问key的派发策略,因此此key事件咱未派发。commandEntry->keyEntry = entry;中保存了此次要派发的entry的信息。
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,DropReason* dropReason, nsecs_t* nextWakeupTime) {// Preprocessing.if (! entry->dispatchInProgress) {//dispatchInProgress默认为false,表示事件是否正在派发entry->dispatchInProgress = true;//设置事件为正在派发中}// 给policy一个机会,去截断key.//如果事件的interceptKeyResult是不知道,但是事件的policyFlags是要发送到应用程序的,则生成一个CommandEntry,然后返回false,等待命令的执行//如果事件的interceptKeyResult是不知道,事件不是发送给应用程序的,则设置falg为INTERCEPT_KEY_RESULT_CONTINUE。//如果事件的interceptKeyResult是INTERCEPT_KEY_RESULT_SKIP,表示跳过此事件,则设置其丢弃原因为DROP_REASON_POLICYif (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {//初始化的时候值是INTERCEPT_KEY_RESULT_UNKNOWN//如果此事件尚未进行过派发策略查询,则通过发送一个命令的方式查询派发策路 if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {CommandEntry* commandEntry = postCommandLocked(//生成了一个commandEntry& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);commandEntry->keyEntry = entry;entry->refCount += 1;return false; // 终止此次派发,开始下一轮的循环等待策略查询完成}}
}
InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {CommandEntry* commandEntry = new CommandEntry(command);mCommandQueue.enqueueAtTail(commandEntry);//入队命令队列return commandEntry;
}

 此时我们回到 2.2 InputDispatcher::dispatchOnceInnerLocked章节,所以我们可以看出来,第一次派发的按键时,要询问该按键的派发策略,所以done的值是flase

//此时回到上面928行的这个函数,注意此时命令队列中存在一条询问key事件派发策略的命令。
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);//进一步分发按键事件,结果赋值给 done// 无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略时是false/**//此时是false,if (done) {if (dropReason != DROP_REASON_NOT_DROPPED) {dropInboundEventLocked(mPendingEvent, dropReason);}mLastDropReason = dropReason;releasePendingEventLocked();*nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately}*/
}

 2.4 InputDispatcher::dispatchOnce

然后开启了下一次循环,此时命令队列中存在查询按键按下的分发策略的命令,所以执行runCommandsLockedInterruptible函数

void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LONG_LONG_MAX;//下一次唤醒该线程的时间,也可以说是下一次线程循环的执行时间点{ AutoMutex _l(mLock);mDispatcherIsAliveCondition.broadcast();//分发线程处于活跃状态,进行广播/*if (!haveCommandsLocked()) {//此时有命令。作用:检查inputdispatcher的缓存队列中是否有还未处理的命令,//只有无命令时才会执行dispatchOnceInnerLocked方法,进行事件的分发。dispatchOnceInnerLocked(&nextWakeupTime);// 传入的nextWakeupTime决定了下次派发线程循环的执行时间点,}*/if (runCommandsLockedInterruptible()) {//如果此时缓存队列中有一条询问key事件派发策略的命令,则立即执行该命令,//并将下一次线程循环的执行时间点设置为LONG_LONG_MIN,将使派发线程立刻开始下次线程。//最小值定义为0,最大值是unsigned long long的最大值:1844674407370955161nextWakeupTime = LONG_LONG_MIN;//立刻下次唤醒。}} // release locknsecs_t currentTime = now();//获取当前时间点int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//下一次唤醒时间点-当前时间点=线程休眠时间mLooper->pollOnce(timeoutMillis);//调用pollOnce使线程休眠,等待回调,唤醒或超时。//Looper的pollOnce()的实质就是epoll_wait()。 因此派发线程的休眠在三种情况下可能被唤醒:1.调用Looper::wake()函数主动唤醒(有输入事件注入派发队列中时),2.到达nextWakeupTime的事件点时唤醒3.epoll_waitepoll_wait()监听的fd有epoll_event发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)。
}

2.5 runCommandsLockedInterruptible

此函数的主要作用是:

1.取出命令,执行doInterceptKeyBeforeDispatchingLockedInterruptible函数,查询分发策略。

bool InputDispatcher::runCommandsLockedInterruptible() {do {CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();//取出命令Command command = commandEntry->command;(this->*command)(commandEntry); // 执行doInterceptKeyBeforeDispatchingLockedInterruptible函数,并传入commandEntry作为参数commandEntry->connection.clear();//清除connectiondelete commandEntry;} while (! mCommandQueue.isEmpty());return true;
}

 2.6 InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible

此处简单介绍一下:

会先通过JNI走到IMS中,IMS会调用到WMS中,最终会调用到phoneWindowManager的interceptKeyBeforeDispatching函数,此函数内部会返回delay。

1.正常应用获取焦点时(即应用在前台交互),那么事件则可以继续派发。

2.比如我们现在按下了手机的音量按键,有可能存在接下来按键是电源按键,则存在是组合按键实现截屏的功能,因此现在系统也不知道此时是控制音量,还是截屏,所以需要等一会再询问此按键派发策略。

3.如果此时系统或者焦点(即应用程序没有焦点),则派发给应用是无效的,所以该按键丢弃。

//向mPolicy询问,
//如果delay小于0,则设置为INTERCEPT_KEY_RESULT_SKIP,代表丢弃该事件
//如果delay=0,则设置为INTERCEPT_KEY_RESULT_CONTINUE,代表事件可以继续派发
//如果delay>0,则设置为INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,并设置interceptKeyWakeupTime,表示等会再询问派发策略
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry) {KeyEntry* entry = commandEntry->keyEntry;//从命令队列中取出key entry事件KeyEvent event;initializeKeyEvent(&event, entry);mLock.unlock();android::base::Timer t;nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,&event, entry->policyFlags);//此时应该是空if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",std::to_string(t.duration().count()).c_str());}mLock.lock();if (delay < 0) {//如果delay小于0,则设置为INTERCEPT_KEY_RESULT_SKIP//代表丢弃该事件entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;} else if (!delay) {如果delay=0,则设置为INTERCEPT_KEY_RESULT_CONTINUE//代表事件可以继续派发entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;} else {//如果delay>0,则设置为INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,表示等会再询问派发策略,并设置interceptKeyWakeupTimeentry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;entry->interceptKeyWakeupTime = now() + delay;}entry->release();
}

 2.7 InputDispatcher::dispatchOnce

然后会询问完按键按下事件的派发策略后,会立刻唤醒派发循环,开始派发此按键按下的事件。

//执行查询派发key事件策略的命令后,会立即执行下一次循环。
void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LONG_LONG_MAX;//下一次唤醒该线程的时间,也可以说是下一次线程循环的执行时间点{ AutoMutex _l(mLock);mDispatcherIsAliveCondition.broadcast();//分发线程处于活跃状态,进行广播if (!haveCommandsLocked()) {//作用:检查inputdispatcher的缓存队列中是否有还未处理的命令//只有无命令时才会执行dispatchOnceInnerLocked方法,进行事件的分发。dispatchOnceInnerLocked(&nextWakeupTime);// 传入的nextWakeupTime决定了下次派发线程循环的执行时间点}} // release locknsecs_t currentTime = now();//获取当前时间点int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//下一次唤醒时间点-当前时间点=线程休眠时间mLooper->pollOnce(timeoutMillis);//调用pollOnce使线程休眠,等待回调,唤醒或超时。//Looper的pollOnce()的实质就是epoll_wait()。 因此派发线程的休眠在三种情况下可能被唤醒://1.调用Looper::wake()函数主动唤醒(有输入事件注入派发队列中时),//2.到达nextWakeupTime的事件点时唤醒//3.epoll_waitepoll_wait()监听的fd有epoll_event发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)。
}

此时我们继续查看dispatchOnceInnerLocked函数

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {nsecs_t currentTime = now();//此时不为空,此时mPendingEvent是之前key按下事件,在上一次返回flse时候,没有释放此事件if (! mPendingEvent) {//如果没有待发送的事件,则从mInboundQueue队列中取出一个事件,进行分发}bool done = false;DropReason dropReason = DROP_REASON_NOT_DROPPED;//默认丢弃原因为不丢弃switch (mPendingEvent->type) {case EventEntry::TYPE_KEY: {//按键类型KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);//进一步分发按键事件,结果赋值给 done// 无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略或者此事件没有处理时是falsebreak;}if (done) {if (dropReason != DROP_REASON_NOT_DROPPED) {dropInboundEventLocked(mPendingEvent, dropReason);}mLastDropReason = dropReason;releasePendingEventLocked();*nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately}
}

 2.8 InputDispatcher::dispatchKeyLocked

此函数的主要作用是:

1.通过findFocusedWindowTargetsLocked从保存窗口信息的列表中,找到合适的派发窗口。

2.dispatchEventLocked向指定窗口派发按键按下的事件。

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,DropReason* dropReason, nsecs_t* nextWakeupTime) {// Preprocessing.if (! entry->dispatchInProgress) {//此时dispatchInProgress为true}Vector<InputTarget> inputTargets;//inputTargets是一个容器,会存储目标窗口的信息,根据key事件的类型,寻找合适的目标窗口。//其返回值injectionResult指明寻找结果,而找到的合适的目标窗口信息将被保存在inputTargets列表中int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,entry, inputTargets, nextWakeupTime);setInjectionResultLocked(entry, injectionResult);//将injectionResult结果赋值到event内部的一个属性中///addMonitoringTargetsLocked(inputTargets);//默认mMonitoringChannels是false// Dispatch the key.dispatchEventLocked(currentTime, entry, inputTargets);return true;
}

 2.9 InputDispatcher::findFocusedWindowTargetsLocked

按键的派发相较于motion事件的派发比较简单,按键事件的派发只需要派发给获取焦点的应用程序即可,获取焦点的应用程序即:用户正在交互使用的应用程序。

此函数的作用是:

1.检查目标窗口的权限和是否已经准备就绪。

2.当窗口准备好了以后,addWindowTargetLocked会将焦点窗口的相关信息保存到inputTargets中。

那么此时读者会疑问,焦点窗口是什么时候被设置到input中的,此篇仅简单介绍一下:

当窗口发生变化时,WMS会调用InputMonitor类的updateInputWindowsLw函数,而InputMonitor类是IMS的回调类,于是便通过jni更新窗口信息到InputDispatcher中。

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {int32_t injectionResult;std::string reason;// 如果有焦点窗口,则检查权限,权限检查失败,则if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;goto Failed;}// 检查窗口是否准备好进行更多输入事件reason = checkWindowReadyForMoreInputLocked(currentTime,mFocusedWindowHandle, entry, "focused");if (!reason.empty()) {//如果reason不为空,则可能发生了anr,用handleTargetsNotReadyLocked去判断injectionResult = handleTargetsNotReadyLocked(currentTime, entry,mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.c_str());goto Unresponsive;}//代表此时有焦点窗口,并且权限通过,并且准备好接受输入事件injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;//给flag赋值addWindowTargetLocked(mFocusedWindowHandle,InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),inputTargets);//addWindowTargetLocked 函数将接收按键事件的目标窗口的一些信息保存到 inputTargets容器中//参数分析://mFocusedWindowHandle是焦点窗口//InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS代表,是前台窗口和必须原样发送事件//0//inputTargets保存目标窗口的信息的容器,此时还是空// Done.
Failed:
Unresponsive:nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);//timeSpentWaitingForApplication是等待应用程序花费的时间updateDispatchStatisticsLocked(currentTime, entry,injectionResult, timeSpentWaitingForApplication);return injectionResult;
}
bool InputDispatcher::checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,const InjectionState* injectionState) {/*		if (injectionState//injectionState代表是否是从IMS注入的事件,默认物理按键是null&& (windowHandle == NULL|| windowHandle->getInfo()->ownerUid != injectionState->injectorUid)&& !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {if (windowHandle != NULL) {ALOGW("Permission denied: injecting event from pid %d uid %d to window %s ""owned by uid %d",injectionState->injectorPid, injectionState->injectorUid,windowHandle->getName().c_str(),windowHandle->getInfo()->ownerUid);} else {ALOGW("Permission denied: injecting event from pid %d uid %d",injectionState->injectorPid, injectionState->injectorUid);}return false;}*/return true;
}
//从windowHandle的句柄中获取getInfo,然后将其inputchannel等信息生成InputTarget类对象,然后保存到inputTargets容器中
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {inputTargets.push();const InputWindowInfo* windowInfo = windowHandle->getInfo();InputTarget& target = inputTargets.editTop();target.inputChannel = windowInfo->inputChannel;//获取发送窗口的通道target.flags = targetFlags;//此时按键的flag是InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IStarget.xOffset = - windowInfo->frameLeft;//窗口坐标的左target.yOffset = - windowInfo->frameTop;窗口坐标的顶部target.scaleFactor = windowInfo->scaleFactor;//窗口的缩放系数target.pointerIds = pointerIds;//pointerIds是0
}

2.10 dispatchEventLocked

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {pokeUserActivityLocked(eventEntry);//保持屏幕唤醒for (size_t i = 0; i < inputTargets.size(); i++) {//此时inputTargets中只有一个焦点窗口const InputTarget& inputTarget = inputTargets.itemAt(i);ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);//此函数的作用是检查此窗口的inputchannel是否已注册,//如果mConnectionsByFd中存在对应的connection,则返回索引if (connectionIndex >= 0) {sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);//获取此窗口的connection类的对象prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);}}
}

 2.11 prepareDispatchCycleLocked

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,const sp<Connection> &connection, EventEntry *eventEntry, const InputTarget *inputTarget)
{//不分割,按照原样事件进行入队分发enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

 2.12 enqueueDispatchEntriesLocked

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,const sp<Connection> &connection, EventEntry *eventEntry, const InputTarget *inputTarget)
{bool wasEmpty = connection->outboundQueue.isEmpty();//此时是空// 调用了六次 enqueueDispatchEntryLocked 函数,仅仅最后一个参数不同,但不是执行六次,//而是根据inputTarget的flags和最后一个参数是否相等,如果相等则处理,不相等会返回。/**enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_OUTSIDE);enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);*/enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_IS);//key事件的flags是InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,//如果是key事件,则只有这个函数执行了,此函数看名字感觉是将entry入队/**enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);*/// 当原先的 outbound 队列为空, 且当前 outbound 不为空的情况执行,开始循环分发if (wasEmpty && !connection->outboundQueue.isEmpty()){startDispatchCycleLocked(currentTime, connection);}
}

2.13  enqueueDispatchEntryLocked

此函数的作用是:

1.将EventEntry 保存到 DispatchEntry中。

2.判断按键是否是抬起按键,如果按键是抬起按键,则判断之前此按键是否按下过,如果没有,则删除此抬起按键。

3.将DispatchEntry按键事件放入outboundQueue的队列中。

void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection> &connection, EventEntry *eventEntry, const InputTarget *inputTarget,int32_t dispatchMode)
{int32_t inputTargetFlags = inputTarget->flags;//此时的获取的flag是InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_ISinputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;//FLAG_DISPATCH_MASK是所有的flag集合,dispatchMode此时传入的是原样发送//这是一个新的事件。//将 EventEntry 转换为 DispatchEntry, 在后面会加入 connection 的 outbound 队列DispatchEntry *dispatchEntry = new DispatchEntry(eventEntry, inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,inputTarget->scaleFactor);//应用target标志并更新连接的输入状态。switch (eventEntry->type){case EventEntry::TYPE_KEY:{KeyEntry *keyEntry = static_cast<KeyEntry *>(eventEntry);dispatchEntry->resolvedAction = keyEntry->action;//从keyEntry中设置其action和flagdispatchEntry->resolvedFlags = keyEntry->flags;//此函数的作用主要是检查一个抬起的按键之前是否有按下过,和将按下的按键添加到一个容器中//返回值为ture,代表是抬起的按键,之前有按下过。返回false,则代表up按键无按下过,不一致的事件,则删除if (!connection->inputState.trackKey(keyEntry,dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)){
#if DEBUG_DISPATCH_CYCLEALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",connection->getInputChannelName().c_str());
#endifdelete dispatchEntry;return; // skip the inconsistent event}break;}}// 我们正在等待此dispatchEntry事件的分发完成if (dispatchEntry->hasForegroundTarget())//是要发送到前台,所以是执行的如果此事件是要发送到前台窗口{incrementPendingForegroundDispatchesLocked(eventEntry);//增加未处理的前台分发数量}// 将dispatchEntry事件放入connection对象的outboundQueue序列的队尾connection->outboundQueue.enqueueAtTail(dispatchEntry);traceOutboundQueueLengthLocked(connection);//追踪队列的长度
}
InputDispatcher::DispatchEntry::DispatchEntry(EventEntry* eventEntry,int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) :seq(nextSeq()),//nextSeq是一个静态函数,里面有一个int值的静态变量,每构造一个DispatchEntry对象,此值就加1,然后赋值给seqeventEntry(eventEntry),//保存的EventEntry对象targetFlags(targetFlags),//发送的flagxOffset(xOffset), //目标窗口的x偏移量yOffset(yOffset), //目标窗口的y偏移量scaleFactor(scaleFactor),//缩放比例deliveryTime(0), resolvedAction(0), resolvedFlags(0) {eventEntry->refCount += 1;}
bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,int32_t action, int32_t flags) {switch (action) {/**case AKEY_EVENT_ACTION_UP: {if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {for (size_t i = 0; i < mFallbackKeys.size(); ) {if (mFallbackKeys.valueAt(i) == entry->keyCode) {mFallbackKeys.removeItemsAt(i);} else {i += 1;}}}ssize_t index = findKeyMemento(entry);//如果从容器中找到此案件之前按下的事件,则移除按下的事件,并返回tureif (index >= 0) {mKeyMementos.removeAt(index);//mKeyMementos存储了所有按下还未抬起的key事件return true;}//我们不能只放弃按键事件,因为这会阻止创建弹出窗口,当按键被按下时,弹出窗口会自动显示,然后在按键被释放时被关闭。//问题是弹出窗口将不会收到原始的向下键,因此向上键将被视为与其观察到的状态不一致。//我们也许可以通过合成一个向下键来处理这个问题,但这会导致其他问题。//因此,现在,允许分发不一致的按键up事件。#if DEBUG_OUTBOUND_EVENT_DETAILSALOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, ""keyCode=%d, scanCode=%d",entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
#endifreturn false;return true;}*/case AKEY_EVENT_ACTION_DOWN: {//如果按键是按下,则用findKeyMemento查找是否已经有此按键按下的事件,如果有,//则表示产生了连续两个按下的事件,故移除上一个按下的按键,并通过addKeyMemento函数将这次按下的按键保存到容器中ssize_t index = findKeyMemento(entry);if (index >= 0) {mKeyMementos.removeAt(index);}addKeyMemento(entry, flags);return true;}default:return true;}
}
void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {mKeyMementos.push();KeyMemento& memento = mKeyMementos.editTop();memento.deviceId = entry->deviceId;memento.source = entry->source;memento.keyCode = entry->keyCode;memento.scanCode = entry->scanCode;memento.metaState = entry->metaState;memento.flags = flags;memento.downTime = entry->downTime;memento.policyFlags = entry->policyFlags;
}
inline bool hasForegroundTarget() const {return targetFlags & InputTarget::FLAG_FOREGROUND;
}
void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {InjectionState* injectionState = entry->injectionState;/*if (injectionState) {//默认是nullinjectionState->pendingForegroundDispatches += 1;//pendingForegroundDispatches默认值是0,表示正在进行的前台分发的数量}*/
}

 2.14 startDispatchCycleLocked

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,const sp<Connection>& connection) {//当connection的状态正常且outboundQueue不为空时while (connection->status == Connection::STATUS_NORMAL&& !connection->outboundQueue.isEmpty()) {DispatchEntry* dispatchEntry = connection->outboundQueue.head;//取出队列头部的dispatchEntry事件dispatchEntry->deliveryTime = currentTime;//完成发送的时间// Publish the event.status_t status;EventEntry* eventEntry = dispatchEntry->eventEntry;//取出EventEntry类型的事件switch (eventEntry->type) {case EventEntry::TYPE_KEY: {KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);// Publish the key event.status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,//DispatchEntry是一个链表,seq表示发送事件的序列号。keyEntry->deviceId, keyEntry->source,dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,keyEntry->keyCode, keyEntry->scanCode,keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,keyEntry->eventTime);//调用了Connection类中的InputPublisher类对象的publishKeyEvent方法。//参数分析://dispatchEntry->seq,请求序列,seq表示发送事件的序列号。//keyEntry->deviceId,设备id//keyEntry->source,是KeyboardInputMapper初始化时赋值的,//其值是INPUT_DEVICE_CLASS_KEYBOARD,INPUT_DEVICE_CLASS_DPAD从,INPUT_DEVICE_CLASS_GAMEPAD的某一个//dispatchEntry->resolvedAction=keyEntry->action,表示按下或者抬起//dispatchEntry->resolvedFlags,表示flag策略,此时应该是POLICY_FLAG_PASS_TO_USER//keyEntry->keyCode,对应的按键码//keyEntry->scanCode,按键码对应的扫描码break;}}// 检查发送的结果if (status) {if (status == WOULD_BLOCK) {if (connection->waitQueue.isEmpty()) {ALOGE("channel '%s' ~ Could not publish event because the pipe is full. ""This is unexpected because the wait queue is empty, so the pipe ""should be empty and we shouldn't have any problems writing an ""event to it, status=%d", connection->getInputChannelName().c_str(),status);abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);} else {// Pipe is full and we are waiting for the app to finish process some events// before sending more events to it.connection->inputPublisherBlocked = true;}} else {ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, ""status=%d", connection->getInputChannelName().c_str(), status);abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);}return;}// Re-enqueue the event on the wait queue.connection->outboundQueue.dequeue(dispatchEntry);//发送完成后,将此dispatchEntry退出outboundQueue队列traceOutboundQueueLengthLocked(connection);//追踪outboundQueue队列长度connection->waitQueue.enqueueAtTail(dispatchEntry);//将此dispatchEntry入队到waitQueue队列中traceWaitQueueLengthLocked(connection);//追踪waitQueue队列的长度}
}

2.15 InputPublisher::publishKeyEvent

主要作用是:

1.将消息封装成InputMessage,然后通过InputChannel发送消息。

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) {if (!seq) {ALOGE("Attempted to publish a key event with sequence number 0.");return BAD_VALUE;}InputMessage msg;msg.header.type = InputMessage::TYPE_KEY;//表示按键事件msg.body.key.seq = seq;//消息的序列号msg.body.key.deviceId = deviceId;//设备idmsg.body.key.source = source;//表示键盘等msg.body.key.action = action;//按下或者抬起msg.body.key.flags = flags;//表示policy,发送给应用msg.body.key.keyCode = keyCode;msg.body.key.scanCode = scanCode;msg.body.key.metaState = metaState;//控制键的状态msg.body.key.repeatCount = repeatCount;msg.body.key.downTime = downTime;msg.body.key.eventTime = eventTime;return mChannel->sendMessage(&msg);
}

 2.16 InputChannel::sendMessage

此处便是真正调用socket send接口发送消息的地方。

status_t InputChannel::sendMessage(const InputMessage* msg) {const size_t msgLength = msg->size();InputMessage cleanMsg;msg->getSanitizedCopy(&cleanMsg);//InputMessage字段之间可能有非零字节。//强制将整个内存初始化为零,然后仅在每个字段的基础上复制有效字节。ssize_t nWrite;do {nWrite = ::send(mFd, &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); } while (nWrite == -1 && errno == EINTR);//如果出错,则一直循环发送等到其成功为止//函数说明:send()用来将数据由指定的socket 传给对方主机. //参数s 为已建立好连接的socket. //参数msg 指向发送的数据内容. //参数len 则为数据长度. //参数flags 一般设0, 其他数值定义如下: //MSG_OOB 传送的数据以out-of-band 送出. //MSG_DONTROUTE 取消路由表查询 //MSG_DONTWAIT 设置为不可阻断运作 //MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断. //返回值:成功则返回实际传送出去的字符数, 失败返回-1. 错误原因存于errno.//当 mFd 写入消息后,此时会唤醒处于 epoll_wait 状态的应用进程的 UI 线程if (nWrite < 0) {//如果发送失败int error = errno;if (error == EAGAIN || error == EWOULDBLOCK) {return WOULD_BLOCK;}if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED || error == ECONNRESET) {return DEAD_OBJECT;}return -error;}if (size_t(nWrite) != msgLength) {return DEAD_OBJECT;}return OK;
}
size_t InputMessage::size() const {switch (header.type) {case TYPE_KEY:return sizeof(Header) + body.key.size();
}
}
//InputMessage字段之间可能有非零字节。强制将整个内存初始化为零,然后仅在每个字段的基础上复制有效字节。
void InputMessage::getSanitizedCopy(InputMessage* msg) const {memset(msg, 0, sizeof(*msg));//初始化所有字节为0// Write the headermsg->header.type = header.type;// Write the bodyswitch(header.type) {case InputMessage::TYPE_KEY: {// uint32_t seqmsg->body.key.seq = body.key.seq;// nsecs_t eventTimemsg->body.key.eventTime = body.key.eventTime;// int32_t deviceIdmsg->body.key.deviceId = body.key.deviceId;// int32_t sourcemsg->body.key.source = body.key.source;// int32_t displayIdmsg->body.key.displayId = body.key.displayId;// int32_t actionmsg->body.key.action = body.key.action;// int32_t flagsmsg->body.key.flags = body.key.flags;// int32_t keyCodemsg->body.key.keyCode = body.key.keyCode;// int32_t scanCodemsg->body.key.scanCode = body.key.scanCode;// int32_t metaStatemsg->body.key.metaState = body.key.metaState;// int32_t repeatCountmsg->body.key.repeatCount = body.key.repeatCount;// nsecs_t downTimemsg->body.key.downTime = body.key.downTime;break;}default: {LOG_FATAL("Unexpected message type %i", header.type);break;}}
}

此处涉及inputchannel的创建过程。

大致讲解一下:

1.应用程序app一定会有activity,然后在打开app时会调用Activity.onCreate,然后经过AMS,再经过WMS,会调用WindowManagerGlobal.addView(),这个函数里面会创建socket对,一个被注册到注册到 InputDispatcher 中,一个返回给app进程的客户端,app进程会监听此socket,当有事件发送到应用程序时,会回调 InputEventReceiver 对应的 native 层对象 NativeInputEventReceiver 的 handleEvent 函数。

此时,我们就通过socket发送按键消息到达了应用端,下一篇android 9】【input】【9.发送按键事件3——Inputchannel的创建过程】我们主要介绍一下应用端是在什么时候创建的sokcet进行的通信。然后在【android 9】【input】【10.发送按键事件4——view的处理流程】,在这一篇主要介绍一下应用层的处理流程。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/22076.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用C语言实现贪吃蛇(超详细)

目录 实现贪吃蛇我们要知道哪些&#xff1f; Easyx图形库 Easyx的安装 游戏思路 游戏实现 头文件的使用 ​编辑和食物以及控制方向的初始化 对于坐标的实现&#xff1a; 食物颜色的实现&#xff1a; 游戏数据的初始化 加载音乐 图形窗口的设置&#xff1a; 蛇身节数…

【动手学深度学习】多层感知机之暂退法问题研究详情

目录 &#x1f30a;问题研究1 &#x1f31e;问题研究2 &#x1f332;问题研究3 &#x1f30d;问题研究4 &#x1f333;问题研究5 &#x1f30c;问题研究6 &#x1f30a;问题研究1 如果更改第一层和第二层的暂退法概率&#xff0c;会发生什么情况&#xff1f;具体地说&am…

深入理解指针(4)--新手小白都能明白的指针解析

深入理解指针(4)–新手小白都能明白的指针解析 文章目录 深入理解指针(4)--新手小白都能明白的指针解析1. 回调函数2. qsort使用举例2.1 冒泡排序2.2 qsort函数介绍2.3 用冒泡排序实现qsort 结语 1. 回调函数 回调函数就是⼀个通过函数指针调用的函数 如果我们把函数的指针&a…

玩转微服务-GateWay

目录 一. 背景二. API网关1. 概念2. API网关定义3. API网关的四大职能4. API网关分类5. 开源API网关介绍6. 开源网关的选择 三. Spring Cloud Gateway1. 文档地址2. 三个核心概念3. 工作流程4. 运行原理4.1 路由原理4.2 RouteLocator 5. Predicate 断言6. 过滤器 Filter6.1. 过…

[图解]建模相关的基础知识-02

1 00:00:01,530 --> 00:00:05,200 第2个概念&#xff0c;谓词&#xff0c;Predicate 2 00:00:07,530 --> 00:00:10,800 或者叫断言&#xff0c;翻译各种各样都有 3 00:00:12,830 --> 00:00:15,050 实际上就是前面命题 4 00:00:15,060 --> 00:00:16,610 相当于常…

记录Nuxt 3 官网项目的一次部署

本来以为就是一次简单的部署&#xff0c;之前也是部署过几次nuxt项目了&#xff0c;所以&#xff0c;并没有要记录的想法。但是过程出现了很多问题&#xff0c;最后考虑还是写下来吧。留个记录&#xff08;完整的配置部署过程&#xff09; 这里我将要说明两种部署方式以供选择&…

开源网安软件安全国产化替代解决方案亮相2024澳门万讯论坛

近日&#xff0c;2024万讯论坛在澳门成功举办。本次论坛由万讯电脑科技主办&#xff0c;旨在引进国内尖端科技厂商&#xff0c;提供全方位的信创解决方案&#xff0c;分享信创化过程中所面临的挑战及阶段性转换经验。开源网安作为拥有软件安全领域全链条产品的厂商&#xff0c;…

Analytical Model(分析模型)和Compact model(紧凑模型)有什么不同

Analytical Model&#xff08;分析模型&#xff09; 和 Compact Model&#xff08;紧凑模型&#xff09; 在电子工程和半导体物理领域有着不同的应用和特点&#xff1a; Analytical Model&#xff08;分析模型&#xff09;: 理论基础&#xff1a;分析模型基于物理原理和数学公…

jeecg dictText字典值

前端列表的字典值回显&#xff0c;配置了数据字典后&#xff0c;在本地测试可以回显中文的数据&#xff0c; 但在线上服务器不能正常回显出来&#xff1b; 原因是在前端拿到records的列表值时可以拿到dictText的字典&#xff0c;但是线上服务器没有dictText的值&#xff1b; …

聚焦 Navicat 17 新特性 | 模型设计优化与创新

随着 Navicat 17 的正式发布&#xff0c;受到了广泛的关注和讨论。Navicat 产品力又一次大跃迁。新引入的特性显著增强了用户的数据库管理和数据分析体验&#xff0c;包括&#xff1a;模型设计与同步、数据字典、数据分析&#xff08;data profiling&#xff09;、用户体验、查…

共享门店模式:快速打造连锁实体店

在数字化浪潮的冲击下&#xff0c;许多线下实体店正面临前所未有的挑战。然而&#xff0c;在这个变革的时代&#xff0c;共享门店模式&#xff0c;也被称为“共享股东”&#xff0c;正以其独特的魅力&#xff0c;为实体店带来新的生机。 一、共享门店模式的崭新定义 共享门店…

​水经微图Web版1.8.0发布

让每一个人都有自己的地图&#xff01; 水经微图&#xff08;简称“微图”&#xff09;新版已上线&#xff0c;在该版本中主要新增了注册登录功能&#xff0c;线与面图层新增矩形、圆或军标等绘制功能&#xff0c;以及其它功能的优化。 现在&#xff0c;为你分享一下本轮迭代…

PostgreSQL调优工具:PGTune

PostgreSQL调优工具&#xff1a;PGTune 1&#xff0c;PGTune网址 https://pgtune.leopard.in.ua/#/ 参数解释&#xff1a; DB Version&#xff1a;数据库版本 OS Type&#xff1a;操作系统 DB Type&#xff1a;数据库类型&#xff0c;一般默认即可 Total Memory (RAM)&#x…

巨详细Linux安装MySQL

巨详细Linux安装MySQL 1、查看是否有自带数据库或残留数据库信息1.1检查残留mysql1.2检查并删除残留mysql依赖1.3检查是否自带mariadb库 2、下载所需MySQL版本&#xff0c;上传至系统指定位置2.1创建目录2.2下载MySQL压缩包 3、安装MySQL3.1创建目录3.2解压mysql压缩包3.3安装解…

2 - 力扣高频 SQL 50 题(基础版)

2.寻找用户推荐人 考点: sql里面的不等于&#xff0c;不包含null -- null 用数字判断筛选不出来 select name from Customer where referee_id !2 OR referee_id IS NULL;

UML行为图-状态图

概述 创建 UML 状态图的目的是研究类、角色、子系统或组件的实时行为。状态图不仅可用于描述用户接口、设备控制器和其他具有反馈的子系统&#xff0c;还可用于描述在生命期中跨越多个不同性质阶段的被动对象的行为&#xff0c;在每一阶段该对象都有自己特殊的行为。 一、状态…

2024最全软件测试面试八股文(答案+文档+视频讲解)

Part1 1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自…

Python-3.12.0文档解读-内置函数zip()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 基本用法 示例 特性 高级用法 注意事项 版本更新 示例代码 记忆策略…

UI的学习(一)

UI的学习(一) 文章目录 UI的学习(一)UIlabelUIButtonUIButton的两种形式UIButton的事件触发 UIView多个视图之间的关系 UIWindowUIViewController一个视图推出另一个视图 定时器和视图移动UISwitchUISlider和UIProgressSlid步进器与分栏控制器UITextFieldUIScrollView有关实现它…

个人笔记-随意记录

常见问题&#xff1f; 1.linux重启服务 端口被占用如何解决&#xff1f; 查看某个端口被占用的进程 netstat -tulnp | grep :23454 强制杀死进程 kill -9 1776 重启服务即可