前言
前面一篇文章剖析了 InputReader 对按键工作的流程流程,大致上便是依据配置文件把按键的扫描码(scan code)转换为按键码(key code),而且同时会从配置文件中获取战略标志位(policy flag),用于操控按键的行为,例如亮屏。然后把按键工作进行包装,分发给 InputDispatcher。本文就接着来剖析 InputDispatcher 对按键工作的处理。
1. InputDispatcher 收到工作
从前面一篇文章可知,InputDispatcher 收到的按键工作的来源如下
void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,
int32_t usageCode) {
// ...
// 按键工作包装成 NotifyKeyArgs
NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
getDisplayId(), policyFlags,
down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
// 参加到 QueuedInputListener 缓存行列中
getListener()->notifyKey(&args);
}
InputReader 把按键工作交给 KeyboardInputMapper 处理,KeyboardInputMapper 把按键工作包装成 NotifyKeyArgs,然后参加到 QueuedInputListener 的缓存行列。
然后,当 InputReader 处理完一切工作后,会改写 QueuedInputListener 的缓存行列,如下
void InputReader::loopOnce() {
// ...
{ // acquire lock
// ...
if (count) {
// 处理工作
processEventsLocked(mEventBuffer, count);
}
// ...
} // release lock
// ...
// 改写缓存行列
mQueuedListener->flush();
}
QueuedInputListener 会把缓存行列中的一切工作,分发给 InputClassifier
// framework/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::flush() {
size_t count = mArgsQueue.size();
for (size_t i = 0; i < count; i++) {
NotifyArgs* args = mArgsQueue[i];
args->notify(mInnerListener);
delete args;
}
mArgsQueue.clear();
}
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
// 交给 InputClassifier
listener->notifyKey(this);
}
InputClassifier 收到 NotifyKeyArgs 工作后,其实什么也没做,就交给了 InputDispatcher
void InputClassifier::notifyKey(const NotifyKeyArgs* args) {
// 直接交给 InputDispatcher
mListener->notifyKey(args);
}
现在了解了按键工作的来源,接下来剖析 InputDispatcher 怎样处理按键工作
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
// 检测 action,action 只能为 AKEY_EVENT_ACTION_DOWN/AKEY_EVENT_ACTION_UP
if (!validateKeyEvent(args->action)) {
return;
}
// 战略标志位,一般来源于配置文件
uint32_t policyFlags = args->policyFlags;
int32_t flags = args->flags;
int32_t metaState = args->metaState;
// InputDispatcher tracks and generates key repeats on behalf of
// whatever notifies it, so repeatCount should always be set to 0
constexpr int32_t repeatCount = 0;
if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
policyFlags |= POLICY_FLAG_VIRTUAL;
flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
}
if (policyFlags & POLICY_FLAG_FUNCTION) {
metaState |= AMETA_FUNCTION_ON;
}
// 来自 InputClassifier 的工作都是受信赖的
policyFlags |= POLICY_FLAG_TRUSTED;
int32_t keyCode = args->keyCode;
accelerateMetaShortcuts(args->deviceId, args->action, keyCode, metaState);
// 创立 KeyEvent,这个方针首要用于,在工作参加到 InputDispatcher 行列前,履行战略切断查询
KeyEvent event;
event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
args->action, flags, keyCode, args->scanCode, metaState, repeatCount,
args->downTime, args->eventTime);
android::base::Timer t;
// 1. 问询战略,在按键工作参加到 InputDispatcher 行列前,是否切断工作
// 假如上层不切断工作,policyFlags 增加 POLICY_FLAG_PASS_TO_USER,标明工作需求传递给用户
// 假如上层切断工作,那么不会增加 policyFlags 增加 POLICY_FLAG_PASS_TO_USER,工作终究不会传递给用户
mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
// 记载处理时刻
// 假如是Power键的切断处理时刻过长,那么亮屏或许灭屏或许会让用户感觉到有推迟
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
}
bool needWake;
{ // acquire lock
mLock.lock();
// 一般体系没有输入过滤器(input filter)
if (shouldSendKeyToInputFilterLocked(args)) {
// ...
}
// 创立 KeyEntry,这个方针是 InputDispatcher 用于分发循环的
std::unique_ptr<KeyEntry> newEntry =
std::make_unique<KeyEntry>(args->id, args->eventTime, args->deviceId, args->source,
args->displayId, policyFlags, args->action, flags,
keyCode, args->scanCode, metaState, repeatCount,
args->downTime);
// 2. 把 KeyEntry 参加到 InputDispatcher 的收件箱 mInboundQueue 中
needWake = enqueueInboundEventLocked(std::move(newEntry));
mLock.unlock();
} // release lock
// 3. 如有必要,唤醒 InputDispatcher 线程处理工作
if (needWake) {
mLooper->wake();
}
}
InputDispatcher 处理按键工作的进程如下
- 把按键工作包装成 KeyEvent 方针,然后查询切断战略,看看战略是否切断该工作。假如战略不切断,那么会在参数 policyFlags 增加 POLICY_FLAG_PASS_TO_USER 标志位,标明工作要发送给用户。 否则不会增加这个标志位,InputDispatcher 后边会丢掉这个工作,也便是不会分发给用户。参阅【1.1 切断战略查询】
- 把按键工作包装成 KeyEntry 方针,然后参加到 InputDispatcher 的收件箱 InputDispatcher::mInboundQueue。参阅【1.2 InputDispatcher 收件箱接纳工作】
- 如有必要,唤醒 InputDispatcher 线程处理工作。一般,InputDispatcher 线程处于休眠状况时,假如收到工作,那么需求唤醒线程来处理工作。
留意,这儿的一切操作,不是产生在 InputDispatcher 线程,而是产生在 InputReader 线程,这个线程是担任不断地读取工作,因而这儿的查询战略是否切断工作的进程,时刻不能太长,否则影响了输入体系读取工作。
别的,在履行完切断战略后,会记载处理的时长,假如时长超越必定的阈值,会收到一个正告信息。我从前听到其他项目的人在议论 power 键亮屏慢的问题,那么能够在这儿排查下。
1.1 切断战略查询
// framework/base/services/core/jni/com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
uint32_t& policyFlags) {
// ...
// 假如处于交互状况,policyFlags 增加 POLICY_FLAG_INTERACTIVE 标志位
bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
// 受信赖的按键工作,才会履行战略查询
// 来自 InputClassifier 的工作都是受信赖的
if ((policyFlags & POLICY_FLAG_TRUSTED)) {
nsecs_t when = keyEvent->getEventTime();
JNIEnv* env = jniEnv();
// 1. 创立上层的 KeyEvent 方针
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
jint wmActions;
if (keyEventObj) {
// 2. 调用上层的 InputManangerService#interceptMotionBeforeQueueingNonInteractive()
// 终究是经过 PhoneWindowManager 完结切断战略查询的
wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeQueueing,
keyEventObj, policyFlags);
if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
wmActions = 0;
}
android_view_KeyEvent_recycle(env, keyEventObj);
env->DeleteLocalRef(keyEventObj);
} else {
ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
wmActions = 0;
}
// 3. 处理战略查询的成果
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
} else {
// 不受信赖的工作,不会履行切断战略查询,而且只要在设备处于交互状况下,才干发送给用户
if (interactive) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
}
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
uint32_t& policyFlags) {
// 4. 假如战略不切断工作,那么在战略标志位 policyFlags 中增加 POLICY_FLAG_PASS_TO_USER 标志位
// 战略查询的成果中有 WM_ACTION_PASS_TO_USER 标志位,标明需求把工作传递给用户
if (wmActions & WM_ACTION_PASS_TO_USER) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
工作切断战略的查询进程,便是便是经过 JNI 调用上层 InputManagerService 的办法,而这个战略终究是由 PhoneWindowManager 完结的。假如战略不切断,假如战略不切断工作,那么在参数的战略标志位 policyFlags 中增加 POLICY_FLAG_PASS_TO_USER 标志位。
为何需求这个切断战略? 或许这样问,假如没有切断战略,那么会有什么问题呢? 假想咱们正在处于通话,此刻按下挂断电话按键,假如输入体系还有许多工作没有处理完,或许说,处理工作的时刻较长,那么挂断电话的按键工作不能得到及时处理,这就适当影响用户体会。而假如有了切断战略,在输入体系正式处理工作前,就能够处理挂断电话按键工作。
因而,切断战略的效果便是及时处理体系一些重要的功用。这给咱们一个什么提示呢?当硬件上增加了一个按键,假如想要快速呼应这个按键的工作,那么就在切断战略中处理。
关于切断战略,以及后边的分发战略,是一个比较好的课题,我会在后边一篇文章中详细剖析。
下面,了解几个概念
- 什么是交互状况?什么是非交互状况?简略了解,亮屏便是交互状况,灭屏便是非交互状况。可是,严格来说,并不精确,假如读者想知道详细的定义,能够查看我写的 PowerManagerService 的文章。
- 什么是受信赖的工作?来自物理输入设备的工作都是受信赖的,别的像 SystemUI ,由于恳求了 android.permission.INJECT_EVENTS 权限, 因而它注入的 BACK, HOME 按键工作也都是受信赖。
- 什么是注入工作?简略来说,不是由物理设备产生的工作。例如,导航栏上的 BACK, HOME 按键,它们的工作都是经过注入产生的,因而它们是注入工作。
1.2 InputDispatcher 收件箱接纳工作
bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
// mInboundQueue 行列为空,需求唤醒 InputDispatcher 线程来处理工作
bool needWake = mInboundQueue.empty();
// 参加到 mInboundQueue 中
mInboundQueue.push_back(std::move(newEntry));
EventEntry& entry = *(mInboundQueue.back());
traceInboundQueueLengthLocked();
switch (entry.type) {
case EventEntry::Type::KEY: {
// Optimize app switch latency.
// If the application takes too long to catch up then we drop all events preceding
// the app switch key.
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
if (isAppSwitchKeyEvent(keyEntry)) {
if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {
mAppSwitchSawKeyDown = true;
} else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
// app 切换按键抬起时,需求做如下工作
// 核算切换超时时刻
// 需求当即唤醒 InputDispatcher 线程来处理,由于这个工作很重要
if (mAppSwitchSawKeyDown) {
mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
mAppSwitchSawKeyDown = false;
// 需求唤醒线程,当即处理按键工作
needWake = true;
}
}
}
break;
}
// ...
}
// 回来值标明是否需求唤醒 InputDispatcher 线程
return needWake;
}
bool InputDispatcher::isAppSwitchKeyEvent(const KeyEntry& keyEntry) {
return !(keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) && isAppSwitchKeyCode(keyEntry.keyCode) &&
(keyEntry.policyFlags & POLICY_FLAG_TRUSTED) &&
(keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER);
}
// AKEYCODE_HOME 是 HOME 按键,AKEYCODE_APP_SWITCH 是 RECENTS 按键
static bool isAppSwitchKeyCode(int32_t keyCode) {
return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL ||
keyCode == AKEYCODE_APP_SWITCH;
}
InputDispatcher::mInboundQueue 是 InputDispatcher 的工作收件箱,一切的工作,包含注入工作,都会参加这个收件箱。
假如收件箱之前没有”邮件”,当接纳到”邮件”后,就需求唤醒 InputDispatcher 线程来处理”邮件”,这个逻辑很合理吧?
别的,聊一下这儿提到的 app switch 按键。从上面的代码可知,HOME, RECENT, ENDCALL 按键都是 app switch 按键。当 app switch 按键抬起时,会核算一个超时时刻,而且当即唤醒 InputDispatcher 线程来处理工作,由于这个工作很重要,需求及时处理,可是处理时刻也不能太长,因而需求设置一个超时时刻。
为何要给 app switch 按键设置一个超时时刻? 假如我在操作一个界面,此刻由于某些原因,例如 CPU 占用率过高,导致界面工作处理比较缓慢,也便是常说的卡顿现象。此刻我觉得这个 app 太渣了,想杀掉它,怎样办呢? 按下导航栏的 RECENT 按键,然后干掉它。可是由于界面处理工作比较缓慢,因而 RECENT 按键工作或许不能得到及时处理,这就会让我很恼火,我很或许丢掉这个手机。因而需求给 app switch 按键设置一个超时时刻,假如超时了,那么就会丢掉 app switch 按键之前的一切工作,来让 app switch 工作得到及时的处理。
2. InputDispatcher 处理按键工作
现在收件箱已 InputDispatcher::mInboundQueue 现已收到了按键工作,那么来看下 InputDisaptcher 线程怎样处理按键工作的。由前面的文章可知,InputDisaptcher 线程循环的代码如下
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// 1. 假如没有指令,分发一次工作
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// 2. 履行指令,而且当即唤醒线程
// 这个指令来自于前一步的工作分发
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
// 3. 处理 ANR ,并回来下一次线程唤醒的时刻。
const nsecs_t nextAnrCheck = processAnrsLocked();
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
if (nextWakeupTime == LONG_LONG_MAX) {
mDispatcherEnteredIdle.notify_all();
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
// 4. 线程休眠 timeoutMillis 毫秒
// 留意,休眠的进程或许会被打破
// 例如,窗口回来处理工作的成果时,会被唤醒
// 又例如,收件箱接纳到了工作,也会被唤醒
mLooper->pollOnce(timeoutMillis);
}
InputDispatcher 的一次线程循环,做了如下几件事
- 履行一次工作分发。其实便是从收件箱中获取一个工作进行分发。留意,此进程只分发一个工作。也便是说,线程循环一次,只处理了一个工作。参阅【2.1 分发工作】
- 履行指令。 这个指令是哪里来的呢?是上一步工作分发中产生的。工作在发送给窗口前,会履行一次分发战略查询,而这个查询的方法便是创立一个指令来履行。
- 处理 ANR,并回来下一次线程唤醒的时刻。窗口在接纳到工作后,需求在规则的时刻内处理,否则会产生 ANR。这儿运用 ANR 的超时时刻核算线程下次唤醒的时刻,以便能及时处理 ANR。
- 线程休眠。线程被唤醒有许多种或许,例如窗口及时地回来了工作的处理成果,或许窗口处理工作超时了。
2.1 分发工作
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
nsecs_t currentTime = now();
// Reset the key repeat timer whenever normal dispatch is suspended while the
// device is in a non-interactive state. This is to ensure that we abort a key
// repeat if the device is just coming out of sleep.
// 体系没有发动完结,或许正在关机,mDispatchEnabled 为 false
if (!mDispatchEnabled) {
// 重置生成重复按键的计时
resetKeyRepeatLocked();
}
// Activity 产生旋转时,会冻住
if (mDispatchFrozen) {
// 被冻住时,工作不会履行分发,比及被冻结后,再履行分发
return;
}
// 判断 app 切换是否超时
bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
// 假如下次线程唤醒的时刻大于app切换超时时刻,那么下次唤醒时刻需求重置为app切换超时时刻
// 以便处理app切换超时的问题
if (mAppSwitchDueTime < *nextWakeupTime) {
*nextWakeupTime = mAppSwitchDueTime;
}
// mPendingEvent 标明正在处理的工作
if (!mPendingEvent) {
if (mInboundQueue.empty()) { // 收件箱为空
// 收件箱为空,而且产生了 app 切换超时
// 也便是说,现在只要一个 app 切换按键工作,而且还超时了
// 处理这种状况很简略,便是重置状况即可,因而没有其他工作需求丢掉
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;
}
// Synthesize a key repeat if appropriate.
// 这儿处理的状况是,输入设备不支持重复按键的生成,那么当用户按下一个按键后,长时刻不松手,因而就需求组成一个重复按键工作
if (mKeyRepeatState.lastKeyEntry) {
if (currentTime >= mKeyRepeatState.nextRepeatTime) {
// 组成一个重复按键工作给 mPendingEvent
mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
} else {
if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
*nextWakeupTime = mKeyRepeatState.nextRepeatTime;
}
}
}
// app
// Nothing to do if there is no pending event.
// 假如此刻 mPendingEvent 仍是为 null,那么标明真的没有工作需求处理,
// 因而此次的分发循环就结束了
if (!mPendingEvent) {
return;
}
} else {
// 1. 从收件箱中取出工作
// mPendingEvent 标明正在处理的工作
mPendingEvent = mInboundQueue.front();
mInboundQueue.pop_front();
traceInboundQueueLengthLocked();
}
// 假如这个工作需求传递给用户,那么需求通知上层的 PowerManagerService,此刻有用户行为,
// 例如,按下音量键,能够延长亮屏的时刻
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(*mPendingEvent);
}
}
// Now we have an event to dispatch.
// All events are eventually dequeued and processed this way, even if we intend to drop them.
ALOG_ASSERT(mPendingEvent != nullptr);
bool done = false;
// 假如工作需求被丢掉,那么丢掉的原因保存到 dropReason
DropReason dropReason = DropReason::NOT_DROPPED;
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
// 工作被切断战略切断了
dropReason = DropReason::POLICY;
} else if (!mDispatchEnabled) {
// 体系没有发动完结,或许正在关机
dropReason = DropReason::DISABLED;
}
if (mNextUnblockedEvent == mPendingEvent) {
mNextUnblockedEvent = nullptr;
}
switch (mPendingEvent->type) {
// ...
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
if (isAppSwitchDue) { // app 切换超时
if (isAppSwitchKeyEvent(*keyEntry)) {
resetPendingAppSwitchLocked(true);
isAppSwitchDue = false;
} else if (dropReason == DropReason::NOT_DROPPED) {
// app switch 工作超时,导致工作被丢掉
dropReason = DropReason::APP_SWITCH;
}
}
// 按键工作产生在10秒之前,丢掉
if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *keyEntry)) {
dropReason = DropReason::STALE;
}
// mNextUnblockedEvent 与接触工作有关
// 举一个例子,假如有两个窗口,当第一个窗口无呼应时,假如用户此刻操作第二个窗口
// 体系需求及时把工作发送给第二个窗口,由于此刻第二个窗口是一个焦点窗口
// 那么就需求体系把无呼应窗口的工作丢掉,避免影响第二个窗口工作的分发
if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DropReason::BLOCKED;
}
// 2. 分发按键工作
done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
break;
}
// ...
}
// 3. 处理工作分发的成果
// done 为 true,有两种状况,一种是工作现已发送给指定窗口,二是工作现已被丢掉
// done 为 false,标明暂时不止怎样处理这个工作,组合键的第一个按键按下时,便是其间一种状况
if (done) {
// 处理工作被丢掉的状况
if (dropReason != DropReason::NOT_DROPPED) {
// 这儿处理的一种状况是,假如窗口收到 DOWN 工作,可是 UP 工作由于某种原因被丢掉,那么需求补发一个 CANCEL 工作
dropInboundEventLocked(*mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
// 无论工作发送给窗口,或许丢掉,都标明工作被处理了,因而重置 mPendingEvent
releasePendingEventLocked();
// 已然当时工作现已处理完,那么当即唤醒,处理下一个工作
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
}
InputDispatcher 线程的一次工作分发的进程如下
- 从收件箱取出工作。
- 分发工作。参阅【3. 按键工作的分发】
- 处理工作分发后的成果。 工作被丢掉或许发送给指定窗口,都会回来 true,标明工作被处理了,因而会重置 mPendingEvent。而假如工作分发的成果回来 false,标明工作没有被处理,这种状况标明体系暂时不知道怎样处理,最常见的状况便是组合键的第一个按键被按下,例如截屏键的 power 键按下,此刻体系不知道是不是要单独处理这个按键,还要等候组合键的别的一个按键,在超时前按下,因而体系不知道怎样处理,需求等等看。
3. 按键工作的分发
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Preprocessing.
if (!entry->dispatchInProgress) {
// ...省略生成重复按键的代码...
// 标明工作处于分发中
entry->dispatchInProgress = true;
logOutboundKeyDetails("dispatchKey - ", *entry);
}
// 分发战略让咱们稍后再试,这是为了等候别的一个组合键的按键工作到来
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
// 还没到等候的超时时刻,那么持续等候
if (currentTime < entry->interceptKeyWakeupTime) {
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
return false; // wait until next wakeup
}
// 这儿标明现已超时了,因而需求再次问询分发战略,看看成果
// 例如,当截屏的power键超时时,再次问询分发战略是否音量下键现已按下,假如按下了,
// 那么这个 power 工作就不再分发给用户
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
entry->interceptKeyWakeupTime = 0;
}
// Give the policy a chance to intercept the key.
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
// 1. 假如工作需求分发给用户,那么先查询分发战略
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
if (INPUTDISPATCHER_SKIP_EVENT_KEY != 0) {
if(entry->keyCode == 0 && entry->scanCode == INPUTDISPATCHER_SKIP_EVENT_KEY) {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
*dropReason = DropReason::POLICY;
ALOGI("Intercepted the key %i", INPUTDISPATCHER_SKIP_EVENT_KEY);
return true;
}
}
// 创立一个指令
std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
&InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
sp<IBinder> focusedWindowToken =
mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));
commandEntry->connectionToken = focusedWindowToken;
commandEntry->keyEntry = entry;
// mCommandQueue 中参加一个指令
postCommandLocked(std::move(commandEntry));
// 回来 false,标明需求运行指令看看这个工作是否需求触发组合键的功用
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 == DropReason::NOT_DROPPED) {
*dropReason = DropReason::POLICY;
}
}
// Clean up if dropping the event.
// 假如工作有满足的原因需求被丢掉,那么不履行后边的工作分发,而是直接保存工作注入的成果
if (*dropReason != DropReason::NOT_DROPPED) {
setInjectionResult(*entry,
*dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
: InputEventInjectionResult::FAILED);
mReporter->reportDroppedKey(entry->id);
return true;
}
// Identify targets.
// 2. 找到方针输入窗口,保存到 inputTargets
std::vector<InputTarget> inputTargets;
InputEventInjectionResult injectionResult =
findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
// 处理 InputEventInjectionResult::PENDING 成果
// 标明现在处理工作的机遇不成熟,例如窗口还在发动中,那么直接结束此刻的工作分发,等候机遇合适再处理
if (injectionResult == InputEventInjectionResult::PENDING) {
// 回来 false,那么会导致线程休眠一段时刻,等再次唤醒时,再来处理工作
return false;
}
// 保存注入成果
setInjectionResult(*entry, injectionResult);
// 处理 InputEventInjectionResult::FAILED 和 InputEventInjectionResult::PERMISSION_DENIED 成果
// 标明没有找到工作的输入方针窗口
if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
// 回来 true,那么工作行将被丢掉
return true;
}
// 走到这儿,标明成功找到焦点窗口
// Add monitor channels from event's or focused display.
// 增加监听一切工作的通道
// TODO:这个暂时不知道有何用
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
// Dispatch the key.
// 处理 InputEventInjectionResult::SUCCEEDED 成果,标明找到了工作输入方针
// 3. 工作分发按键给方针窗口
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
按键工作分发的首要进程如下
- 履行分发战略。分发战略的效果,一方面是完结组合按键的功用,别的一方面是为了在工作分发给窗口前,给体系一个优先处理的时机。
- 寻觅处理按键工作的焦点窗口。参阅【3.1 寻觅焦点窗口】
- 只要成功寻觅到焦点窗口,才进行按键工作分发。参阅【3.2 分发按键工作给方针窗口】
分发战略涉及到组合按键的完结,因而是一个非常杂乱的论题,咱们将在后边的文章中,把它和切断战略一起剖析。
3.1 寻觅焦点窗口
InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
nsecs_t* nextWakeupTime) {
std::string reason;
int32_t displayId = getTargetDisplayId(entry);
// 1. 获取焦点窗口
sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
// 获取焦点app
std::shared_ptr<InputApplicationHandle> focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
// 没有焦点窗口,也没有焦点app,那么丢掉工作
if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
ALOGI("Dropping %s event because there is no focused window or focused application in "
"display %" PRId32 ".",
NamedEnum::string(entry.type).c_str(), displayId);
return InputEventInjectionResult::FAILED;
}
// Drop key events if requested by input feature
// 窗口feature为 DROP_INPUT 或 DROP_INPUT_IF_OBSCURED,那么丢掉工作
if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
return InputEventInjectionResult::FAILED;
}
// 没有焦点窗口,可是有焦点app
// 这儿处理的状况是,app正在发动,可是还没有显示行将取得焦点的窗口
if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) {
if (!mNoFocusedWindowTimeoutTime.has_value()) {
// 发动一个 ANR 计时器,超时时刻默许5秒
std::chrono::nanoseconds timeout = focusedApplicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
mNoFocusedWindowTimeoutTime = currentTime + timeout.count();
// 保存正在等候焦点窗口的app
mAwaitedFocusedApplication = focusedApplicationHandle;
mAwaitedApplicationDisplayId = displayId;
ALOGW("Waiting because no window has focus but %s may eventually add a "
"window when it finishes starting up. Will wait for %" PRId64 "ms",
mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
// 线程唤醒时刻修改为超时时刻,以保证能及时处理 ANR
*nextWakeupTime = *mNoFocusedWindowTimeoutTime;
// 由于焦点窗口正在发动,暂时不处理工作
return InputEventInjectionResult::PENDING;
} else if (currentTime > *mNoFocusedWindowTimeoutTime) {
// Already raised ANR. Drop the event
ALOGE("Dropping %s event because there is no focused window",
NamedEnum::string(entry.type).c_str());
// 等候焦点窗口超时,丢掉这个工作
return InputEventInjectionResult::FAILED;
} else {
// Still waiting for the focused window
// 等候焦点窗口还没有超时,持续等候,暂时不处理工作
return InputEventInjectionResult::PENDING;
}
}
// 走到这儿,标明现已有了一个有用的焦点窗口
// we have a valid, non-null focused window
// 由于有了有用焦点窗口,重置 mNoFocusedWindowTimeoutTime 和 mAwaitedFocusedApplication
resetNoFocusedWindowTimeoutLocked();
// 关于注入工作,假如注入者的 UID 与窗口所属的 UDI 不同,而且注入者没有 android.Manifest.permission.INJECT_EVENTS 权限
// 那么注入工作将会被丢掉
if (!checkInjectionPermission(focusedWindowHandle, entry.injectionState)) {
return InputEventInjectionResult::PERMISSION_DENIED;
}
// 窗口处于暂停状况,暂时不处理当时按键工作
if (focusedWindowHandle->getInfo()->paused) {
ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
return InputEventInjectionResult::PENDING;
}
// 假如前面还有工作没有处理结束,那么需求等候前面工作处理结束,才干发送按键工作
// 由于前面的工作,或许影响焦点窗口,所以按键工作需求等候前面工作处理结束才干发送
if (entry.type == EventEntry::Type::KEY) {
if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
*nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
// 前面有时刻没有处理结束,因而暂不处理当时的按键工作
return InputEventInjectionResult::PENDING;
}
}
// Success! Output targets.
// 走到这儿,标明工作能够成功发送焦点窗口
// 2. 依据焦点窗口创立 InputTarget,并保存到参数 inputTargets
// 留意第二个参数,后边把工作参加到一个输入通道链接的收件箱时,会用到
addWindowTargetLocked(focusedWindowHandle,
InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
BitSet32(0), inputTargets);
// Done.
return InputEventInjectionResult::SUCCEEDED;
}
寻觅方针窗口的进程其实便是找到焦点窗口,然后依据焦点窗口创立 InputTarget,保存到参数 inputTargets 中。
结合前面的代码剖析,寻觅焦点窗口回来的成果,会影响工作的处理,总结如下
寻觅焦点窗口的成果 | 成果的阐明 | 怎样影响工作的处理 |
---|---|---|
InputEventInjectionResult::SUCCEEDED | 成功为工作找到焦点窗口 | 工作会被分发到焦点窗口 |
InputEventInjectionResult::FAILED | 没有找到可用的焦点窗口 | 工作会被丢掉 |
InputEventInjectionResult::PERMISSION_DENIED | 没有权限把工作发送到焦点窗口 | 工作会被丢掉 |
InputEventInjectionResult::PENDING | 有焦点窗口,可是暂时不可用于接纳工作 | 线程会休眠,等候机遇被唤醒,发送工作到焦点窗口 |
在工作中,有时分需求咱们剖析按键工作为何没有找到焦点窗口,这儿罗列一下一切的状况,以供大家工作或面试时运用
- 没有焦点app,而且没有焦点窗口。这种状况应该比较极端,应该整个surface体系都出问题了。
- 窗口的 Feature 标明要丢掉工作。这个丢掉工作的 Feature 是 Surface 体系给窗口设置的,现在我还没有搞清楚这儿面的逻辑。
- 焦点app发动焦点窗口,超时了。
- 关于注入工作,假如注入者的 UID 与焦点窗口的 UID 不同,而且注入者没有恳求 android.Manifest.permission.INJECT_EVENTS 权限。
没有找到焦点窗口的一切状况,都有日志对应输出,可帮助咱们定位问题。
现在来看一下,找到焦点窗口后,创立并保存 InputTarget 的进程
// 留意,参数 targetFlags 的值为 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
// InputTarget::FLAG_FOREGROUND 标明工作正在发送给前台窗口
// InputTarget::FLAG_DISPATCH_AS_IS 标明工作不加工,直接依照原样进行发送
void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
int32_t targetFlags, BitSet32 pointerIds,
std::vector<InputTarget>& inputTargets) {
std::vector<InputTarget>::iterator it =
std::find_if(inputTargets.begin(), inputTargets.end(),
[&windowHandle](const InputTarget& inputTarget) {
return inputTarget.inputChannel->getConnectionToken() ==
windowHandle->getToken();
});
const WindowInfo* windowInfo = windowHandle->getInfo();
if (it == inputTargets.end()) {
// 创立 InputTarget
InputTarget inputTarget;
// 获取窗口通道
std::shared_ptr<InputChannel> inputChannel =
getInputChannelLocked(windowHandle->getToken());
if (inputChannel == nullptr) {
ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
return;
}
// 保存输入通道,经过这个通道,工作才干发送给指定窗口
inputTarget.inputChannel = inputChannel;
// 留意,值为 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
inputTarget.flags = targetFlags;
inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
inputTarget.displayOrientation = windowInfo->displayOrientation;
inputTarget.displaySize =
int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
// 保存到 inputTargets中
inputTargets.push_back(inputTarget);
it = inputTargets.end() - 1;
}
ALOG_ASSERT(it->flags == targetFlags);
ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
// InputTarget 保存窗口的 Transform 信息,这会把显示屏的坐标,转换到窗口的坐标系上
// 关于按键工作,不需求把显示屏坐标转换到窗口坐标
// 因而,关于按键工作,pointerIds 为0,这儿只是简略保存一个默许的 Transfrom 罢了
it->addPointers(pointerIds, windowInfo->transform);
}
3.2 分发按键工作给方针窗口
现在,处理按键工作的焦点窗口现已找到,而且现已保存到 inputTargets,是时分来分发按键工作了
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
std::shared_ptr<EventEntry> eventEntry,
const std::vector<InputTarget>& inputTargets) {
updateInteractionTokensLocked(*eventEntry, inputTargets);
pokeUserActivityLocked(*eventEntry);
for (const InputTarget& inputTarget : inputTargets) {
// 获取方针窗口的衔接
sp<Connection> connection =
getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
if (connection != nullptr) {
// 预备分发循环
prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
} else {
if (DEBUG_FOCUS) {
ALOGD("Dropping event delivery to target with channel '%s' because it "
"is no longer registered with the input dispatcher.",
inputTarget.inputChannel->getName().c_str());
}
}
}
}
焦点窗口只要一个,为何需求一个 inputTargets 集合来保存一切的方针窗口,由于依据前面的剖析,除了焦点窗口以外,还有一个大局的监听工作的输入方针。
WindowManagerService 会在创立窗口时,创立一个衔接,其间一端给窗口,别的一端给输入体系。当输入体系需求发送工作给窗口时,就会经过这个衔接进行发送。至于衔接的树立进程,有点小杂乱,本分不剖析,后边假如写 WMS 的文章,再来细致剖析一次。
找到这个窗口的衔接后,就预备分发循环 ? 问题来了,什么是分发循环 ? InputDispatcher 把一个工作发送给窗口,窗口处理完工作,然后回来成果为 InputDispatcher,这便是一个循环。可是留意,分发工作给窗口,窗口回来处理工作成果,这两个是互为异步进程。
现在来看下分发循环之前的预备
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection,
std::shared_ptr<EventEntry> eventEntry,
const InputTarget& inputTarget) {
// ...
// 衔接处理反常状况,丢掉工作
if (connection->status != Connection::STATUS_NORMAL) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
connection->getInputChannelName().c_str(), connection->getStatusLabel());
#endif
return;
}
// Split a motion event if needed.
// 针对接触工作的split
if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
// ...
}
// Not splitting. Enqueue dispatch entries for the event as is.
// 把工作参加到衔接的发件箱中,然后发动分发循环
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection,
std::shared_ptr<EventEntry> eventEntry,
const InputTarget& inputTarget) {
// ...
bool wasEmpty = connection->outboundQueue.empty();
// Enqueue dispatch entries for the requested modes.
// 1. 保存工作到衔接的发件箱 Connection::outboundQueue
// 留意最终一个参数,它的窗口的分发形式,定义了工作怎样分发到指定窗口
// 依据前面的代码剖析,现在保存的方针窗口的分发形式只支持下面罗列的 InputTarget::FLAG_DISPATCH_AS_IS
// InputTarget::FLAG_DISPATCH_AS_IS 标明工作依照原样进行发送
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 the outbound queue was previously empty, start the dispatch cycle going.
// 衔接的发件箱忽然有工作了,那得发动分发循环,把工作发送到指定窗口
if (wasEmpty && !connection->outboundQueue.empty()) {
// 2. 发动分发循环
startDispatchCycleLocked(currentTime, connection);
}
}
分发循环前的预备工作,其实便是依据窗口所支持的分发形式(dispatche mode),调用enqueueDispatchEntryLocked() 创立并保存工作到衔接的收件箱。前面剖析过,焦点窗口的的分发形式为 InputTarget::FLAG_DISPATCH_AS_IS | InputTarget::FLAG_FOREGROUND,而此刻只用到了InputTarget::FLAG_DISPATCH_AS_IS。 参阅【3.2.1 依据分发形式,增加工作到衔接纳件箱】
假如衔接的收件箱之前没有工作,那么证明衔接没有处于发送工作的状况中,而现在有工作了,那就发动分发循环来发送工作。参阅 【3.2.2 发动分发循环】
3.2.1 依据分发形式,增加工作到衔接纳件箱
void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
std::shared_ptr<EventEntry> eventEntry,
const InputTarget& inputTarget,
int32_t dispatchMode) {
// ...
// 前面保存的 InputTarget,它的 flags 为 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
int32_t inputTargetFlags = inputTarget.flags;
// 窗口不支持恳求的dispatcher mode,那么不增加工作到衔接的发件箱中
// 关于按键工作,dispatchMode 只能是 InputTarget::FLAG_DISPATCH_AS_IS
if (!(inputTargetFlags & dispatchMode)) {
return;
}
// 1. 为每一个窗口所支持的 dispatche mode,创立一个 DispatchEntry
inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
std::unique_ptr<DispatchEntry> dispatchEntry =
createDispatchEntry(inputTarget, eventEntry, inputTargetFlags);
// Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
// different EventEntry than what was passed in.
EventEntry& newEntry = *(dispatchEntry->eventEntry);
// Apply target flags and update the connection's input state.
switch (newEntry.type) {
case EventEntry::Type::KEY: {
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(newEntry);
dispatchEntry->resolvedEventId = keyEntry.id;
dispatchEntry->resolvedAction = keyEntry.action;
dispatchEntry->resolvedFlags = keyEntry.flags;
if (!connection->inputState.trackKey(keyEntry, dispatchEntry->resolvedAction,
dispatchEntry->resolvedFlags)) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
connection->getInputChannelName().c_str());
#endif
return; // skip the inconsistent event
}
break;
}
// ...
}
// Remember that we are waiting for this dispatch to complete.
// 检测工作是否正在发送到前台窗使用,依据前面的代码剖析,方针窗口的flags包含 FLAG_FOREGROUND
// 因而,条件成立
if (dispatchEntry->hasForegroundTarget()) {
// EventEntry::injectionState::pendingForegroundDispatches +1
incrementPendingForegroundDispatches(newEntry);
}
// 2. 把 DispatchEntry 参加到衔接的发件箱中
connection->outboundQueue.push_back(dispatchEntry.release());
traceOutboundQueueLength(*connection);
}
依据前面创立 InputTarget 的代码可知,InputTarget::flags 的值为 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS。
InputTarget::FLAG_FOREGROUND 标明工作正在发送给前台使用,InputTarget::FLAG_DISPATCH_AS_IS 标明工作依照原样进行发送。
而参数 dispatchMode 只运用了 InputTarget::FLAG_DISPATCH_AS_IS,因而,关于按键工作,只会创立并增加一个 DispatchEntry 到 Connection::outboundQueue。
3.2.2 发动分发循环
现在,焦点窗口衔接的发件箱中现已有工作了,此刻真的到了发送工作给焦点窗口的时分了
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
// ...
// 遍历衔接发件箱中的一切工作,逐一发送给方针窗口
while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
DispatchEntry* dispatchEntry = connection->outboundQueue.front();
dispatchEntry->deliveryTime = currentTime;
// 核算工作分发的超时时刻
const std::chrono::nanoseconds timeout =
getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
dispatchEntry->timeoutTime = currentTime + timeout.count();
// Publish the event.
status_t status;
const EventEntry& eventEntry = *(dispatchEntry->eventEntry);
switch (eventEntry.type) {
case EventEntry::Type::KEY: {
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
std::array<uint8_t, 32> hmac = getSignature(keyEntry, *dispatchEntry);
// 1. 发送按键工作
status = connection->inputPublisher
.publishKeyEvent(dispatchEntry->seq,
dispatchEntry->resolvedEventId, keyEntry.deviceId,
keyEntry.source, keyEntry.displayId,
std::move(hmac), dispatchEntry->resolvedAction,
dispatchEntry->resolvedFlags, keyEntry.keyCode,
keyEntry.scanCode, keyEntry.metaState,
keyEntry.repeatCount, keyEntry.downTime,
keyEntry.eventTime);
break;
}
// ...
}
// Check the result.
if (status) {
// 发送反常
if (status == WOULD_BLOCK) {
// ...
}
return;
}
// 走到这儿,标明按键工作发送成功
// 2. 按键工作发送成功,那么从衔接的发件箱中移除
connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
connection->outboundQueue.end(),
dispatchEntry));
traceOutboundQueueLength(*connection);
// 3. 把现已发送的工作,参加到衔接的等候行列中 Connection::waitQueue
// 衔接在等候什么呢?当然是比及窗口的处理成果
connection->waitQueue.push_back(dispatchEntry);
// 衔接可呼应,那么会记载工作处理的超时时刻,一旦超时,会引发 ANR
// 由于咱们不或许无限等候窗口处理完工作,后边还有很多工作要处理呢
// 4. 用 AnrTracker 记载工作处理的超时时刻
if (connection->responsive) {
mAnrTracker.insert(dispatchEntry->timeoutTime,
connection->inputChannel->getConnectionToken());
}
traceWaitQueueLength(*connection);
}
}
工作分发循环的进程如下
- 经过窗口衔接,把工作发送给窗口,并从衔接的发件箱 Connection::outboundQueue 中移除。
- 把刚刚发送的工作,保存到衔接的等候行列 Connection::waitQueue。衔接在等候什么呢?当然是比及窗口的处理成果。
- 用 AnrTracker 记载工作处理的超时时刻,假如工作处理超时,会引发 ANR。
已然叫做一个循环,现在工作现已发送出去了,那么怎样接纳处理成果呢? InputDispatcher 线程运用了底层的 Looper 机制,当窗口与输入体系树立衔接时,Looper 经过 epoll 机制监听衔接的输入端的文件描述符,当窗口经过衔接反应处理成果时,epoll 就会收到可读工作,因而 InputDispatcher 线程会被唤醒来读取窗口的工作处理成果,而这个进程便是下面的下面的回调函数
假如读者想了解底层的 Looper 机制,能够参阅我写的 深入了解Native层音讯机制
int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
std::scoped_lock _l(mLock);
// 1. 获取对应的衔接
sp<Connection> connection = getConnectionLocked(connectionToken);
if (connection == nullptr) {
ALOGW("Received looper callback for unknown input channel token %p. events=0x%x",
connectionToken.get(), events);
return 0; // remove the callback
}
bool notify;
if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
if (!(events & ALOOPER_EVENT_INPUT)) {
ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
"events=0x%x",
connection->getInputChannelName().c_str(), events);
return 1;
}
nsecs_t currentTime = now();
bool gotOne = false;
status_t status = OK;
// 经过一个无限循环读取,尽或许读取一切的反应成果
for (;;) {
// 2. 读取窗口的处理成果
Result<InputPublisher::ConsumerResponse> result =
connection->inputPublisher.receiveConsumerResponse();
if (!result.ok()) {
status = result.error().code();
break;
}
if (std::holds_alternative<InputPublisher::Finished>(*result)) {
const InputPublisher::Finished& finish =
std::get<InputPublisher::Finished>(*result);
// 3. 完结分发循环
finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,
finish.consumeTime);
} else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
// ...
}
gotOne = true;
}
if (gotOne) {
// 4. 履行第三步发送的指令
runCommandsLockedInterruptible();
if (status == WOULD_BLOCK) {
return 1;
}
}
notify = status != DEAD_OBJECT || !connection->monitor;
if (notify) {
ALOGE("channel '%s' ~ Failed to receive finished signal. status=%s(%d)",
connection->getInputChannelName().c_str(), statusToString(status).c_str(),
status);
}
} else {
// ...
}
// Remove the channel.
// 衔接的一切工作都发送结束了,从 mAnrTracker 和 mConnectionsByToken 移除相应的数据
// TODO: 为何必定要移除呢?
removeInputChannelLocked(connection->inputChannel->getConnectionToken(), notify);
return 0; // remove the callback
}
处理窗口反应的工作处理成果的进程如下
- 依据衔接的 token 获取衔接。
- 从衔接中读取窗口回来的工作处理成果。
- 完结这个工作的分发循环。此进程会创立一个指令,并加如到指令行列中,然后在第四步履行。
- 履行第三步创立的指令,以完结分发循环。
当监听到窗口的衔接有工作到来时,会从衔接读取窗口对工作的处理成果,然后创立一个行将履行的指令,保存到指令行列中,如下
void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection, uint32_t seq,
bool handled, nsecs_t consumeTime) {
if (connection->status == Connection::STATUS_BROKEN ||
connection->status == Connection::STATUS_ZOMBIE) {
return;
}
// Notify other system components and prepare to start the next dispatch cycle.
onDispatchCycleFinishedLocked(currentTime, connection, seq, handled, consumeTime);
}
void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime,
const sp<Connection>& connection, uint32_t seq,
bool handled, nsecs_t consumeTime) {
// 创立指令并参加到指令行列 mCommandQueue 中
// 当指令调用时,会履行 doDispatchCycleFinishedLockedInterruptible 函数
std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
&InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
commandEntry->connection = connection;
commandEntry->eventTime = currentTime;
commandEntry->seq = seq;
commandEntry->handled = handled;
commandEntry->consumeTime = consumeTime;
postCommandLocked(std::move(commandEntry));
}
指令是用来完结工作分发循环的,那么指令什么时分履行呢?这便是第四步履行的,终究调用如下函数来履行指令
void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) {
sp<Connection> connection = commandEntry->connection;
const nsecs_t finishTime = commandEntry->eventTime;
uint32_t seq = commandEntry->seq;
const bool handled = commandEntry->handled;
// Handle post-event policy actions.
// 1. 依据序号seq,从衔接的等候行列中获取工作
std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
if (dispatchEntryIt == connection->waitQueue.end()) {
return;
}
// 获取对应的工作
DispatchEntry* dispatchEntry = *dispatchEntryIt;
// 假如工作处理的有一点点慢,可是没超越超时工作,那么这儿会给一个正告
// 这也阐明,窗口处理工作,不要履行耗时的代码
const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
}
if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
connection->inputChannel->getConnectionToken(),
dispatchEntry->deliveryTime, commandEntry->consumeTime,
finishTime);
}
bool restartEvent;
if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry->eventEntry));
restartEvent =
afterKeyEventLockedInterruptible(connection, dispatchEntry, keyEntry, handled);
} else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry->eventEntry));
restartEvent = afterMotionEventLockedInterruptible(connection, dispatchEntry, motionEntry,
handled);
} else {
restartEvent = false;
}
// Dequeue the event and start the next cycle.
// Because the lock might have been released, it is possible that the
// contents of the wait queue to have been drained, so we need to double-check
// a few things.
dispatchEntryIt = connection->findWaitQueueEntry(seq);
if (dispatchEntryIt != connection->waitQueue.end()) {
dispatchEntry = *dispatchEntryIt;
// 2. 从 Connection::waitQueue 中移除等候反应的工作
connection->waitQueue.erase(dispatchEntryIt);
const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
// 3. 已然工作处理成果现已反应了,那么就不必再记载它的处理超时时刻了
mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
// 衔接从无呼应变为可呼应,那么停止 ANR
if (!connection->responsive) {
connection->responsive = isConnectionResponsive(*connection);
if (connection->responsive) {
// The connection was unresponsive, and now it's responsive.
processConnectionResponsiveLocked(*connection);
}
}
traceWaitQueueLength(*connection);
if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
connection->outboundQueue.push_front(dispatchEntry);
traceOutboundQueueLength(*connection);
} else {
releaseDispatchEntry(dispatchEntry);
}
}
// Start the next dispatch cycle for this connection.
// 4. 已然经过衔接纳到反应,那趁这个时机,假如发件箱还有工作,持续发动分发循环来发送工作
startDispatchCycleLocked(now(), connection);
}
分发循环的完结进程如下
- 查看衔接中是否有对应的正在的等候的工作。
- 已然窗口现已反应的工作的处理成果,那么从衔接的等候行列 Connection::waitQueue 中移除。
- 已然窗口现已反应的工作的处理成果,那么就不必处理这个工作的 ANR,因而移除工作的 ANR 超时时刻。
- 已然此刻窗口正在反应工作的处理成果,那趁热打铁,那么开启下一次分发循环,发送衔接发件箱中的工作。当然,假如发件箱没有工作,那么什么也不做。
完结分发循环,其实最首要的便是把按键工作从衔接的等候行列中移除,以及免除 ANR 的触发。
总结
本文尽管剖析的只是按键工作的分发进程,可是从全体上剖析了一切工作的分发进程。咱们将以此为根底去剖析接触工作(motion event)的分发进程。
现在总结下一个按键工作的根本发送流程
- InputReader 线程把按键工作参加到 InputDispatcher 的收件箱之前,会问询切断战略,假如战略切断了,那么工作终究不会发送给窗口。
- InputDispatcher 经过一次线程循环来发送按键工作
- 工作在发送之前,会循环分发战略,首要是为了完结组合按键功用。
- 假如切断战略和分发战略都不切断按键工作,那么会寻觅能处理按键工作的焦点窗口。
- 焦点窗口找到了,那么会把按键工作参加到窗口衔接的发件箱中。
- 履行分发循环,从窗口衔接的发件箱中获取工作,然后发送给窗口。然后把工作从发件箱中移除,并参加到衔接的等候行列中。最终,记载 ANR 时刻。
- 窗口回来工作的处理成果,InputDispatcher 会读取成果,然后把工作从衔接的等候行列中移除,然后免除 ANR 的触发。
- 持续发送衔接中的工作,并重复上述进程,直至衔接中没有工作停止。
感想
我是一个注重实际效果的人,我花这么大力气去剖析工作的分发流程,是否值得? 从长期的考虑看,肯定是值得的,从短期看,咱们能够从 trace log 中剖分出 ANR 的原因是否是由于工作处理超时。
最终,说一个题外话。我有个大学同学是这个渠道的签约作者,一年下来的,渠道给的费用有小几千。我老婆知道这个工作后问我怎样不搞呢? 我解释说,我不是一个喜爱被束缚的人,我觉得我的文章没写好,我就不会发出去,我觉得我心境不好,也不会把文章发出去,所以我更新文章喜爱断断续续。假如大家觉得我的文章的不错,能够关注我,假如觉得我的文章有什么建议,欢迎留言,bye~