上次跟着 InputReader 看完它的流程后,就到 InputDispatcher 这里来进行分发了。InputDispatcher 也运行在一个独立的线程中(InputDispatcherThread,也可以叫派发线程),在它一次线程循环中会获取位于派发队列队首位置的事件,并将其派发给合适的窗口。根据上一节的介绍,当事件进入 InputDispatcher 时,输入事件已经被 InputReader 加工成三种类型:Key 事件、Motion 事件以及 Switch 事件。
1. 将事件注入派发队列
InputDispatcher 实现了 InputListenerInterface,并在 InputReader 线程循环的最后,QueuedInputListener 调用此接口将 InputReader 产生的事件以 NotifyXXXArgs 结构体的形式提交给 InputDispatcher,根据 Motion 事件来看下它是如何进一步处理的。
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
/* 首先验证参数的有效性。对Motion事件来说,主要是为了验证触控点的数量与Id是否在合理范围 */
if (!validateMotionEvent(args->action, args->actionButton,
args->pointerCount, args->pointerProperties)) {
return;
}
// 1.在输入事件进入派发队列之前,首先向DispatcherPolicy获取本事件的派发策略
uint32_t policyFlags = args->policyFlags;
// 从InputReader过来的事件都是TRUSTED,如果通过adb shell input则没有该flag
policyFlags |= POLICY_FLAG_TRUSTED;
android::base::Timer t;
/* 分发前拦截操作,注意,policyFlag是一个按引用传递的参数,也就是说DispatcherPolicy会更改policyFlags的取值 */
mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
}
bool needWake;
{ // acquire lock
mLock.lock();
// 2.判断是否设置了过滤器,走过滤步骤,进入后就不会再往下走了,正常不会进入
if (shouldSendMotionToInputFilterLocked(args)) {
mLock.unlock();
MotionEvent event;
// 使用NotifyMotionArgs参数中所保存的时间信息填充一个MotionEvent对象
event.initialize(args->deviceId, args->source, args->displayId,
args->action, args->actionButton,
args->flags, args->edgeFlags, args->metaState, args->buttonState,
args->classification, 0, 0, args->xPrecision, args->yPrecision,
args->downTime, args->eventTime,
args->pointerCount, args->pointerProperties, args->pointerCoords);
// 通过policyFLags标记此事件已被过滤
policyFlags |= POLICY_FLAG_FILTERED;
// 由DispatcherPolicy启动过滤动作。注意,当过滤结果为false时,此事件将被忽略
if (!mPolicy->filterInputEvent(&event, policyFlags)) {
return; // event was consumed by the filter
}
mLock.lock();
}
/* 3.使用NotifyMotionArgs参数中的事件信息构造一个MotionEntry,并通过enqueueInboundEventLocked()函数将其放入派发队列中 */
MotionEntry* newEntry = new MotionEntry(args->sequenceNum, args->eventTime,
args->deviceId, args->source, args->displayId, policyFlags,
args->action, args->actionButton, args->flags,
args->metaState, args->buttonState, args->classification,
args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
// 注意返回值needWake,它指示派发现场是否处于休眠状态
needWake = enqueueInboundEventLocked(newEntry);
mLock.unlock();
} // release lock
// 4.唤醒派发线程
if (needWake) {
mLooper->wake();
}
}
看下mPolicy->interceptMotionBeforeQueueing函数:
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and pass them along.
// - No special filtering for injected events required at this time.
// - Filter normal events based on screen state.
// - For normal events brighten (but do not wake) the screen if currently dim.
bool interactive = mInteractive.load();
// 通过dumpsys input可以查看是否interactive,一般都为true
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
if (policyFlags & POLICY_FLAG_INTERACTIVE) {
// 正常走这里,设置掩码
policyFlags |= POLICY_FLAG_PASS_TO_USER;
} else {
JNIEnv* env = jniEnv();
jint wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
displayId, when, policyFlags);
if (checkAndClearExceptionFromCallback(env,
"interceptMotionBeforeQueueingNonInteractive")) {
wmActions = 0;
}
// 否则做拦截操作
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
} else {
if (interactive) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
}
这里的mPolicy是初始化InputDispatcher传进来的实现InputDispatcherPolicyInterface接口的com_android_server_input_InputManagerService.cpp,这里根据policyFlags来判断是否被拦截。
之后会把NotifyMotionArgs构建成MotionEntry,通过enqueueInboundEventLocked将MotionEntry加入到队列中
// frameworks/native/services/inputflinger/InputDispatcher.cpp
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
/* 如果mInboundQueue为空,则表示此时派发线程处于休眠状态,因此将事件注入mInboundQueue后需要将其唤醒 */
bool needWake = mInboundQueue.isEmpty();
// 添加到队尾
mInboundQueue.enqueueAtTail(entry);
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.
KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
if (isAppSwitchKeyEvent(keyEntry)) {
if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
mAppSwitchSawKeyDown = true;
} else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
if (mAppSwitchSawKeyDown) {
mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
mAppSwitchSawKeyDown = false;
needWake = true;
}
}
}
break;
}
case EventEntry::TYPE_MOTION: {
// Optimize case where the current application is unresponsive and the user
// decides to touch a window in a different application.
// If the application takes too long to catch up then we drop all events preceding
// the touch into the other window.
MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
&& (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
&& mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
&& mInputTargetWaitApplicationToken != nullptr) {
int32_t displayId = motionEntry->displayId;
int32_t x = int32_t(motionEntry->pointerCoords[0].
getAxisValue(AMOTION_EVENT_AXIS_X));
int32_t y = int32_t(motionEntry->pointerCoords[0].
getAxisValue(AMOTION_EVENT_AXIS_Y));
sp<InputWindowHandle> touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y);
if (touchedWindowHandle != nullptr
&& touchedWindowHandle->getApplicationToken()
!= mInputTargetWaitApplicationToken) {
// User touched a different application than the one we are waiting on.
// Flag the event, and start pruning the input queue.
mNextUnblockedEvent = motionEntry;
needWake = true;
}
}
break;
}
return needWake;
}
该函数先将EventEntry添加到队尾,根据entry->type走TYPE_MOTION,这里的注释写的比较明白:
优化当前应用程序无响应且用户无响应的情况
来决定触摸其他应用程序中的窗口。
如果应用程序需要很长时间才能赶上,那么会删除之前的到其他窗口的所有事件
如果action为AMOTION_EVENT_ACTION_DOWN窗口处于等待,才会进入该判断,设置needWake为true
执行完成后会回到InputDispatcher::notifyMotion,剩余:
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
// ...
if (needWake) {
mLooper->wake();
}
}
mLooper->wake 实际就是唤醒 InputDispatcher 的 Looper
通过这两个函数可以看出,到达 InputDispatcher 的 Motion 事件被保存在 MotionEntry 类中,然后排在 mInboundQueue 队列的队尾,这个 mInboundQueue 就是 InputDispatcher 的派发队列。MotionEntry 是 EventEntry 的一个子类,保存了 Motion 事件的信息。Key 事件也会有一个KeyEntry与之对应。EventEntry 是输入事件在 InputDispatcher 中的存在形式。
另外,由于InputDispatcher在没有事件可以派发时(mInboundQueue为空),将会进入休眠状态,因此在将事件放入派发队列时,需要将派发线程唤醒。
需要注意的是,notifyMotion()由InputReader在其线程循环中调用,因此在此函数执行的两个策略相关的动作interceptMotionBeforeQueuing()和InputFilter都发生在InputReader线程,而不是派发线程。
现在一个事件已经被注入派发队列中,接下里看一下派发线程的工作流程。
2. 派发线程的线程循环
InputDispatcher 工作在 InputDispatcherThread 中,与 InputReader 一次线程循环可以读取并生成多个输入事件不同,InputDispatcher 至少需要一次线程循环才能完成一个输入事件的派发动作。其线程循环由 InputDispatcher::dispatchOnce() 函数完成
// frameworks/native/services/inputflinger/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) {
mLooper = new Looper(false);
mReporter = createInputReporter();
// ...
}
构造InputDispatcher时new了Looper
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
/* 1.通过dispatchOnceInnerLocked()进行输入事件的派发。其中的传出参数nextWakeupTime决定了下次派发线程循环的执行时间*/
if (!haveCommandsLocked()) {
//mLooper->pollOnce之后的下一次就会进入该方法
dispatchOnceInnerLocked(&nextWakeupTime);
}
// 2.执行命令队列中的命令
if (runCommandsLockedInterruptible()) {
// 设置nextWkeupTime为LONG_LONG_MIN将使派发线程立刻开始下次线程循环
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock
// 3.如果有必要,将派发线程进入休眠状态,并由nextWakeupTime确定休眠的具体时间
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
InputDispatcherThread 线程启动时,调用 threadLoop 一直循环在这里,即 dispatchOnce,mLooper->wake 之后,就开始执行 pollOnce 了,再下一次就会进入 dispatchOnceInnerLocked 进行内部分发了
派发线程的一次循环包括如下三项工作:
- 进行一次事件派发。事件的派发工作仅当命令队列中没有命令时才会进行。派发工作会设置
nextWakeupTime指明随后休眠时间长短。 - 执行命令列表中的命令。所谓的命令,不过是一个符合 Command 签名的回调函数,可以通过
InputDispatcher::postCommandLocked()函数将其追加到命令列表中。可以参照Handler的工作方式来理解InputDispatcher执行命令的过程。 - 陷入休眠状态。
Looper的pollOnce()的实质就是epoll_wait(),因此派发线程的休眠在三种情况下可能被唤醒:- 调用
Looper::wake()函数主动唤醒(有输入事件注入派发队列中时) - 到达
nextWakeupTime的事件点时被唤醒 epoll_wait()监听的fd有epoll_event()发生时唤醒
- 调用
nextWakeupTime 的存在意义:当派发队列中最后一个事件的派发完成后,nextWakeupTime 将被设置为 LONG_LONG_MAX,使之在新的输入事件或命令到来前休眠以节约资源。另外,有时因为窗口尚未准备好接受事件(如已经有一个事件发送给窗口,但此窗口尚未对其进行反馈),则可以放弃此事件的派发并设置 nextWakeupTime 为一个合理的时间点,以便在下次循环时再尝试派发。
3. 派发工作的整体流程
dispatchOneInnerLocked() 函数体现了派发过程的整体流程。接下来以 Motion 事件为例,看一下 dispatchOnceInnerLocked() 的工作原理,并总结事件派发工作的特点。
// frameworks/native/services/inputflinger/InputDispatcher.cpp
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.
if (!mDispatchEnabled) {
resetKeyRepeatLocked();
}
/* 如果InputDispatcher被冻结,则不做任何派发操作。setInputDispatchMode()函数可以使得InputDispatcher在禁用、冻结与正常三种状态下切换 */
if (mDispatchFrozen) {
return;
}
// Optimize latency of app switches.
// Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
// been pressed. When it expires, we preempt dispatch and drop all other pending events.
bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
if (mAppSwitchDueTime < *nextWakeupTime) {
*nextWakeupTime = mAppSwitchDueTime;
}
// 1.从派发队列中取出一事件进行派发
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;
}
// Synthesize a key repeat if appropriate.
if (mKeyRepeatState.lastKeyEntry) {
if (currentTime >= mKeyRepeatState.nextRepeatTime) {
mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
} else {
if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
*nextWakeupTime = mKeyRepeatState.nextRepeatTime;
}
}
}
// 如果派发队列为空,则直接返回。此时nextWakeupTime将保持LONG_LONG_MAX,因此派发队列将进入无限期休眠是状态
if (!mPendingEvent) {
return;
}
} else {
/* 正常mInBoundQueue不为空,到这里将位于队首的一条EventEntry取出并保存在mPengdingEvent成员变量中。mPendingEvent表示处于派发过程中的一个输入事件。使用成员变量而不是局部变量保存,是由于此次线程循环有可能不能完成此事件的派发 */
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}
// Poke user activity for this event. 保持屏幕为活动状态
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(mPendingEvent);
}
// Get ready to dispatch the event.
resetANRTimeoutsLocked();
}
// ...
这里取出mPendingEvnet后,这里如果设置了Policy有POLICY_FLAG_PASS_TO_USER,则会调用pokeUserActivityLocked函数
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {
int32_t displayId = getTargetDisplayId(eventEntry);
sp<InputWindowHandle> focusedWindowHandle =
getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
if (focusedWindowHandle != nullptr) {
const InputWindowInfo* info = focusedWindowHandle->getInfo();
if (info->inputFeatures & InputWindowInfo::INPUT_FEATURE_DISABLE_USER_ACTIVITY) {
return;
}
}
int32_t eventType = USER_ACTIVITY_EVENT_OTHER;
switch (eventEntry->type) {
case EventEntry::TYPE_MOTION: {
const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry);
if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) {
return;
}
if (MotionEvent::isTouchEvent(motionEntry->source, motionEntry->action)) {
eventType = USER_ACTIVITY_EVENT_TOUCH;
}
break;
}
case EventEntry::TYPE_KEY: {
// ...
}
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doPokeUserActivityLockedInterruptible);
commandEntry->eventTime = eventEntry->eventTime;
commandEntry->userActivityEventType = eventType;
}
这里主要做的事情是设置eventType,配置touch或者key,调用InputDispatcher::doPokeUserActivityLockedInterruptible,实际做的是到InputManager通过jni调用了PowerManagerService.userActivityFromNative方法,通知它我们当前的事件类型做相应的处理。
重新回到InputDispatcher::dispatchOnceInnerLocked方法:
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// ...
// 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 = DROP_REASON_NOT_DROPPED;
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
/* 在将事件注入派发队列时曾向DispatcherPolicy询问过派发策略。如果派发策略不允许此事件被派发给用户,则设置对应的dropReason*/
dropReason = DROP_REASON_POLICY;
} else if (!mDispatchEnabled) {
/* 如果InputDispatcher被禁用(通过InputDispatcher:setInputDispatcherMode()),则此事件也会被丢弃。*/
dropReason = DROP_REASON_DISABLED;
}
if (mNextUnblockedEvent == mPendingEvent) {
mNextUnblockedEvent = nullptr;
}
// 根据不同的事件类型采取不同的派发流程
switch (mPendingEvent->type) {
// ...
case EventEntry::TYPE_MOTION: {
MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
dropReason = DROP_REASON_APP_SWITCH;
}
// 事件因为过期而被丢弃
if (dropReason == DROP_REASON_NOT_DROPPED
&& isStaleEvent(currentTime, typedEntry)) {
dropReason = DROP_REASON_STALE;
}
// 事件因为阻碍了其他窗口获取事件而被丢弃
if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DROP_REASON_BLOCKED;
}
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break;
}
}
// 如果事件派发完成,则准备派发下一个事件
if (done) {
// 如果事件犹豫某种原因被丢弃,为了保证窗口收到的事件仍能保持DOWN/UP,ENTER/EXIT的配对,还需要对事件进行补发
if (dropReason != DropReason::NOT_DROPPED) {
dropInboundEventLocked(*mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
// 设置mPendingEvent对象为NULL,使之在下次循环时可以处理派发队列中的下一条事件
releasePendingEventLocked();
// 立刻开始下一次循环。如果此时派发队列为空,下次循环调用此函数会保持nextWakeupTime为LONG_LONG_MAX并直接返回,使得派发线程进入无限期休眠
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
}
dispatchOnceInnerLocked() 函数的实现解释了以下几个问题:
- 如果派发队列为空,则会使得派发线程陷入无限期休眠问题
- 即将被派发的事件从派发队列中取出并保存在
mPendingEvent成员变量中 - 事件有可能因为某些原因而被丢弃,被丢弃的原因保存在
dropReason中 - 不同类型的事件使用不同的派发函数进行实际的派发动作。如
Motion事件使用dispatchMotionLocked()来进行派发 - 派发一个事件至少需要一次线程循环才能完成。是否在下次循环继续尝试此事件的派发由派发函数的返回值决定
- 事件的派发是串行的,在排在队首的时间完成派发或被丢弃之前,不会对后续的时间进行派发
4. 事件被丢弃的原因
前面的代码介绍了因为 DispatcherPolicy 认为事件不应派发给用户以及 InputDispatcher 被停用而导致事件被丢弃。DropReason 枚举完整地描述了事件被丢弃的所有原因。
DROP_REASON_POLICY:某些输入事件具有系统级的功能,例如 HOME 键、电源键、电话接听/挂断键等被系统处理,因此DispatcherPolicy不希望这些事件被窗口所捕获。当InputDispatcher在将输入事件放入派发队列前向DispatcherPolicy询问此事件的拍发策略时,DispatcherPolicy会将POLICY_FLAG_PASS_TO_USER策略去掉。没有这个派发策略的对象会被丢弃DROP_REASON_APP_SWITCH:dispatchOnceInnerLocked()函数说明了InputDispatcher的事件派发是串行的。因此在前一个事件的派发成功并得到目标窗口的反馈前,后续的输入事件都会被其阻塞。当某个窗口阴程序缺陷而无法响应输入时,懊恼的用户可能会尝试使用 HOME 键退出这个程序。然后遗憾的是,由于派发的串行性,用户所按的HOME键在其之前的输入事件成功派发给无响应的窗口之前无法获得派发的机会,因此在ANR对话框弹出之前的 5 秒里,用户不得不面对无响应的程序欲哭无泪。为了解决这个问题,InputDispatcher为HOME键设置了限时派发的要求。当InputDispatcher的enqueueInboundEventLocked()函数发现HOME键被加入派发队列后,便要求HOME键之前的所有输入事件在 0.5 秒(由APP_SWITCH_TIMEOUT常量定义)之前派发完毕,否则这些事件都将被丢弃,使得HOME键至少能够在 0.5 秒内得到响应DROP_RAESON_BLOCKED:和APP_SWITCH原因类似,如果是因为一个窗口无法响应输入事件,用户可能希望在其他窗口上进行点击,以尝试是否能得到响应。因为派发的串行性,这次尝试会以失败而告终。为此,当enqueueInboundEventLocked()发现有窗口正阻塞着派发的进行,并且新入队的触摸事件的目标是另外一个窗口,则将这个新事件保存到mNextunblockedEvent中。随后的dispatchOnceInnerLocked()会将此事件之前的输入事件全部丢弃,使得用户在其他窗口上进行点击的尝试可以立即得到相应DROP_REASON_DISABLED:因为InputDispatcher被禁用而使得事件被丢弃。InputDispatcher::setInputDispatchMode()函数可以使得InputDispatcher在禁用、冻结与正常三种状态之间进行切换。禁用状态会使得所有事件被丢弃,冻结将会使得dispatchOnceInnerLocked()函数直接返回从而停止派发工作。inputDispatcher的这三种状态的切换由Java层的IMS提供接口,由AMS和WMS根据需要进行设置。例如,当手机进入休眠状态时,InputDispatcher会被禁用,而屏幕旋转过程中,InputDispatcher会被暂时冻结DROP_REASON_STALE:在dispatchOnceInnerLocked()函数准备对事件进行派发时,会先检查一下时间所携带的时间戳与当前时间的差距。如果事件过于陈旧(10 秒以上,由常量STALE_EVENT_TIMEOUT所指定),则此事件需要被抛弃。 当事件幸运地避开了所有上述原因之后,才能由InputDispatcher尝试派发。对Motion时间来说,下一步是dispatchMotionLocked()函数。在这个函数中,InputDispatcher将为事件寻找合适的目标窗口
5. Motion 事件目标窗口的确定
接下来分析 dispatchMotionLocekd() 函数,这个函数专门用户为 Motion 事件寻找合适的目标窗口, 如果派发完成,无论是成功派发还是事件被丢弃,都返回 true,否则返回 false,以便在下次循环时再次尝试此事件的派发
// frameworks/native/services/inputflinger/InputDispatcher.cpp
bool InputDispatcher::dispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ...
// 正常为true
bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
// Identify targets. 触摸事件要发送的目标窗口
std::vector<InputTarget> inputTargets;
// 触摸事件是否有冲突
bool conflictingPointerActions = false;
int32_t injectionResult;
// 根据Motion事件的类型,寻找合适的目标窗口。其返回值injectionResult指明寻找结果,而找到合适的目标窗口信息将被保存在inputTargets列表中
if (isPointerEvent) {
// 对于基于坐标点形式的事件,如触摸屏点击等,将根据坐标点、窗口ZOrder与区域寻找目标窗口
injectionResult = findTouchedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
} else {
// 对于其他类型的Motion事件(例如轨迹球),将以拥有焦点的窗口作为目标
injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
}
/* 返回值PENDING表明找到了下一个窗口,不过如果窗口处于无响应状态,则返回false,也就是说这个事件尚未派发完成,将在下次派发线程的循环中再次尝试派发 */
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false;
}
/* 如果返回值不为SUCCEEDED, 表明无法为此事件找到合适的窗口,例如没有窗口处于焦点状态,或点击的位置没能落在任何一个窗口内,这个事件将被直接丢弃 */
if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
CancelationOptions::Mode mode(isPointerEvent
? CancelationOptions::CANCEL_POINTER_EVENTS
: CancelationOptions::CANCEL_NON_POINTER_EVENTS);
CancelationOptions options(mode, "input event injection failed");
synthesizeCancelationEventsForMonitorsLocked(options);
return true;
}
setInjectionResult(entry, injectionResult);
if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
if (injectionResult != INPUT_EVENT_INJECTION_PERMISSION_DENIED) {
CancelationOptions::Mode mode(isPointerEvent ?
CancelationOptions::CANCEL_POINTER_EVENTS :
CancelationOptions::CANCEL_NON_POINTER_EVENTS);
CancelationOptions options(mode, "input event injection failed");
synthesizeCancelationEventsForMonitorsLocked(options);
}
return true;
}
// 向inputTargets列表中添加特殊的接收目标,添加当前屏幕的全局Window监测, 该接收者可以监听所有的输入事件
// Add monitor channels from event's or focused display.
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(entry));
if (isPointerEvent) {
ssize_t stateIndex = mTouchStatesByDisplay.indexOfKey(entry->displayId);
if (stateIndex >= 0) {
const TouchState& state = mTouchStatesByDisplay.valueAt(stateIndex);
if (!state.portalWindows.empty()) {
// The event has gone through these portal windows, so we add monitoring targets of
// the corresponding displays as well.
for (size_t i = 0; i < state.portalWindows.size(); i++) {
const InputWindowInfo* windowInfo = state.portalWindows[i]->getInfo();
addGlobalMonitoringTargetsLocked(inputTargets, windowInfo->portalToDisplayId,
-windowInfo->frameLeft, -windowInfo->frameTop);
}
}
}
}
// Dispatch the motion.
if (conflictingPointerActions) {
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
"conflicting pointer actions");
synthesizeCancelationEventsForAllConnectionsLocked(options);
}
// 调用dispatchEventLocked()将事件派发给inputTargets列表中的所有目标
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
这个函数主要包括以下三项工作:
- 对于那些被丢弃的事件,直接返回
true - 为事件寻找合适的目标窗口。目标窗口分为两种:普通窗口以及监听窗口(
monitoring)。普通窗口通过按点查找与按焦点查找两种方式获得,而监听窗口则无条件监听所有输入事件。普通窗口的查找结果决定了此次线程循环是否可以完成事件派发。 - 如果成功地找到了可以接收事件的目标窗口,则通过
dispatchEventLocked()函数完成实际的派发工作 可以看到,查找到的派发目标使用InputTarget结构体存储。其中重要的字段定义如下:
struct InputTarget {
/* 用于连接InputDispatcher窗口的通信管道。InputDispatcher通过它将事件派发给目标窗口,并通过它读取来自目标窗口对事件的响应 */
std::shared_ptr<InputChannel> inputChannel;
/* 事件的派发选项。这个选项将使得事件派发时可以自动产生一些服务的输入事件。向此目标窗口发送的事件内容有可能因这个flag的设置而发生改变 */
int32_t flags = 0;
/* 目标窗口相对于屏幕坐标系的偏移与缩放参数。InputReader将输入事件从传感器物理坐标系转换为屏幕坐标系,而这几个参数可以将输入事件从屏幕坐标系转换到窗口自身的坐标系 */
float xOffset, yOffset;
float globalScaleFactor;
// pointerIds字段描述在这个窗口下由哪些触控点被按下
BitSet32 pointerIds;
}
接下来分别看一下采用按点查找与按焦点查找两种方式如何获取合适的 InputTarget 对象
1.根据坐标点查找目标窗口
dispatchMotionLocked() 使用 findTouchedWindowTargetsLocked() 函数更具事件的坐标点获取合适的目标窗口。犹豫多点触控下会出现某触控点移出其按下窗口的情况,此时需要将多点触控事件分割为多条单点事件。这个处理也位于此函数中,使得其逻辑十分复杂,为了能够清晰地分析根据坐标点查找窗口的原理,下面代码仅保留了关键的逻辑:
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const MotionEntry* entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
bool* outConflictingPointerActions) {
// ...
// For security reasons, we defer updating the touch state until we are sure that
// event injection will be allowed.
int32_t displayId = entry->displayId;
int32_t action = entry->action;
int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
// ...
// Copy current touch state into mTempTouchState.
// This state is always reset at the end of this function, so if we don't find state
// for the specified display then our initial state will be empty.
const TouchState* oldState = nullptr;
ssize_t oldStateIndex = mTouchStatesByDisplay.indexOfKey(displayId);
if (oldStateIndex >= 0) {
oldState = &mTouchStatesByDisplay.valueAt(oldStateIndex);
mTempTouchState.copyFrom(*oldState);
}
// 分离触摸
bool isSplit = mTempTouchState.split;
// 判断是否切换,比如input注入和手指滑动导致切换
bool switchedDevice = mTempTouchState.deviceId >= 0 && mTempTouchState.displayId >= 0
&& (mTempTouchState.deviceId != entry->deviceId
|| mTempTouchState.source != entry->source
|| mTempTouchState.displayId != displayId);
bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
// 第一次是DOWN,newGesture=true
bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
|| maskedAction == AMOTION_EVENT_ACTION_SCROLL
|| isHoverAction);
bool wrongDevice = false;
if (newGesture) {
bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
if (switchedDevice && mTempTouchState.down && !down && !isHoverAction) {
injectionResult = INPUT_EVENT_INJECTION_FAILED;
switchedDevice = false;
wrongDevice = true;
goto Failed;
}
mTempTouchState.reset();
mTempTouchState.down = down;
mTempTouchState.deviceId = entry->deviceId;
mTempTouchState.source = entry->source;
mTempTouchState.displayId = displayId;
isSplit = false;
} else {
// ...
}
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
int32_t pointerIndex = getMotionEventActionPointerIndex(action);
int32_t x = int32_t(entry->pointerCoords[pointerIndex].
getAxisValue(AMOTION_EVENT_AXIS_X));
int32_t y = int32_t(entry->pointerCoords[pointerIndex].
getAxisValue(AMOTION_EVENT_AXIS_Y));
bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
// 1.寻找触摸窗口的集合
sp<InputWindowHandle> newTouchedWindowHandle = findTouchedWindowAtLocked(
displayId, x, y, isDown /*addOutsideTargets*/, true /*addPortalWindows*/);
// ...
if (newTouchedWindowHandle != nullptr) {
// 已知x,y在newTouchedWindowHandle上,再次判断这个window上是否被其他覆盖
if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
// 判断部分被覆盖
} else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
}
// ...
// 添加到集合中
mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
}
}
// ...
{
bool haveForegroundWindow = false;
for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
haveForegroundWindow = true;
// 判断注入权限,第三方应用如何注入手势只能在自己APP中操作之类的
if (! checkInjectionPermission(touchedWindow.windowHandle,
entry->injectionState)) {
injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
injectionPermission = INJECTION_PERMISSION_DENIED;
goto Failed;
}
}
}
bool hasGestureMonitor = !mTempTouchState.gestureMonitors.empty();
if (!haveForegroundWindow && !hasGestureMonitor) {
injectionResult = INPUT_EVENT_INJECTION_FAILED;
goto Failed;
}
// Permission granted to injection into all touched foreground windows.
injectionPermission = INJECTION_PERMISSION_GRANTED;
}
// 2.检查TempTouchStateh总所有目标窗口是否已准备好接收新的输入事件
for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
// Check whether the window is ready for more input.
// 确定窗口是否可以接收新的事件
std::string reason = checkWindowReadyForMoreInputLocked(currentTime,
touchedWindow.windowHandle, entry, "touched");
if (!reason.empty()) {
/* 如果窗口不能接收新事件,则记录不能接收事件的原因,并设置nextWakeupTime为5s后,
如果5s后线程仍未将此事件派发成功而进入这个分支,则会开始向Java层通报ANR。在这里,
injectionResultg被设置成INPUT_EVENT_INJECTION_PENDING */
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
nullptr, touchedWindow.windowHandle, nextWakeupTime, reason.c_str());
// 因为此次查找到的窗口不能接收事件,所以跳过后续产生InputTarget的过程
goto Unresponsive;
}
}
}
// 判断是否窗口可见,如果可见的话也把WallPaper的window添加进来,效果就像在桌面滑动,桌面壁纸也会移动的样子
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
sp<InputWindowHandle> foregroundWindowHandle =
mTempTouchState.getFirstForegroundWindowHandle();
if (foregroundWindowHandle && foregroundWindowHandle->getInfo()->hasWallpaper) {
const std::vector<sp<InputWindowHandle>> windowHandles =
getWindowHandlesLocked(displayId);
for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
const InputWindowInfo* info = windowHandle->getInfo();
if (info->displayId == displayId
&& windowHandle->getInfo()->layoutParamsType
== InputWindowInfo::TYPE_WALLPAPER) {
mTempTouchState.addOrUpdateWindow(windowHandle,
InputTarget::FLAG_WINDOW_IS_OBSCURED
| InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED
| InputTarget::FLAG_DISPATCH_AS_IS,
BitSet32(0));
}
}
}
}
// ...
// 3.如果执行到这里,说明窗口的查找过程一切顺利。设置injectionResult为SUCCEEDED,并将生成的InputTarget放入参数inputTargets中
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
// 为每一个mTempTouchState中的窗口生成InputTargets
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, inputTargets);
}
// ...
这里的mTempTouchState是一个临时保存触摸窗口的结构体,最主要保存的是触摸的窗口的集合windows
// frameworks/native/services/inputflinger/InputDispatcher.h
struct TouchState {
bool down;
bool split;
int32_t deviceId; // id of the device that is currently down, others are rejected
uint32_t source; // source of the device that is current down, others are rejected
int32_t displayId; // id to the display that currently has a touch, others are rejected
std::vector<TouchedWindow> windows;
// This collects the portal windows that the touch has gone through. Each portal window
// targets a display (embedded display for most cases). With this info, we can add the
// monitoring channels of the displays touched.
std::vector<sp<InputWindowHandle>> portalWindows;
std::vector<TouchedMonitor> gestureMonitors;
TouchState();
~TouchState();
void reset();
void copyFrom(const TouchState& other);
void addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle,
int32_t targetFlags, BitSet32 pointerIds);
void addPortalWindow(const sp<InputWindowHandle>& windowHandle);
void addGestureMonitors(const std::vector<TouchedMonitor>& monitors);
void removeWindow(const sp<InputWindowHandle>& windowHandle);
void removeWindowByToken(const sp<IBinder>& token);
void filterNonAsIsTouchWindows();
void filterNonMonitors();
sp<InputWindowHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
};
寻找触摸窗口的方法findTouchedWindowAtLocked():
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,
int32_t x, int32_t y, bool addOutsideTargets, bool addPortalWindows) {
/* InputDispatcher保存了一个名为mWindowHandlesByDisplay的map,每个displayId对应一个列表,这个列表中所保存的
InputWindowHandle类保存了窗口的InputChannal以及InputWindowInfo结构体
InputWindowInfo结构体则保存了窗口的各种布局信息,包括可见性、位置和尺寸、flag等,
可以说,InputWindowHandle是WMS中的WindowState在输入系统中化身。而每个windowHandles列表则按照Z轴顺序保存了当前WMS中的所有窗口。
这个列表是WMS更新进InputDispatcher的 */
const std::vector<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);
// 1.遍历windowHandles列表中所有的WindowHandle,检查事件坐标点是否落在其上
for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
// 获取保存窗口布局信息的windowInfo
const InputWindowInfo* windowInfo = windowHandle->getInfo();
if (windowInfo->displayId == displayId) {
int32_t flags = windowInfo->layoutParamsFlags;
if (windowInfo->visible) {
if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
// 模态对话框,可以理解为有该弹窗就它在最前面
bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
| InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
/* 以下是选择目标窗口的两个关键条件:坐标落在窗口之上,或者此窗口没有指定FLAG_NOT_TOUCH_MODAL选项 */
if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
int32_t portalToDisplayId = windowInfo->portalToDisplayId;
if (portalToDisplayId != ADISPLAY_ID_NONE
&& portalToDisplayId != displayId) {
if (addPortalWindows) {
mTempTouchState.addPortalWindow(windowHandle);
}
return findTouchedWindowAtLocked(
portalToDisplayId, x, y, addOutsideTargets, addPortalWindows);
}
// Found window.
return windowHandle;
}
}
if (addOutsideTargets && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
mTempTouchState.addOrUpdateWindow(
windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, BitSet32(0));
}
}
}
}
return nullptr;
}
这里主要获取到当前屏幕的所有窗口,并遍历判断xy坐标点是否在window中,如果有的话则直接返回
在InputDispatcher::findTouchedWindowAtLocked()中,是通过getWindowHandlesLocked()来获取到当前displayId的所有window,这里再详细看一下:
// frameworks/native/services/inputflinger/InputDispatcher.cpp
std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>> mWindowHandlesByDisplay
GUARDED_BY(mLock);
// ...
std::vector<sp<InputWindowHandle>> InputDispatcher::getWindowHandlesLocked(
int32_t displayId) const {
std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>>::const_iterator it =
mWindowHandlesByDisplay.find(displayId);
if(it != mWindowHandlesByDisplay.end()) {
return it->second;
}
// Return an empty one if nothing found.
return std::vector<sp<InputWindowHandle>>();
}
可以看到是在mWindowHandlesByDisplay中通过displayId查找的,来看下mWindowHandlesByDisplay里是什么时候存储进去的
在InputDispatcher.cpp中查找添加的地方,最后发现是在InputDispatcher::setInputWindows中
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::setInputWindows(const std::vector<sp<InputWindowHandle>>& inputWindowHandles,
int32_t displayId, const sp<ISetInputWindowsListener>& setInputWindowsListener) {
// ...
// Insert or replace
mWindowHandlesByDisplay[displayId] = newHandles;
}
再追一下发现是在
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray,
int32_t displayId) {
std::vector<sp<InputWindowHandle> > windowHandles;
if (windowHandleObjArray) {
jsize length = env->GetArrayLength(windowHandleObjArray);
for (jsize i = 0; i < length; i++) {
jobject windowHandleObj = env->GetObjectArrayElement(windowHandleObjArray, i);
if (! windowHandleObj) {
break; // found null element indicating end of used portion of the array
}
sp<InputWindowHandle> windowHandle =
android_view_InputWindowHandle_getHandle(env, windowHandleObj);
if (windowHandle != nullptr) {
windowHandles.push_back(windowHandle);
}
env->DeleteLocalRef(windowHandleObj);
}
}
// ...
mInputManager->getDispatcher()->setInputWindows(windowHandles, displayId);
// ...
}
最后大概是这样:
InputDispatcher::setInputWindows(const std::vector<sp<InputWindowHandle>>& inputWindowHandles,
int32_t displayId, const sp<ISetInputWindowsListener>& setInputWindowsListener) //frameworks/native/services/inputflinger/InputDispatcher.cpp
InputManager::setInputWindows(const std::vector<InputWindowInfo>& infos,
const sp<ISetInputWindowsListener>& setInputWindowsListener) //frameworks/native/services/inputflinger/InputManager.cpp
SurfaceFlinger::updateInputWindowInfo() //frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
其实是由SurfaceFlinger::updateInputWindowInfo来传输的,这些窗口的信息实际上是SurfaceFlinger绘制渲染形成的
我们看窗口可以通过 adb shell dumpsys SurfaceFlinger 来查看
总的来说,findTouchedWindowTargetsLocked() 包括三项主要工作:
- 根据窗口的点击区域与事件发生的坐标点选取合适的目标窗口。注意其遍历顺序是沿着
ZOrder由上至下进行遍历,因此ZOrder越靠上,则拥有获取事件的优先权。另外,如果窗口没有在其LayoutParams.flag中指明FLAG_NOT_TOUCH_MODAL选项,说明是一个模式窗口。模式窗口将会阻止点击事件被派发给位于其下的窗口,无论点击事件是否发生在它的可点击区域内 - 检查所找到的窗口是否可以接收信的按键事件。这个检查工作是由
isWindowReadyForMoreInputLocked()函数完成的。如果窗口尚无法接收事件,则说明此窗口有可能发生ANR。handleTargetsNotReadlyLocked()会记录下这一事实,并将injectionResult设置为PENDING,要求下次派发线程的循环中重试此事件的派发。因为检查窗口是否可以接收新的输入事件需要我们清楚地理解向窗口派发事件以及窗口对事件做出反馈的过程。 - 如果找到的窗口可以接收新的事件,则由
addWindowTargetLocked()生成一个InputTarget,并放入参数inputTargets列表中。
2.根据焦点查找目标窗口
根据焦点查找目标窗口是由 findFocusedWindowTargetsLocked() 函数完成的。这个函数的主要工作与 findTouchedWindowTargetsLocked() 函数一样,而且其窗口查找过程毫不费力。因为 InputDispatcher 有一个名为 mFocusedWindowHandle 的 InputWindowHandle 对象,所以 InputDispatcher 只要判断此对象是否为 NULL 即可。
WMS 在进行布局操作时,会根据处于焦点状态的 Activity、窗口的属性与 ZOrder 确定处于焦点状态的窗口,并随同窗口列表一起提交至 InputDispatcher
6. 向窗口发送事件
合适的目标窗口被确定下来之后,便可以开始将实际的事件发送给窗口了。dispatchMotionLocked() 使用 dispatchEventLocked() 函数将事件发送给指定的 InputTarget。从函数的名字可以知道,我们又回到了通用的派发流程中。
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
EventEntry* eventEntry, const std::vector<InputTarget>& inputTargets) {
ATRACE_CALL();
// 调用PowerManagerService方法
pokeUserActivityLocked(eventEntry);
// 遍历目标窗口
for (const InputTarget& inputTarget : inputTargets) {
// 根据InputTarget中的InputChannel,获取对应的Connection对象的索引
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
// 调用prepareDispatchCycleLocked(),针对当前InputTarget启动事件发送循环
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());
#endif
}
}
}
这里就是暂时派发工作的终点,目前只需要知道 dispatchEventLocked() 函数可以将输入事件发送给特定的窗口即可,之后会再介绍 InputChannel 类、Connection 类以及时间发送循环的原理。
7. 通用事件派发流程总结
这次完成了输入事件从注入派发队列开始,到通过 InputTargets 将事件发送给指定窗口的过程,还有以下几点需要注意:
- 在事件进入派发队列之前的处理位于
InputReader线程中。而其他操作则位于派发线程中 - 事件以
EventEntry子类的形式存在于InputDispatcher中 - 事件派发是串行的,在队首的事件派发完成之前,不会进行其他事件的派发
- 在选择
InputTarget的过程中,如果发现有一个目标窗口尚未准备好接收事件,则暂停当前事件的派发,并通过设置nextWakeupTime在下次派发循环时再次尝试。