上篇文章 Android Input系统3 InputReader线程,我们介绍了 InputReader 利用 EventHub 获取数据后生成 EventEntry 事件,然后加入到 InputDispatcher 的 mInboundQueue 队列,再唤醒 InputDispatcher 线程。本文将介绍 InputDispatcher,同样从 threadLoop 为起点开始分析。
先来回顾一下 InputDispatcher 对象的初始化过程:
InputDispatcher.cpp
InputDispatcher::InputDispatcher(
const sp<InputDispatcherPolicyInterface>& policy) :
mPolicy(policy),
mPendingEvent(nullptr), mLastDropReason(DROP_REASON_NOT_DROPPED),
mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
mNextUnblockedEvent(nullptr),
mDispatchEnabled(false), mDispatchFrozen(false),
mInputFilterEnabled(false),
mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
// 创建 Looper 对象
mLooper = new Looper(false);
......
// 获取分发超时参数
// policy 是 NativeInputManager,最终回调到 IMS
policy->getDispatcherConfiguration(&mConfig);
}
该方法主要工作:
InputDispatcher.cpp
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
整个过程就是不断循环地调用 InputDispatcher 的 dispatchOnce() 来分发事件。
InputDispatcher.cpp
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{
......
if (!haveCommandsLocked()) {
// 当 mCommandQueue 不为空时处理
// 通过 dispatchOnceInnerLocked() 进行输入事件的派发
// 其中的传出参数 nextWakeupTime 决定了下次派发线程循环的执行时间
dispatchOnceInnerLocked(&nextWakeupTime);
}
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
}
nsecs_t currentTime = now(); // 获取当前时间
// 计算 InputDispatcherThread 线程的睡眠时间
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis); // 进入 epoll_wait
}
haveCommandsLocked() 用于检查 InputDispatcher 的缓存队列 mCommandQueue 中是否有等待处理的命令,如果没有就会执行 dispatchOnceInnerLocked 函数,用来对输入事件进行分发。如果有命令需要处理,则跳过事件分发进行命令的处理操作。
bool InputDispatcher::haveCommandsLocked() const {
return !mCommandQueue.empty();
}
mCommandQueue 是一个存储 CommandEntry 结构体的队列,CommandEntry 是用来描述一条指令,是调度当前 input 事件的指令,构造 CommandEntry 时需要向其构造函数传递一个 Command 对象,这个 Command 其实是个函数指针:
typedef std::function<void(InputDispatcher&, CommandEntry*)> Command;
struct CommandEntry {
explicit CommandEntry(Command command);
......
}
如果 mCommandQueue 不为空,就会调用 runCommandsLockedInterruptible 函数,循环遍历执行 mCommandQueue 中的所有指令:
bool InputDispatcher::runCommandsLockedInterruptible() {
if (mCommandQueue.empty()) {
return false;
}
do {
std::unique_ptr<CommandEntry> commandEntry =
std::move(mCommandQueue.front());
mCommandQueue.pop_front();
Command command = commandEntry->command;
// commands are implicitly 'LockedInterruptible'
command(*this, commandEntry.get());
commandEntry->connection.clear();
} while (!mCommandQueue.empty());
return true;
}
即循环取出队列中的元素 CommandEntry,并执行其 command 函数,command 函数就是构造 CommandEntry 时传入的函数指针,至于 CommandEntry 是什么时候和如何构造的,我们下面会分析。
如果 mCommandQueue 队列为空,就会调用 dispatchOnceInnerLocked 函数,进行事件分发。
事件分发完毕后,最后调用 Looper 的 pollOnce 函数使 InputDispatcherThread 进入睡眠状态,并将它的最长的睡眠的时间设置为 timeoutMillis。
当有输入事件产生时,InputReader 就会将睡眠状态的 InputDispatcherThread 唤醒,然后会重新开始调用 InputDispatcher 的 dispatchOnce 方法,执行对输入事件的分发。就这样一直循环下去。
线程执行 Looper->pollOnce,进入 epoll_wait 等待状态后,当发生以下任一情况则退出休眠状态:
熟悉 Looper,handler 机制的同学应该很清楚这个函数的作用,它会让当前线程进入睡眠状态,核心原理是采用 epoll 机制对指定 fd 进行监听,对于 InputDispatcherThread 线程来说,主要监听了两个 fd,一个是 Looper 创建时就会默认添加到监听的 mWakeEventFd,另一个是后面要分析的 InputChannel 机制的 fd。关于 Looper 机制,同学们可以参考 Android消息机制2 (Native层)。
dispatchOnce 小结:
派发线程的一次循环包括如下三项工作:
可见派发线程的线程循环是比较清晰的,不过读者可能对 nextWakeupTime 的存在意义有一些费解。它对派发线程的执行过程有着举足亲重的作用。假如,当派发队列中最后一个事件派发完成后,nextWakeupTime 将被设置为 LONG_LONG_MAX,使之在新的输入事件或命令到来前休眠以节约资源。另外,有时因为窗口尚未准备好接受事件(如已经有一个事件发送给窗口,但此窗口尚未对其进行反馈),则可以放弃此事件的派发并设置 nextWakeupTime 为一个合理的时间点,以便在下次循环时再尝试派发。
接下来看输入事件分发的核心函数 dispatchOnceInnerLocked。
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
nsecs_t currentTime = now(); // 当前时间,也是后面 ANR 计时的起点
if (!mDispatchEnabled) { // 默认值为 false
resetKeyRepeatLocked(); // 重置上一个事件
}
// 分发被冻结, WMS 冻屏时会设置这个值,例如转屏
if (mDispatchFrozen) { // 默认值为 false
return; // 如果 InputDispatcher 被冻结,则不进行派发操作,直接返回
}
// 对于特殊按键 HOME, END_CALL, APP_SWITCH 增加了 0.5s 超时时间
// 超时之后会丢弃其他即将处理的按键事件。即 isAppSwitchDue 为 true 的情况
// 目的是优化 app 切换延迟,当切换超时,则抢占分发,丢弃其他所有即将要处理的事件
// 如果 isAppSwitchDue 为 true,说明没有及时响应 HOME 键等操作
bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
// 这样当 InputDispatcher 处理完分发事件后,会第一时间处理窗口切换操作
if (mAppSwitchDueTime < *nextWakeupTime) {
*nextWakeupTime = mAppSwitchDueTime;
}
// 如果还没有待分发的事件,去 mInboundQueue 中取出一个事件
if (!mPendingEvent) {
if (mInboundQueue.isEmpty()) {
if (isAppSwitchDue) {
// The inbound queue is empty so the app switch key we were waiting
// for will never arrive. Stop waiting for it.
resetPendingAppSwitchLocked(false);
isAppSwitchDue = false;
}
if (!mPendingEvent) {
// 如果 mInboundQueue 为空,并且没有待分发的事件,就 return
return;
}
} else {
// 取队列 mInboundQueue 头部的 EventEntry 赋值给 mPendingEvent
mPendingEvent = mInboundQueue.dequeueAtHead();
}
......
resetANRTimeoutsLocked(); // 重置 ANR 信息
}
bool done = false;
DropReason dropReason = DROP_REASON_NOT_DROPPED; // 默认值为不丢弃
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
// 当前事件不包含 POLICY_FLAG_PASS_TO_USER 的标志,则不合法,需要丢弃
dropReason = DROP_REASON_POLICY;
} else if (!mDispatchEnabled) {
// 当前事件被禁止分发,需要丢弃
dropReason = DROP_REASON_DISABLED;
}
......
switch (mPendingEvent->type) {// 判断事件的类型,有 key,motion 等
......
case EventEntry::TYPE_KEY: {
KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
if (isAppSwitchDue) {
if (isAppSwitchKeyEventLocked(typedEntry)) {
// 重置 mAppSwitchDueTime
resetPendingAppSwitchLocked(true);
isAppSwitchDue = false;
} else if (dropReason == DROP_REASON_NOT_DROPPED) {
// 没有及时响应窗口切换操作
// 由于 APP 切换需要丢弃非 HOME,ENDCALL,APP_SWITCH 之外的事件
dropReason = DROP_REASON_APP_SWITCH;
}
}
if (dropReason == DROP_REASON_NOT_DROPPED
&& isStaleEventLocked(currentTime, typedEntry)) {
// 当前处理事件的时间减去事件产生的时间大于 10s 也需要丢弃
dropReason = DROP_REASON_STALE;// 事件过期
}
if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DROP_REASON_BLOCKED;// 阻碍其他窗口获取事件
}
// 进一步分发按键事件,结果赋值给 done
// 无论是成功派发还是事件被丢弃,都返回 true,否则返回false
// 以便在下次循环时再次尝试此事件的派发
done = dispatchKeyLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break;
}
......
}
......
// 如果事件派发完成,则准备派发下一个事件
if (done) {
if (dropReason != DROP_REASON_NOT_DROPPED) {
// 最后事件还是被丢弃
dropInboundEventLocked(mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
releasePendingEventLocked(); // 释放 pending 事件 mPendingEvent 等
// 立即开始下一次循环。如果此时派发队列为空,
// 下次循环调用此函数时会保持 nextWakeupTime 为
// LONG_LONG_MAX 并直接返回,使得派发线程进入无限期休眠
*nextWakeupTime = LONG_LONG_MIN; // 强制立刻执行轮询
}
}
在 enqueueInboundEventLocked() 的过程中已设置 mAppSwitchDueTime 等于 eventTime 加上 500ms:
mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
dispatchOnceInnerLocked 主要功能小结:
1.如果派发队列 mInboundQueue 为空,则会使派发线程陷入无限期休眠状态
2.如果 mPendingEvent 为空,则从派发队列中取出一个并保存在 mPendingEvent 中
3.事件有可能因为某些原因被丢弃,被丢弃的原因保存在 dropReason 中
4.不同类型的事件使用不同的派发函数进行实际的派发动作。如 Motion 事件使用 dispatchMotionLocked() 函数进行派发,Key 事件使用 dispatchKeyLocked 函数进行派发
5.派发一个事件至少需要一次线程循环才能完成。是否在下次循环继续尝试此事件的派发由派发函数的返回值决定
6.事件的派发是串行的,在排队首的事件完成派发或被丢弃之前,不会对后续的事件进行派发。理解 InputDispatcher 的这一特点非常重要
派发一个事件至少需要一次线程循环才能完成的原因是:
事件的目标窗口有可能正在处理先前的一个输入事件,在窗口完成先前事件的处理并给予反馈之前,InputDispatcher 不会再向此窗口派发新事件。
其他说明:
1.mDispatchFrozen 用于决定是否冻结事件分发工作,如果冻结则不再往下执行。InputDispatcher 有三种状态,分别是正常状态、冻结状态和禁用状态,可以通过 InputDispatcher 的 setInputDispatchMode 函数来设置
2.当特定输入事件(HOME,END_CALL,APP_SWITCH)分发的时间点距离该事件加入 mInboundQueue 的时间超过 500ms,则认为 app 切换过期,即 isAppSwitchDue=true
3.执行完成 (done) 后的处理工作
另外,读者可能对此函数最后设置 nextWakeupTime 为 LONG_LONG_MIN 使派发线程立即进行下一次循环有一些疑问。如果此时派发队列为空,为什么不设置其为 LONG_LONG_MAX 使其进入无限期休眠状态呢?在这里先给读者提个醒,当派发队列为空时,派发线程可能需要在下次循环中生成重复按键事件,因此不能直接进入休眠。后面的内容会讨论这个话题。
void InputDispatcher::dropInboundEventLocked(EventEntry* entry,
DropReason dropReason) {
const char* reason;
switch (dropReason) {
case DROP_REASON_POLICY:
reason = "inbound event was dropped because the policy consumed it";
break;
case DROP_REASON_DISABLED:
if (mLastDropReason != DROP_REASON_DISABLED) {
ALOGI("Dropped event because input dispatch is disabled.");
}
reason = "inbound event was dropped because input" +
" dispatch is disabled";
break;
case DROP_REASON_APP_SWITCH:
ALOGI("Dropped event because of pending overdue app switch.");
reason = "inbound event was dropped because of pending "+
"overdue app switch";
break;
case DROP_REASON_BLOCKED:
ALOGI("Dropped event because the current application is not "+
"responding and the user "
"has started interacting with a different application.");
reason = "inbound event was dropped because the current"+
" application is not responding "
"and the user has started interacting with a different application";
break;
case DROP_REASON_STALE:
ALOGI("Dropped event because it is stale.");
reason = "inbound event was dropped because it is stale";
break;
default:
return;
}
switch (entry->type) {
case EventEntry::TYPE_KEY: {
CancelationOptions options(
CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
synthesizeCancelationEventsForAllConnectionsLocked(options);
break;
}
......
}
}
前面的代码介绍了因为 DispatcherPolicy 认为事件不应派发给用户,以及 InputDispatcher 被停用而导致事件被丢弃。DropReason 枚举完整地描述了事件被丢弃的所有原因。
当事件幸运地避开所有上述原因之后,才能由 InputDispatcher 尝试派发。对 Key 事件来说,下一步是 dispatchKeyLocked() 函数。在这个函数中,InputDispatcher 将为事件寻找合适的目标窗口。
关于 dispatchKeyLocked 分发事件:
1.不会执行 done 的情况:
2.会执行 done 的情况:
下面会以按键为例来展开说明 dispatchKeyLocked。
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
......
if (entry->interceptKeyResult ==
KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
// case1: 当前时间小于唤醒时间,则进入等待状态
if (currentTime < entry->interceptKeyWakeupTime) {
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
return false; //直接返回 wait until next wakeup
}
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
entry->interceptKeyWakeupTime = 0;
}
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
//case2: 让 policy 有机会执行拦截操作
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
sp<InputWindowHandle> focusedWindowHandle =
getValueByKey(mFocusedWindowHandlesByDisplay, getTargetDisplayId(entry));
if (mFocusedWindowHandle != nullptr) {
commandEntry->inputChannel =
getInputChannelLocked(focusedWindowHandle->getToken());
}
commandEntry->keyEntry = entry;
entry->refCount += 1;
return false; //直接返回 wait for the command to run
} else {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
}
} else if (entry->interceptKeyResult ==
KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
if (*dropReason == DROP_REASON_NOT_DROPPED) {
*dropReason = DROP_REASON_POLICY;
}
}
//case3: 如果需要丢弃该事件,则执行清理操作
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
mReporter->reportDroppedKey(entry->sequenceNum);
return true; //直接返回
}
// 目标窗口信息列表会存储在 inputTargets 中
Vector<InputTarget> inputTargets;
// case4: 寻找焦点
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
// 输入事件被挂起,说明找到了窗口并且窗口无响应
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false; //直接返回
}
setInjectionResultLocked(entry, injectionResult);
// 输入事件没有分发成功,说明没有找到合适的窗口
if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
return true; // 直接返回
}
// Add monitor channels from event's or focused display.
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(entry));
// 只有 injectionResult 是成功,才有机会执行分发事件
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
dispatchKeyLocked 函数,在以下场景中,有可能无法分发事件:
如果成功跳过以上所有 case,则会继续调用 dispatchEventLocked,继续进行事件的分发。
接上节,我们重点来讨论第二种,也是最常见的定制需求,即:policy 需要提前拦截事件的情况(通过 PhoneWindowManager 来拦截):
dispatchKeyLocked 函数中对于输入事件,并不是直接拿到就处理,而是首先为其构造 CommandEntry,并添加到 mCommandQueue 队列,然后此函数就返回了,返回 false 表明此事件并未处理完成。
我们前面说过 mCommandQueue 这个队列,它会在执行 dispatchOnce 的时候,首先判断这个队列是否为空,如果非空的话,就会调用 runCommandsLockedInterruptible 函数来处理,方法是遍历 mCommandQueue 中的每一个 CommandEntry ,并执行其内部的 Command 函数,这个 command 函数就是构造 CommandEntry 时传入的函数指针:doInterceptKeyBeforeDispatchingLockedInterruptible
,接下来会先调用这个函数判断按键事件是否被提前拦截:
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
CommandEntry* commandEntry) {
// 将 KeyEntry 转换为 KeyEvent
KeyEntry* entry = commandEntry->keyEntry;
KeyEvent event = createKeyEvent(*entry);
mLock.unlock();
android::base::Timer t;
sp<IBinder> token = commandEntry->inputChannel != nullptr
? commandEntry->inputChannel->getConnectionToken()
: nullptr;
// 这里会通过 JNI 调到 java 层 PhoneWindowManager 中去提前处理按键
nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(token, &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) {
// 按键将被拦截,不会发到应用窗口
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
} else if (!delay) {
// 按键会接着立即执行
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
} else {
//按键延时执行
entry->interceptKeyResult =
KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
entry->interceptKeyWakeupTime = now() + delay;
}
entry->release();
}
上面函数会将按键在发给应用窗口之前先将按键发给 java 层 PhoneWindowManager 的 interceptKeyBeforeDispatching 方法,这就给了我们提前拦截按键的机会,如果有需求禁用 HOME,RECENT,BACK 就可以到 interceptKeyBeforeDispatching 方法中添加相应 case,并返回 -1 就行了。
此函数有三个结果,1. 完全拦截按键,2. 按键立即执行,3. 按键延时执行。
按键的提前拦截执行完之后,再次循环执行到 dispatchKeyLocked 函数之时,就可以根据 PhoneWindowManager 提前拦截的结果进行下一步处理了,如果 entry->interceptKeyResult 不等于 INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER 和 INTERCEPT_KEY_RESULT_SKIP,就会接着调用 findFocusedWindowTargetsLocked 函数寻找按键分发的焦点窗口:
要理解 findFocusedWindowTargetsLocked,需要先明白 InputTarget 结构体。
struct InputTarget {
enum {
// 此标记表示事件正在交付给前台应用程序
FLAG_FOREGROUND = 1 << 0,
//此标记指示 MotionEvent 位于目标区域内
FLAG_WINDOW_IS_OBSCURED = 1 << 1,
......
};
// inputDispatcher 与目标窗口的通信管道
sp<InputChannel> inputChannel; //1
// 事件派发的标记
int32_t flags;
// 屏幕坐标系相对于目标窗口坐标系的偏移量
float xOffset, yOffset;//2
// 屏幕坐标系相对于目标窗口坐标系的缩放系数
float scaleFactor;//3
BitSet32 pointerIds;
}
InputTarget 结构体可以说是 InputDispatcher 与目标窗口的转换器,存储了目标窗口的所有信息,其分为两大部分,一部分是枚举中存储的 InputDispatcher 与目标窗口交互的标记,另一部分是 InputDispatcher 与目标窗口交互参数,比如 flags,inputChannel 等。
inputChannel 本质上是一个 SocketPair,SocketPair 用于进程间双向通信,这非常适合 InputDispatcher 与目标窗口之间的通信,因为 InputDispatcher 不仅要将事件分发到目标窗口,同时 InputDispatcher 也需要得到目标窗口对事件的响应更新。其中的 xOffset 和 yOffset,是屏幕坐标系相对于目标窗口坐标系的偏移量,MotionEntry (MotionEvent) 中的存储的坐标是屏幕坐标系,因此就需要一些参数,来将屏幕坐标系转换为目标窗口的坐标系。
接下来看 findFocusedWindowTargetsLocked:
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry* entry, Vector<InputTarget>& inputTargets,
nsecs_t* nextWakeupTime) {
int32_t injectionResult;
std::string reason;
int32_t displayId = getTargetDisplayId(entry);
// 首先找到焦点窗口,关于焦点窗口如何得来的,以后有时间单独分析
sp<InputWindowHandle> focusedWindowHandle =
getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
// 找到焦点应用程序,由 java 层 InputMonitor 传下来
sp<InputApplicationHandle> focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
// If there is no currently focused window and no focused application
// then drop the event.
if (focusedWindowHandle == nullptr) {
if (mFocusedApplicationHandle != nullptr) {
// 可能发生 anr,使用 handleTargetsNotReadyLocked 判断
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, nullptr, nextWakeupTime,
"Waiting because no window has focus but there is a "
"focused application that may eventually add a window "
"when it finishes starting up.");
goto Unresponsive;
}
ALOGI("Dropping event because there is no focused" +
" window or focused application in display ");
injectionResult = INPUT_EVENT_INJECTION_FAILED;
goto Failed;
}
// 权限检查
if (! checkInjectionPermission(focusedWindowHandle,
entry->injectionState)) {
injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
goto Failed;
}
// 检测窗口是否为更多的输入操作而准备就绪
// Check whether the window is ready for more input.
reason = checkWindowReadyForMoreInputLocked(currentTime,
focusedWindowHandle, entry, "focused");
if (!reason.empty()) {
injectionResult = handleTargetsNotReadyLocked(currentTime,
entry, mFocusedApplicationHandle, focusedWindowHandle,
nextWakeupTime, reason.string());
goto Unresponsive;
}
// Success! Output targets.
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
// 成功找到目标窗口,添加到目标窗口
addWindowTargetLocked(focusedWindowHandle,
InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
BitSet32(0), inputTargets);
// Done.
Failed:
Unresponsive:
// 统计等待时长信息,目前没有实现,这个方法还是很值得去改造
nsecs_t timeSpentWaitingForApplication =
getTimeSpentWaitingForApplicationLocked(currentTime);
updateDispatchStatisticsLocked(currentTime, entry,
injectionResult, timeSpentWaitingForApplication);
return injectionResult;
}
findFocusedWindowTargetsLocked
的主要作用是对按键事件是否能够成功分发做出条件判断,我们总结一下此函数:
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
const EventEntry* entry,
const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle,
nsecs_t* nextWakeupTime, const char* reason) {
if (applicationHandle == nullptr && windowHandle == nullptr) {
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
mInputTargetWaitStartTime = currentTime; // 当前时间
mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
mInputTargetWaitTimeoutExpired = false;
mInputTargetWaitApplicationHandle.clear();
}
} else {
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
nsecs_t timeout;
if (windowHandle != nullptr) {
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else if (applicationHandle != nullptr) {
timeout = applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else {
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; // 5s
}
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
// 这里的 currentTime 是执行 dispatchOnceInnerLocked 方法的起点时间
mInputTargetWaitStartTime = currentTime;
mInputTargetWaitTimeoutTime = currentTime + timeout;
mInputTargetWaitTimeoutExpired = false;
mInputTargetWaitApplicationHandle.clear();
if (windowHandle != nullptr) {
mInputTargetWaitApplicationToken = windowHandle->getApplicationToken();
}
if (mInputTargetWaitApplicationToken == nullptr && applicationHandle != nullptr) {
mInputTargetWaitApplicationToken = applicationHandle->getApplicationToken();
}
}
}
if (mInputTargetWaitTimeoutExpired) {
return INPUT_EVENT_INJECTION_TIMED_OUT; // 等待超时已过期,则直接返回
}
// 当超时 5s,则进入 ANR 流程
if (currentTime >= mInputTargetWaitTimeoutTime) {
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
*nextWakeupTime = LONG_LONG_MIN; // 强制立刻执行轮询来执行 ANR 策略
return INPUT_EVENT_INJECTION_PENDING;
} else {
if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
*nextWakeupTime = mInputTargetWaitTimeoutTime; // 当触发超时则强制执行轮询
}
return INPUT_EVENT_INJECTION_PENDING;
}
}
ANR 超时时间点为 mInputTargetWaitTimeoutTime,该值等于 currentTime + 5s,这里的 currentTime 是指执行 dispatchOnceInnerLocked 方法体的起点。
此处设置 mInputTargetWaitCause 等于 INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY (应用没有准备就绪),而前面 resetANRTimeoutsLocked() 过程是唯一用于重置该变量的地方。
可见,ANR 时间区间是从 dispatchOnceInnerLocked 方法体的起点,直到下次执行 handleTargetsNotReadyLocked() 方法的这段应用未准备就绪的时间段,该时间段是否超过 5s 来决定是否触发 ANR。
当前这次的事件 dispatch 过程中执行 findFocusedWindowTargetsLocked() 方法到下一次执行 resetANRTimeoutsLocked() 的时间区间。
handleTargetsNotReadyLocked
() 的判断过程:
1.当 applicationHandle 和 windowHandle 同时为空,且 mInputTargetWaitCause 没有被标记为 NOT_READY(判断为系统没有 ready)
2.当 applicationHandle 和 windowHandle 至少一个不为空,且 mInputTargetWaitCause 没有被标记为 NOT_READY
继续回到 findFocusedWindowTargetsLocked,查看 checkWindowReadyForMoreInputLocked
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
const char* targetType) {
// 当窗口暂停的情况,则保持等待
if (windowHandle->getInfo()->paused) {
return String8::format("Waiting because the %s window is paused.", targetType);
}
// 当窗口连接未注册,则保持等待
ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
if (connectionIndex < 0) {
return String8::format("Waiting because the %s window's input channel is not "
"registered with the input dispatcher. The window may be in the process "
"of being removed.", targetType);
}
// 当窗口连接已死亡,则保持等待
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
if (connection->status != Connection::STATUS_NORMAL) {
return String8::format("Waiting because the %s window's input connection is %s."
"The window may be in the process of being removed.", targetType,
connection->getStatusLabel());
}
// 当窗口连接已满,则保持等待
if (connection->inputPublisherBlocked) {
return String8::format("Waiting because the %s window's input channel is full. "
"Outbound queue length: %d. Wait queue length: %d.",
targetType, connection->outboundQueue.count(), connection->waitQueue.count());
}
if (eventEntry->type == EventEntry::TYPE_KEY) {
// 按键事件,输出队列或事件等待队列不为空
if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
return String8::format("Waiting to send key event because the %s window has not "
"finished processing all of the input events that were previously "
"delivered to it. Outbound queue length: %d. Wait queue length: %d.",
targetType, connection->outboundQueue.count(), connection->waitQueue.count());
}
} else {
// 非按键事件,事件等待队列不为空且头事件分发超时 500ms
if (!connection->waitQueue.isEmpty()
&& currentTime >= connection->waitQueue.head->deliveryTime
+ STREAM_AHEAD_EVENT_TIMEOUT) {
return String8::format("Waiting to send non-key event because the %s window has not "
"finished processing certain input events that were delivered to it over "
"%0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms.",
targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
connection->waitQueue.count(),
(currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
}
}
return String8::empty();
}
继续回到 findFocusedWindowTargetsLocked,如果没有发生 ANR,则会通过 addWindowTargetLocked() 将该事件的目标窗口添加到 inputTargets 列表中。
我们来看 addWindowTargetLocked 函数:
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
sp<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
if (inputChannel == nullptr) {
ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
return;
}
const InputWindowInfo* windowInfo = windowHandle->getInfo();
// 构建 InputTarget 添加到 inputTargets 中
InputTarget target;
target.inputChannel = inputChannel;
target.flags = targetFlags;
target.xOffset = - windowInfo->frameLeft;
target.yOffset = - windowInfo->frameTop;
target.globalScaleFactor = windowInfo->globalScaleFactor;
target.windowXScale = windowInfo->windowXScale;
target.windowYScale = windowInfo->windowYScale;
target.pointerIds = pointerIds;
inputTargets.push_back(target);
}
构建一个 InputTarget 对象 target,并将当前焦点窗口 focusedWindowHandle 的所有信息添加到 target 中,然后把 target 添加到 inputTargets 中,需要重点关注 inputChannel。
至此,findFocusedWindowTargetsLocked 函数分析完毕,也就是检查是否发生 ANR,同时找到目标窗口后,把窗口信息用 InputTarget 来表示,并添加到 inputTargets 中。
接下来回到 dispatchKeyLocked 函数中,最后调用 dispatchEventLocked 继续来看事件的分发。
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
// 向 mCommandQueue 队列添加 doPokeUserActivityLockedInterruptible 命令
pokeUserActivityLocked(eventEntry);
for (const InputTarget& inputTarget : inputTargets) {
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
// 找到目标连接
prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
}
}
}
这里会遍历 inputTargets,并调用 prepareDispatchCycleLocked 函数继续事件的分发。
需要重点关注参数 connection,它是用来描述窗口的连接,每一个窗口对应一个连接,这个连接就是用来将输入事件传递到目标窗口,connection 是在 registerInputChannel 的时候创建的,可以参考 Android Input系统5 UI线程。
其中 pokeUserActivityLocked(eventEntry) 方法,最终会调用到 Java 层的 PowerManagerService.java 中的 userActivityFromNative() 方法。 这也是 PMS 中唯一的 native call 方法。
ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
if (inputChannel == nullptr) {
return -1;
}
for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
sp<Connection> connection = mConnectionsByFd.valueAt(i);
if (connection->inputChannel->getToken() == inputChannel->getToken()) {
return i;
}
}
return -1;
}
根据 inputChannel 的 fd 从 mConnectionsByFd 队列中查询目标 connection。
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
if (connection->status != Connection::STATUS_NORMAL) {
return; //当连接已破坏,则直接返回
}
......
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
bool wasEmpty = connection->outboundQueue.isEmpty();
// 调用了六次 enqueueDispatchEntryLocked 函数,仅仅最后一个参数不同
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);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
if (wasEmpty && !connection->outboundQueue.isEmpty()) {
// 当原先的 outbound 队列为空, 且当前 outbound 不为空的情况执行
startDispatchCycleLocked(currentTime, connection);
}
}
该方法主要功能:
void InputDispatcher::enqueueDispatchEntryLocked(
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
int32_t dispatchMode) {
int32_t inputTargetFlags = inputTarget->flags;
if (!(inputTargetFlags & dispatchMode)) {
return; // 分发模式不匹配,则直接返回
}
inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
// 将 EventEntry 转换为 DispatchEntry, 加入 connection 的 outbound 队列
DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry,
inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
inputTarget->scaleFactor);
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
dispatchEntry->resolvedAction = keyEntry->action;
dispatchEntry->resolvedFlags = keyEntry->flags;
if (!connection->inputState.trackKey(keyEntry,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
delete dispatchEntry;
return; // 忽略不连续的事件
}
break;
}
case EventEntry::TYPE_MOTION: {
......
}
......
}
......
// 添加到 connection 的 outboundQueue 队尾
// Enqueue the dispatch entry.
connection->outboundQueue.enqueueAtTail(dispatchEntry);
}
enqueueDispatchEntryLocked
主要功能:
执行到这里,其实等于做了一次搬运的工作,将 InputDispatcher 中 mInboundQueue 中的事件取出后,找到目标 window 后,封装成 dispatchEntry 加入到目标窗口对应的 connection 中的 outboundQueue 队列。
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->deliveryTime = currentTime; // 设置 deliveryTime 时间
status_t status;
EventEntry* eventEntry = dispatchEntry->eventEntry;
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
// 核心动作:发布 Key 事件
status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
keyEntry->deviceId, keyEntry->source,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
keyEntry->keyCode, keyEntry->scanCode,
keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
keyEntry->eventTime);
break;
}
......
}
// Check the result.
if (status) { // publishKeyEvent 失败情况
if (status == WOULD_BLOCK) {
if (connection->waitQueue.isEmpty()) {
// pipe 已满,但 waitQueue 为空. 不正常的行为
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 {
// 不不正常的行为
abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
}
return;
}
// 从 outboundQueue 中取出事件,重新放入 waitQueue 队列
connection->outboundQueue.dequeue(dispatchEntry);
connection->waitQueue.enqueueAtTail(dispatchEntry);
}
}
startDispatchCycleLocked
说明:
1.startDispatchCycleLocked 触发时机:当起初 connection.outboundQueue 等于空,经 enqueueDispatchEntryLocked 处理后,outboundQueue 不等于空。
2.startDispatchCycleLocked 主要功能:将目标窗口 connection 的 outboundQueue 队列中的 dispatchEntry 依次取出来,对于按键类型的输入事件会调用 connection->inputPublisher 的 publishKeyEvent 进行分发,分发成功之后会将 outboundQueue 队列中的 dispatchEntry 移除,并转移到 waitQueue 中。
3.publishKeyEvent 执行结果 status 不等于 OK 的情况下:
status_t InputPublisher::publishKeyEvent(...) {
......
// 拿到输入事件的各种信息之后构造一个 InputMessage
InputMessage msg;
msg.header.type = InputMessage::TYPE_KEY;
msg.body.key.seq = seq;
msg.body.key.deviceId = deviceId;
msg.body.key.source = source;
msg.body.key.action = action;
msg.body.key.flags = flags;
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;
// 通过 InputChannel 来发送消息
return mChannel->sendMessage(&msg);
}
接下来看 InputChannel 的 sendMessage:
status_t InputChannel::sendMessage(const InputMessage* msg) {
const size_t msgLength = msg->size();
InputMessage cleanMsg;
msg->getSanitizedCopy(&cleanMsg);
ssize_t nWrite;
do {
nWrite = ::send(mFd.get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
if (nWrite < 0) {
//异常情况,发送失败
return -error;
}
if (size_t(nWrite) != msgLength) {
return DEAD_OBJECT;
}
return OK;
}
sendMessage 的核心就是向 mFd 写入数据,mFd 是什么呢?mFd 其实就是一对 socket 的其中一个,InputChannel 在构造的时候是一对,对应了一对 socket,一个代表 “client” 端,一个代表 “server” 端,“server” 端被注册到了 InputDispatcher,“client” 端返回给了 APP 进程,InputDispatcher 和 APP 进程都会对自己的 socket 一端进行监听,所以 APP 进程和 InputDispatcher 就这样完成了通信。
socket 通道是如何建立的呢? InputDispatcher 又是如何与前台的 window 通信的呢? 请参考 Android Input系统5 UI线程。
InputDispatcher 对事件的分发流程是相当复杂的,我们仅对最简单的按键类型事件进行分析,同时分析过程省略了对细节的处理,我们做的是把握整体架构,现在就对这个过程做一个总结:
整个事件传输过程中,有三个重要队列:mInboundQueue,outboundQueue,waitQueue。
mInboundQueue 位于 InputDispatcher 线程,代表即将分发的输入事件,outboundQueue 位于目标窗口的 connection 中,代表即将要分发给目标窗口的输入事件,waitQueue 也位于目标窗口的 connection 中,表示等待目标窗口处理的输入事件。