系列文章目录
目录
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.11 prepareDispatchCycleLocked
2.12 enqueueDispatchEntriesLocked
2.13 enqueueDispatchEntryLocked
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的最大值:1844674407370955161
nextWakeupTime = 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派发策略时是false
break;
}
default:
break;
}
if (done) {//无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略时是false
mLastDropReason = 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_POLICY
if (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 (runCom