一 InputDispatcher 起点
上篇文章 Android Input系统3 InputReader线程,我们介绍了 InputReader 利用 EventHub 获取数据后生成 EventEntry 事件,然后加入到 InputDispatcher 的 mInboundQueue 队列,再唤醒 InputDispatcher 线程。本文将介绍 InputDispatcher,同样从 threadLoop 为起点开始分析。
1.1 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);
}
该方法主要工作:
- 创建属于自己线程的 Looper 对象
- 超时参数来自于 IMS,参数默认值 keyRepeatTimeout = 500,keyRepeatDelay = 50。
InputDispatcher.cpp
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
整个过程就是不断循环地调用 InputDispatcher 的 dispatchOnce() 来分发事件。
1.2 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 等待状态后,当发生以下任一情况则退出休眠状态:
- timeout:到达 nextWakeupTime 时间,超时唤醒
- wake: 主动调用 Looper 的 wake() 方法(有输入事件注入派发队列中时)
- epol_wait() 监听的 fd 有 epoll_event 发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)
熟悉 Looper,handler 机制的同学应该很清楚这个函数的作用,它会让当前线程进入睡眠状态,核心原理是采用 epoll 机制对指定 fd 进行监听,对于 InputDispatcherThread 线程来说,主要监听了两个 fd,一个是 Looper 创建时就会默认添加到监听的 mWakeEventFd,另一个是后面要分析的 InputChannel 机制的 fd。关于 Looper 机制,同学们可以参考 Android消息机制2 (Native层)。
dispatchOnce 小结:
派发线程的一次循环包括如下三项工作:
- 进行一次事件派发。事件的派发工作仅当命令队列中没有命令时才会进行。派发工作会设置 nextWakeupTime,指明随后休眠时间的长短
- 执行命令列表中的命令。所谓的命令,不过是一个符合 command 签名的回调函数,可以通过 InputDispatcher::postCommandLocked() 函数将其追加到命令列表中
- 陷入休眠状态
可见派发线程的线程循环是比较清晰的,不过读者可能对 nextWakeupTime 的存在意义有一些费解。它对派发线程的执行过程有着举足亲重的作用。假如,当派发队列中最后一个事件派发完成后,nextWakeupTime 将被设置为 LONG_LONG_MAX,使之在新的输入事件或命令到来前休眠以节约资源。另外,有时因为窗口尚未准备好接受事件(如已经有一个事件发送给窗口,但此窗口尚未对其进行反馈),则可以放弃此事件的派发并设置 nextWakeupTime 为一个合理的时间点,以便在下次循环时再尝试派发。
二 InputDispatcher事件分发
接下来看输入事件分发的核心函数 dispatchOnceInnerLocked。
2.1 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,