接着上篇文章Android IMS原理解析之InputReader的分析,本文主要分析Input事件派发:
Input事件派发
在事件派发之前,先回忆一下上篇文章讲到的,InputReader从EventHub获取到Events后,在loopOnce()末尾会调用QueuedInputListener::flush()来遍历执行listener.notifyxx(),该listener就是InputDispatcher,因此就开始了事件的派发,这里以notifyMotion()为例进行分析:
一.执行入口
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
........
//首先验证参数的有效性。对Motion事件来说,主要是为了验证触控点的数量与Id是否在合理范围
if (!validateMotionEvent(args->action, args->actionButton,
args->pointerCount, args->pointerProperties)) {
return;
}
//获取policyFlags,后面可能会对该值进行更改
uint32_t policyFlags = args->policyFlags;
//从InputReader过来的事件都是TRUSTED的
policyFlags |= POLICY_FLAG_TRUSTED;
//---------------------工作1--------------------------------
mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);
bool needWake;
{ // acquire lock
mLock.lock();
//---------------------工作2--------------------------------
if (shouldSendMotionToInputFilterLocked(args)) {
mLock.unlock();
//使用NotifyMotionArgs参数中所保存的事件信息填充一个MotionEvent对象
MotionEvent event;
event.initialize(args->deviceId, args->source, args->action, args->actionButton,
args->flags, args->edgeFlags, args->metaState, args->buttonState,
0, 0, args->xPrecision, args->yPrecision,
args->downTime, args->eventTime,
args->pointerCount, args->pointerProperties, args->pointerCoords);
//通过policyFlags标记此事件已被过滤,由DispatcherPolicy启动过滤动作。
//注意,当过滤结果为false时,此事件将被忽略
policyFlags |= POLICY_FLAG_FILTERED;
if (!mPolicy->filterInputEvent(&event, policyFlags)) {
return; // event was consumed by the filter
}
mLock.lock();
}
//使用NotifyMotionArgs参数中的事件信息构造一个MotionEntry
MotionEntry* newEntry = new MotionEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, args->actionButton, args->flags,
args->metaState, args->buttonState,
args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
args->displayId,
args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
//---------------------------------工作3-----------------------------
needWake = enqueueInboundEventLocked(newEntry);
mLock.unlock();
} // release lock
if (needWake) {
//------------------工作4--------------------------
mLooper->wake();
}
}
在该方法内部,主要做了四项工作:
1.向DispatcherPolicy获取本事件的派发策略,会回调到Java层的IMS,通过InputMonitor最终调用到PhoneWindowManager的同名方法进行策略控制派发,派发策略会通过PolicyFlags来体现;
2.把事件交给InputFilter进行过滤;
3.将构造的MotionEntry通过enqueueInboundEventLocked()将其放入派发队列中;
4.根据enqueueInboundEventLocked()的返回值来决定是否唤醒派发线程;
根据上面的第四项工作来看一下enqueueInboundEventLocked()方法具体实现:
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
bool needWake = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(entry);
traceInboundQueueLengthLocked();
switch (entry->type) {
.......
.......
case EventEntry::TYPE_MOTION: {
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
&& mInputTargetWaitApplicationHandle != NULL) {
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 != NULL
&& touchedWindowHandle->inputApplicationHandle
!= mInputTargetWaitApplicationHandle) {
// 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;
}
通过这个函数可以看出,构造的MotionEntry事件排在mInboundQueue列表的队尾,这个mInboundQueue就是InputDispatcher的派发队列。MotionEntry是EventEntry的一个子类,保存了Motion事件的信息。Key事件也有一个KeyEntry与之对应。EventEntry是输入事件在InputDispatcher中的存在形式。
由于InputDispatcher在没有事件可以派发时(mInboundQueue为空),将会进入休眠状态,因此在将事件放入派发队列时,需要将派发线程唤醒。
注意:notifyMotion()由InputReader在其线程循环中调用,因此在此函数执行的两个策略相关的动作interceptMotionBeforeQueueing()和InputFilter都发生在InputReader线程,而不是派发线程中。
现在一个事件已被注入派发队列中,接下来看一下派发线程的工作流程,前面讲到,在start()后会启动InputDispatcherThread线程来进行事件派发,跟InputreaderThread相同,也是在threadLoop()返回true,不断的调用执行;
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
接下来就是通过dispatchOnce()来执行事件派发:
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();
//通过dispatchOnceInnerLocked()进行输入事件的派发。其中的传出参数nextWakeupTime决定了下次派发线程循环的执行时间
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
//执行命令队列中的命令
if (runCommandsLockedInterruptible()) {
//设置nextWakeupTime为LONG_LONG_MIN将使派发线程立刻开始下次线程循环
nextWakeupTime = LONG_LONG_MIN;
}
}
//如果有必要,将派发线程进入休眠状态,并由nextWakeupTime确定休眠的具体时间
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
在dispatchOnce()内部主要做了两项工作:
1.执行dispatchOnceInnerLocked()来对事件进行派发,在线程循环中不断地将派发队列中的事件派发给合适的窗口;
2.在派发完成后,如果没有新的事件到来,则不会一直运行,执行mLooper->pollOnce(timeoutMillis)来进行休眠等待;如果有新事件到来时会唤醒退出dispatchOnce()方法并返回true,接着执行threadLoop()来调用dispatchOnce()进行事件处理;
前面分析事件获取时讲到,事件会通过QueuedInputListener先进行缓存,然后在执行flush()进行统一派发,为什么要避免在原始事件的加工过程中实时地向InputDispatcher提交事件呢?
在dispatchOnce()内部第二项工作:当派发对象为空时,为了节约资源,InputDispatcher的线程会进入休眠状态。当InputReader把新事件注入InputDispatcher的派发队列时,就需要休眠的InputDispatcher唤醒以进行派发工作。于是问题就出现了,倘若InputDispatcher派发一个事件的速度快于InputReader加工一个原始输入事件的速度,则一次InputReader线程循环所产生的多个事件会导致InputDispatcher多次休眠和唤醒。这种开销是没有必要的。
QueuedInputListener的存在,使得InputReader所产生的输入事件尽可能密集的注入InputDispatcher的派发队列中,从而让InputDispatcher在进行休眠之前尽可能多派发事件,少休眠唤醒。
接下来根据调用关系进行一一分析:
二.执行流程
1.dispatchOnceInnerLocked()
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
nsecs_t currentTime = now();
.......
//从派发队列中取出一事件进行派发
if (! mPendingEvent) {
if (mInboundQueue.isEmpty()) {
......
//如果派发队列为空,则直接返回。此时nextWakeupTime将保持LONG_LONG_MAX,因此派发队列将进入无限期休眠状态
if (!mPendingEvent) {
return;
}
} else {
//从派发队列中将位于队首的一条EventEntry取出并保存在mPendingEvent成员变量中。
//mPendingEvent表示处于派发过程中的一个输入事件。之所以使用一个成员变量而不是局部变量保存它,
//是由于此次线程循环有可能不能完成此事件的派发。
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}
}
//检查事件是否需要被丢弃
//dropReason描述了事件是否需要被丢弃。在后面的代码中可以看到各种导致事件被丢弃的原因
bool done = false;
DropReason dropReason = DROP_REASON_NOT_DROPPED;
......
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
&& isStaleEventLocked(currentTime, typedEntry)) {
dropReason = DROP_REASON_STALE;
}
if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DROP_REASON_BLOCKED;
}
//执行dispatchMotionLocked()进行Motion事件的派发。如果派发完成,无论是成功派发还是事件被丢弃,都返回true,
//否则返回false,以便在下次循环时再次尝试此事件的派发
done = dispatchMotionLocked(currentTime, typedEntry,&dropReason, nextWakeupTime);
break;
}
.....
if (done) {
if (dropReason != DROP_REASON_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()函数内部主要做了以下几项工作:
1.如果派发队列mInboundQueue为空,则会使派发线程陷入无限期休眠状态;
2.队列不为空,将被派发的事件从派发队列mInboundQueue中取出并保存在mPendingEvent成员变量中;
3.事件有可能因为某些原因被丢弃,被丢弃的原因保存在dropReason中(后续会分析一下dropReason对应的场景);
4.不同类型的事件使用不同的派发函数进行实际的派发动作。如本例中的Motion事件使用dispatchMotionLocked()函数进行派发;
5.派发一个事件至少需要一次线程循环才能完成,根据派发函数的返回值来决定是否在下次循环继续尝试此事件的派发;
6.事件的派发是串行的,在排队首的事件完成派发或被丢弃之前,不会对后续的事件进行派发;
派发一个事件至少需要一次线程循环才能完成的原因是事件的目标窗口有可能正在处理先前的一个输入事件,在窗口完成先前事件的处理并给予反馈之前,InputDispatcher不会再向此窗口派发新事件;另外,可能对此函数最后设置nextWakeupTime为LONG_LONG_MIN,使派发线程立即进行下一次循环有一些疑问。如果此时派发队列为空,为什么不设置其为LONG_LONG_MAX使其进入无限期休眠状态呢?在这里先提个醒,当派发队列为空时,派发线程可能需要在下次循环中生成重复按键事件,因此不能直接进入休眠。
dropReason
在上面的第三项工作中,提到了事件被丢弃的dropReason,一起看一下每个dropReason对应的含义:
1.DROP_REASON_POLICY:某些输入事件具有系统级的功能,例如HOME键、电源键、电话接听/挂断键等被需要被系统处理的事件,DispatcherPolicy不希望这些事件被窗口所捕获,当InputDispatcher在将输入事件放入派发队列前向DispatcherPolicy询问此事件的派发策略时,DispatcherPolicy会将POLICY_FLAG_PASS_TO_USER策略去掉,没有这个派发策略的对象会被丢弃;
2.DROP_REASON_DISABLED:因为InputDispatcher被禁用而使得事件被丢弃。InputDispatcher::setInputDispatchMode()函数可以使得InputDispatcher在禁用、冻结与正常三种状态之间进行切换。禁用状态会使得所有事件被丢弃,冻结将会使得dispatchOnceInnerLocked()函数直接返回从而停止派发工作。InputDispatcher的这三种状态的切换由java层的IMS提供接口setInputDispatchMode(xx, xx),由AMS和WMS根据需要进行设置。例如,当手机进入休眠状态时,InputDispatcher会被禁用,而屏幕旋转过程中,InputDispatcher会被暂时冻结;
3.DROP_REASON_APP_SWITCH:dispatchOnceInnerLocked()函数说明了InputDispatcher的事件派发是串行的。因此在前一个事件的派发成功并得到目标窗口的反馈前,后续的输入事件都会被其阻塞。当某个窗口因程序缺陷而无法响应输入时,用户可能会尝试使用HOME键退出这个程序。然而遗憾的是,由于派发的串行性,用户所按的HOME键在其之前的输入事件成功派发给无响应的窗口之前无法获得派发的机会,因此在ANR对话框弹出之前的5秒里,用户不得不面对无响应的应用程序欲哭无泪。为了解决这个问题,InputDispatcher为HOME键设置了限时派发的要求。当InputDispatcher的enqueueInboundEventLocked()函数发现HOME键被加入派发队列后,便要求HOME键之前的所有输入事件在0.5秒(由APP_SWITCH_TIMEOUT常量定义)之前派发完毕,否则这些事件将都会被丢弃,使得HOME键至少能够在0.5秒内得到响应。
4.DROP_REASON_BLOCKED:和APP_SWITCH原因类似,如果是因为一个窗口无法响应输入事件,用户可能希望在其他窗口上进行点击,以尝试是否能够得到响应。因为派发的串行性,这次尝试会以失败而告终。为此,当enqueueInboundEventLocked()发现有窗口正阻塞着派发的进行,并且新入队的触摸事件的目标是另外一个窗口,则将这个新事件保存到mNextUnblockedEvent中。随后的dispatchOnceInnerLocked()会将此事件之前的输入事件全部丢弃,使得用户在其他窗口上进行点击的尝试可以立即得到响应;
5.DROP_REASON_STALE:在dispatchOnceInnerLocked()函数准备对事件进行派发时,会先检查一下事件所携带的事件戳与当前时间的差距。如果事件过于陈旧(10秒以上,由常量STALE_EVENT_TIMEOUT所指定),则此事件需要被抛弃;
2.dispatchMotionLocked()
bool InputDispatcher::dispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
.......
//对于那些不幸被丢弃的事件,直接返回
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
return true;
}
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;
setInjectionResultLocked(entry, injectionResult);
//返回值不为SUCCEEDED,表明无法为此事件找到合适的窗口,例如没有窗口处于焦点状态,或点击的位置没能落在任何一个窗口内,
//这个事件将被直接丢弃
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列表中添加特殊的接收目标。可以看出这些名为MonitoringTargets的接受者可以监听所有的输入事件
addMonitoringTargetsLocked(inputTargets);
// Dispatch the motion.
if (conflictingPointerActions) {
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
"conflicting pointer actions");
synthesizeCancelationEventsForAllConnectionsLocked(options);
}
//调用dispatchEventLocked()将事件派发给inputTargets列表中的目标
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
dispatchMotionLocked()内部主要包含以下三项工作:
1.对于那些不同dropReason被丢弃的事件,直接返回true;
2.通过findTouchedWindowTargetsLocked()为事件寻找合适的目标窗口。目标窗口分为2种:普通窗口以及监听窗口(monitoring)。普通窗口通过按点查找与按焦点查找两种方式获得,而监听窗口则无条件监听所有输入事件,普通窗口的查找结果决定了此次线程循环是否可以完成事件派发;
3.如果成功地找到了可以接收事件的目标窗口,则通过dispatchEventLocked()函数完成实际的派发工作;
3.findTouchedWindowTargetsLocked()
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
bool* outConflictingPointerActions) {
.......
size_t numWindows = mWindowHandles.size();
for (size_t i = 0; i < numWindows; i++) {
sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
const InputWindowInfo* windowInfo = windowHandle->getInfo();
if (windowInfo->displayId != displayId) {
continue; // wrong display
}
int32_t flags = windowInfo->layoutParamsFlags;
if (windowInfo->visible) {
if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
| InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
//找到窗口,退出
newTouchedWindowHandle = windowHandle;
break; // found touched window, exit window loop
}
}
if (maskedAction == AMOTION_EVENT_ACTION_DOWN
&& (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
mTempTouchState.addOrUpdateWindow(
windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, BitSet32(0));
}
}
}
...................
...................
// Ensure all touched foreground windows are ready for new input.
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
//通过checkWindowReadyForMoreInputLocked()函数检查窗口是否准备好接受新事件
String8 reason = checkWindowReadyForMoreInputLocked(currentTime,
touchedWindow.windowHandle, entry, "touched");
if (!reason.isEmpty()) {
//如果尚未准备好,则通过handleTargetsNotReadyLocked()对原因进行记录,并安排时间尝试重试派发,或者引发ANR
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
NULL, touchedWindow.windowHandle, nextWakeupTime, reason.string());
goto Unresponsive;
}
}
}
......
// Success! Output targets.
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, inputTargets);
}
.............
}
3.1 checkWindowReadyForMoreInputLocked()
在对窗口进行派发前,会先判读窗口是否可以接收事件,如果窗口无法接收事件,则安排稍后再试,或者引发ANR:
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
const char* targetType) {
// If the window is paused then keep waiting.
if (windowHandle->getInfo()->paused) {
return String8::format("Waiting because the %s window is paused.", targetType);
}
// If the window's connection is not registered then keep waiting.
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);
}
//获取窗口的Connection
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());
}
//该窗口Connection的InputPublisher被阻塞,在执行publishMotionEvent()时会根据返回的status来判断
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) {
//对按键事件来说,要求Connection必须处于空闲状态
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 {
//对Motion事件来说,可以发送事件的条件相对宽松,只要窗口能在0.5s内发送反馈即可
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();
}
可以看出,判断窗口是否可以接受事件的依据有两个:InputPublisher是否被阻塞以及Connection两个队列的状态。
InputPublisher的工作是将事件信息写入InputChannel中,如果窗口端因为某种原因迟迟未能从InputChannel中将事件读取就会导致SocketPair的写入缓冲区满,在执行InputPublisher.publishMotionEvent()后返回的status为WOULD_BLOCK,从而来会将connection->inputPublisherBlocked设为true;
Connection两个队列的状态体现了发送循环的状态。如果两个队列至少有一个队列为空,则表示Connection正处于发送循环的过程中,否则处于空闲状态。
对按键事件来说,仅当Connection处于空闲状态,也就是窗口已经完成对之前事件的响应之后才会发送给窗口。因为之前的输入事件有可能会影响焦点窗口,进而影响按键事件的接受者。例如,用户快速地按下了两次back键,第一个back键将会发送给位于顶端的窗口,这个事件可能会导致窗口关闭,因此第一个back键的处理行为决定了第2个back应该发送给哪个窗口。因此按键事件的发送要求窗口完成对所有之前事件的处理。
而Motion事件的条件则相对宽松些,允许Connection处于发送循环的过程中,但是如果等待队列中的第一个事件没能在0.5s获得反馈,则判定窗口处于未响应状态。这是因为Motion事件具有实时性的特点--用户的意图就是希望输入事件发送给他所看到的窗口,所以不在乎之前事件的处理结果。
3.2 handleTargetsNotReadyLocked()
如果checkWindowReadyForMoreInputLocked()判定窗口无法接收事件,则调用handleTargetsNotReadyLocked()安排重试,或引发ANR,看一下具体实现:
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 == NULL && windowHandle == NULL) {
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 {
//------------------------------------------工作1-------------------------------
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
nsecs_t timeout;
//获取引发ANR的超时时间
if (windowHandle != NULL) {
//如果有目标窗口,则获取由窗口所指定的超时时间
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else if (applicationHandle != NULL) {
//如果没有目标窗口,则从AMS获取超时时间
timeout = applicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else {
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}
//设置mInputTargetWaitCause为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
mInputTargetWaitStartTime = currentTime;//检测到未响应的时间
mInputTargetWaitTimeoutTime = currentTime + timeout;//设置引发ANR的时间
mInputTargetWaitTimeoutExpired = false;//当引发ANR后将被置true
mInputTargetWaitApplicationHandle.clear();
if (windowHandle != NULL) {
mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
}
if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
mInputTargetWaitApplicationHandle = applicationHandle;
}
}
}
if (mInputTargetWaitTimeoutExpired) {
return INPUT_EVENT_INJECTION_TIMED_OUT;
}
////---------------------工作2-------------------------------
if (currentTime >= mInputTargetWaitTimeoutTime) {
//当前事件大于引发ANR的时间后,则引发ANR
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
// Force poll loop to wake up immediately on next iteration once we get the
// ANR response back from the policy.
*nextWakeupTime = LONG_LONG_MIN;
return INPUT_EVENT_INJECTION_PENDING;
} else {
//如果尚未达到引发ANR的时间点,设置nextWakeupTime后返回,等待下次再试
if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
*nextWakeupTime = mInputTargetWaitTimeoutTime;
}
return INPUT_EVENT_INJECTION_PENDING;
}
}
该方法内主要两项工作:
1.如果是第一次发生窗口未响应的情况,则记录下来响应的窗口信息,并设置引发ANR的时间点;
2.在随后的重试过程中,将当前时间点与引发ANR的时间点进行比对,并决定是否引发ANR或再次重试;
3.3.简单总结
InputDispatcher从派发队列中获取了一个事件mPendingEvent,并为它查找目标窗口,然后通过checkWindowReadyForMoreInputLocked()确定此窗口是否可以接收事件。如果可以则将事件放入窗口的Connection对象的发送队列中并启动发送循环,否则调用handleTargetsNotReadyLocked()计算引发ANR的时间点,然后通过返回INPUT_EVENT_INJECTION_PENDING停止对mPendingEvent的派发工作,并通过设置nextWakeupTime使派发循环进入休眠状态。
休眠的过程有可能因为窗口反馈到来、新输入事件到来或新的窗口信息到来而唤醒,派发线程便重新开始对mPendingEvent的派发过程,进而重新寻找目标窗口,再通过checkWindowReadyForMoreInputLocked()检查目标窗口是否准备好接收事件,如果可以接收事件,则将其提交给Connection进行发送,并重置之前所设置的ANR信息。否则再次进入handleTargetsNotReadyLocked(),这时将当前时间与ANR时间进行对比,以决定引发ANR还是再次使派发线程进入休眠。
InputDispatcher在派发按键事件时使用了mFocusedWindowHandle作为目标窗口,这个mFocusedWindowHandle成员变量表示系统中处于焦点状态的窗口,该变量是由mWindowHandles列表来获取的,如代码注释所述,它是WMS所有窗口在输入系统的存在形式,它所保存的布局信息为查找输入窗口提供了依据。因此,每当窗口布局发生变化时,WMS都会通过IMS将所有窗口的信息提交到InputDispatcher,并完成mWindowHandles每一个元素的更新工作。注意mWindowHandles中的窗口顺序是索引越小,ZOrder越大,这与WMS中的窗口列表顺序相反。
3.4.窗口更新
mWindowHandles列表是在setInputWindows()里面赋值的,当有新窗口显示时,在WMS addWindow()内部调用InputMonitor的updateInputWindowsLw()来更新mInputWindowHandles,最后通知InputDispatcher进行更新具体,流程总结如下:
1.窗口对应一个WindowState,在addWindow()时会创建窗口对应的WindowState,在WindowState构造方法内创建InputWindowHandle对象;
2.在创建完WindowState后,接下来会调用InputMonitor的updateInputWindowsLw()来更新InputWindowHandles来通知到InputDispatcher,具体执行过程为:
a.调用RootWindowContainer的forAllWindows()来遍历DisplayContent下的所有WindowState,最终执行addInputWindowHandle()来将WindowState的参数赋值给内部变量InputWindowHandle,然后加入到mInputWindowHandles数组进行管理,对应流程图如下:
updateInputWindowsLw流程.png
b.遍历完所有WindowState得到mInputWindowHandles数组之后,调用InputManagerService的setInputWindows()将mInputWindowHandles数组最终传递给InputDispatcher;整体调用流程如下:
setInputWindows().png
void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
{
AutoMutex _l(mLock);
Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
mWindowHandles = inputWindowHandles;
sp<InputWindowHandle> newFocusedWindowHandle;
bool foundHoveredWindow = false;
for (size_t i = 0; i < mWindowHandles.size(); i++) {
const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {
mWindowHandles.removeAt(i--);
continue;
}
.......
......
}
...............
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
通过以上方法可以看到,在setInputWindows()里面对mWindowHandles进行赋值更新,在使用时会将InputWindowHandle信息封装成native层的InputWindowHandle,通过updateInfo()来创建并封装InputWindowInfo,具体定义是在InputWindow.h,如果需要对窗口做些特殊操作时,可以在findTouchedWindowTargetsLocked()内部通过InputWindowInfo来进行过滤处理;
接着上面的分析,总体来说,findTouchedWindowTargetsLocked()函数包括三项主要工作:
1.根据窗口的点击区域与事件发生的坐标点选取合适的目标窗口。注意其遍历顺序是沿ZOrder由上至下进行遍历,因此ZOreder越靠上,则拥有获取事件的优先权。另外,如果窗口没有在其LayoutParams.flag中指明FLAG_NOT_TOUCH_MODAL选项,说明是一个模式窗口。模式窗口将会阻止点击事件被派发给位于其下的窗口,无论点击事件是否发生在它的可点击区域内。
2.检查所找的的窗口是否可以接收新的按键事件。这个检查工作是由checkWindowReadyForMoreInputLocked()函数完成的。如果窗口尚无法接收事件,则说明此窗口有可能发生ANR。handleTargetsNotReadyLocked()会记录下这一事实,并将injectionResult设置为PENDING,要求下次派发线程的循环中重试此事件的派发。因为检查窗口是否可以接收新的输入事件需要我们清楚地理解向窗口派发事件以及窗口对事件做出反馈的过程,因此这里不做探讨。
3.如果找到的窗口可以接收新的事件,则由addWindowTargetLocked()生成一个InputTarget,并放入参数inputTargets列表中。由于InputTarget中几乎所有字段都可以从InputWindowHandle中找到。
4.dispatchEventLocked()
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
//该方法会最终调用到PowerManagerService内部的userActivityFromNative()方法,用来表示
//用户点击了当前界面,主要用于屏保相关的倒计时打断处理
pokeUserActivityLocked(eventEntry);
for (size_t i = 0; i < inputTargets.size(); i++) {
const InputTarget& inputTarget = inputTargets.itemAt(i);
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
}
}
}
在dispatchEventLocked()内部,会遍历inputTarget,然后根据connectionIndex获取到Connection,最后将connection作为参数传入prepareDispatchCycleLocked()执行:
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
if (connection->status != Connection::STATUS_NORMAL) {
return;
}
// Split a motion event if needed.
if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);
MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
MotionEntry* splitMotionEntry = splitMotionEvent(
originalMotionEntry, inputTarget->pointerIds);
if (!splitMotionEntry) {
return; // split event was dropped
}
enqueueDispatchEntriesLocked(currentTime, connection,
splitMotionEntry, inputTarget);
splitMotionEntry->release();
return;
}
}
// Not splitting. Enqueue dispatch entries for the event as is.
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}
接下来会执行enqueueDispatchEntriesLocked():
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
bool wasEmpty = connection->outboundQueue.isEmpty();
// Enqueue dispatch entries for the requested modes.
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
......
// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty && !connection->outboundQueue.isEmpty()) {
startDispatchCycleLocked(currentTime, connection);
}
}
接下来执行startDispatchCycleLocked():
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
while (connection->status == Connection::STATUS_NORMAL
&& !connection->outboundQueue.isEmpty()) {
DispatchEntry* dispatchEntry = connection->outboundQueue.head;
dispatchEntry->deliveryTime = currentTime;
// Publish the event.
status_t status;
EventEntry* eventEntry = dispatchEntry->eventEntry;
switch (eventEntry->type) {
....
case EventEntry::TYPE_MOTION: {
MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
PointerCoords scaledCoords[MAX_POINTERS];
const PointerCoords* usingCoords = motionEntry->pointerCoords;
.......
.......
// Publish the motion event.
status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
motionEntry->deviceId, motionEntry->source, motionEntry->displayId,
dispatchEntry->resolvedAction, motionEntry->actionButton,
dispatchEntry->resolvedFlags, motionEntry->edgeFlags,
motionEntry->metaState, motionEntry->buttonState,
xOffset, yOffset, motionEntry->xPrecision, motionEntry->yPrecision,
usingCoords);
break;
}
//执行出入队列操作
connection->outboundQueue.dequeue(dispatchEntry);
traceOutboundQueueLengthLocked(connection);
connection->waitQueue.enqueueAtTail(dispatchEntry);
traceWaitQueueLengthLocked(connection);
}
}
可以看到,最终通过Connection调用到InputPublisher内部的publishMotionEvent()方法开启事件发送,然后执行出入对列操作,connection->outboundQueue表示要处理的队列,connection->waitQueue正在等待处理的队列;
5.handleReceiveCallback()
在发送完事件后,Java层window收到事件处理完成后需要给一个反馈,即finishInputEvent(),InputDispatcher通过fd监听会回调handleReceiveCallback()方法:
int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
InputDispatcher* d = static_cast<InputDispatcher*>(data);
{ // acquire lock
AutoMutex _l(d->mLock);
ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd);
......
bool notify;
//根据可读的InputChannel的描述符fd获取对应的Connection对象
sp<Connection> connection = d->mConnectionsByFd.valueAt(connectionIndex);
if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
.......
nsecs_t currentTime = now();
bool gotOne = false;
status_t status;
for (;;) {
uint32_t seq;
bool handled;
//在循环中不断地读取尽可能多的反馈信息
status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
if (status) {
break;
}
//调用finishDispatchCycleLocked()函数完成对反馈的处理
d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
gotOne = true;
}
if (gotOne) {
//执行doDispatchCycleFinishedLockedInterruptible
d->runCommandsLockedInterruptible();
if (status == WOULD_BLOCK) {
return 1;
}
}
notify = status != DEAD_OBJECT || !connection->monitor;
......
} else {
notify = !connection->monitor;
......
}
// Unregister the channel.
d->unregisterInputChannelLocked(connection->inputChannel, notify);
return 0; // remove the callback
} // release lock
}
在执行finishDispatchCycleLocked()内部会执行onDispatchCycleFinishedLocked(),然后执行doDispatchCycleFinishedLockedInterruptible:
void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
CommandEntry* commandEntry) {
sp<Connection> connection = commandEntry->connection;
nsecs_t finishTime = commandEntry->eventTime;
uint32_t seq = commandEntry->seq;
bool handled = commandEntry->handled;
//从waitQueue中,按照序号取出反馈对应的事件
DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
if (dispatchEntry) {
nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
.......
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;
}
if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
//将事件从waitQueue中移除
connection->waitQueue.dequeue(dispatchEntry);
traceWaitQueueLengthLocked(connection);
//取出outboundQueue中的事件继续处理
if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
connection->outboundQueue.enqueueAtHead(dispatchEntry);
traceOutboundQueueLengthLocked(connection);
} else {
releaseDispatchEntryLocked(dispatchEntry);
}
}
// 启动下一次循环
startDispatchCycleLocked(now(), connection);
}
}
该方法内部主要执行了两项工作:
1.将事件从Connection的waitQueue队列中删除,该删除动作标志着此事件的派发流程完成,也意味着这个事件经过漫长的加工、传递之旅后生命的结束。
2.调用startDispatchCycleLocked()函数继续尝试发送队列中的下一个事件;
6.事件拦截-dispatchKeyLocked()
前面已经分析了dispatchMotionLocked(),dispatchKeyLocked()的实现与dispatchMotionLocked()类似,不同点在于,按键事件在派发前,会向DispatcherPolicy询问一次后续的派发策略,查询结果将被放置在KeyEntry::interceptKeyResult中,此次派发策略的查询主要是为了给DispatcherPolicy优先处理按键事件的机会,并决定是否将此按键派发给用户。
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
.......
.......
//如果上次的策略查询结果是TRY_AGAIN_LATER,表示上次进行策略查询时,DispatcherPolicy尚未决定好对此事件所使用的策略,
//要求InputDispatcher等一段时间后再做策略查询。此时要检查是否可以再进行策略查询
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
if (currentTime < entry->interceptKeyWakeupTime) {
//派发线程被过早地唤醒,设置nextWakeupTime后进入休眠状态
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) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
//发送一个command,在这个command中进行策略查询
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
if (mFocusedWindowHandle != NULL) {
commandEntry->inputWindowHandle = mFocusedWindowHandle;
}
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) {
//如果派发策略的查询结果是SKIP,则表示InputDispatcher需要丢弃这个事件
if (*dropReason == DROP_REASON_NOT_DROPPED) {
*dropReason = DROP_REASON_POLICY;
}
}
......
//回到通用的派发流程中
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
可以看到,大多数情况下,按键事件都需要两次派发循环才能完成派发工作。按键事件第一次进入dispatchKeyLocked()函数时,其interceptKeyResult为INTERCEPT_KEY_RESULT_UNKNOWN,因此需要向DispatcherPolicy询问派发策略,询问过程是通过一个command完成的,因此dispatchKeyLocked()将这个Command加入InputDispatcher的命令队列之后就直接返回;
接下来在dispatchOnce()内部的runCommandsLockedInterruptible()来执行这个Command命令然后执行doInterceptKeyBeforeDispatchingLockedInterruptible()函数,向DispatcherPolicy查询派发策略,并将查询结果保存在interceptKeyResult中,命令完成后[nextWakeupTime = LONG_LONG_MIN,接着执行dispatchOnce()]的下次派发循环将再次进入dispatchKeyLocked(),此时便可根据interceptKeyResult做出不同的处理;
接下来看一下doInterceptKeyBeforeDispatchingLockedInterruptible()这个方法:
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
CommandEntry* commandEntry) {
KeyEntry* entry = commandEntry->keyEntry;
KeyEvent event;
initializeKeyEvent(&event, entry);
mLock.unlock();
nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
&event, entry->policyFlags);
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();
}
从以上方法可以看到:为按键询问派发策略的函数是DispatcherPolicy的interceptKeyBeforeDispatching()。这个函数对按键事件进行处理,并返回延迟派发的时间delay。delay的三种取值范围决定了interceptKeyResult的三种取值:
1.INTERCEPT_KEY_RESULT_SKIP,当delay小于0时,表示DispatcherPolicy(PhoneWindowManager)自己处理了按键事件,或不希望此事件被派发给用户;
2.INTERCEPT_KEYRESULT_CONTINUE,当delay等于0时,表示DispatcherPolicy(PhoneWindowManager)认为此事件可以正常地派发给用户;
3.INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,当delay大于0时,表示DispatcherPolicy(PhoneWindowManager)尚未决定好如何处理这个按键事件,先进行等待处理[比如组合键按下执行截屏];
7.InterceptKeyBeforeDispatching()
由前面的文章Android IMS原理解析介绍,在创建InputDispatcher时,传入的InputDIspatcherPolicyInterface的实现是NativeInputManager,所以看一下NativeInputManager内部该方法的实现:
nsecs_t NativeInputManager::interceptKeyBeforeDispatching(
const sp<InputWindowHandle>& inputWindowHandle,
const KeyEvent* keyEvent, uint32_t policyFlags) {
.......
nsecs_t result = 0;
if (policyFlags & POLICY_FLAG_TRUSTED) {
JNIEnv* env = jniEnv();
jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
if (keyEventObj) {
jlong delayMillis = env->CallLongMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeDispatching,
inputWindowHandleObj, keyEventObj, policyFlags);
........
env->DeleteLocalRef(inputWindowHandleObj);
}
return result;
}
在interceptKeyBeforeDispatching()回调的是Java层InputManagerService的接口interceptKeyBeforeDispatching(),经过InputMonitor最终调用的是PhoneWindowManager的interceptKeyBeforeDispatching():
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
......
......
if (keyCode == KeyEvent.KEYCODE_HOME) {
......
return -1;
} else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
return 0;
} else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
if (!keyguardOn) {
if (down && repeatCount == 0) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
return -1;
}
.....
.....
// Let the application handle the key.
return 0;
}
通过该方法可以看到,在 PhoneWindowManager里面对有些全局事件都返回-1进行了拦截,比如:当KeyEvent.KEYCODE_APP_SWITCH时,执行toggleRecentApps()触发最近使用,如果不处理,最后返回0将事件派发到应用。
8.interceptKeyBeforeQueueing()与interceptMotionBeforeQueueing()
前面讲到派发事件拦截,这里也介绍一下事件派发前进行处理方法interceptKeyBeforeQueueing()与interceptMotionBeforeQueueing(),跟interceptKeyBeforeDispatching()实现类似,实现其实并不在IMS中,对它们的调用由IMS转给WMS的InputMonitor,然后再由InputMonitor转给PhoneWindowManager内部来实现的;
接着上面分析,该方法是在notifyKey()及notifyMotion()内部调用的,因此运行在InputReader线程,其主要作用就是返回policyFlags派发策略,主要用来是否要ACTION_PASS_TO_USER,看一下代码实现:
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
......
int result;
case KeyEvent.KEYCODE_ENDCALL: {
result &= ~ACTION_PASS_TO_USER;
break;
case KeyEvent.KEYCODE_POWER: {
// Any activity on the power button stops the accessibility shortcut
cancelPendingAccessibilityShortcutAction();
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactive);
} else {
interceptPowerKeyUp(event, interactive, canceled);
}
break;
}
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN:
// fall through
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP:
// fall through
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
// fall through
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
result &= ~ACTION_PASS_TO_USER;
interceptSystemNavigationKey(event);
break;
}
case KeyEvent.KEYCODE_WAKEUP: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = true;
break;
}
.......
return result;
}
从上面方法可以看到,有些KEY在处理后返回的结果移除ACTION_PASS_TO_USER,在dispatchOnceInnerLocked()函数中会将事件丢弃,代码处理如下:
DropReason dropReason = DROP_REASON_NOT_DROPPED;
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
dropReason = DROP_REASON_POLICY;
} else if (!mDispatchEnabled) {
dropReason = DROP_REASON_DISABLED;
}
比如:WAKE_UP、POWER等,系统可能认为此次点击可以将设备唤醒,设备唤醒操作发生在输入事件进入派发队列之前,因此它不会被ANR所阻塞。
接下来将介绍事件发送涉及到的逻辑Android IMS原理解析之InputChannel
网友评论