本文首发于微信公众号「后厂技术官」
关联系列
解析WindowManager系列
解析WMS系列
深入理解JNI系列
输入系统系列
基于Android 8.1
前言
在Android输入系统(三)InputReader的加工类型和InputDispatcher的分发过程这篇文章中,由于文章篇幅的原因,InputDispatcher的分发过程还有一部分没有讲解,这一部分就是事件分发到目标窗口的过程。
1. 为事件寻找合适的分发目标
我们先来回顾上一篇文章讲解的InputDispatcher的dispatchOnceInnerLocked函数:
frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { ... DropReason dropReason = DROP_REASON_NOT_DROPPED; ... 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 && isStaleEventLocked(currentTime, typedEntry)) { dropReason = DROP_REASON_STALE; } if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) { dropReason = DROP_REASON_BLOCKED; } done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime); break; } default: ALOG_ASSERT(false); break; } ... }
|
dispatchOnceInnerLocked函数中主要做了5件事,这里只截取了其中的一件事:事件的丢弃。
注释1处的dropReason代表了事件丢弃的原因,它的默认值为DROP_REASON_NOT_DROPPED,代表事件不被丢弃。
注释2处根据mPendingEvent的type做区分处理,这里主要截取了对Motion类型的处理。经过条件语句过滤,会调用注释3处的dispatchMotionLocked函数为Motion事件寻找合适的窗口。
frameworks/native/services/inputflinger/InputDispatcher.cpp
bool InputDispatcher::dispatchMotionLocked( nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { if (! entry->dispatchInProgress) { entry->dispatchInProgress = true; logOutboundMotionDetailsLocked("dispatchMotion - ", entry); } if (*dropReason != DROP_REASON_NOT_DROPPED) { setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED); return true; } bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; Vector<InputTarget> inputTargets; bool conflictingPointerActions = false; int32_t injectionResult; if (isPointerEvent) { injectionResult = findTouchedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime, &conflictingPointerActions); } else { injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime); } if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { return false; } setInjectionResultLocked(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; } addMonitoringTargetsLocked(inputTargets); if (conflictingPointerActions) { CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, "conflicting pointer actions"); synthesizeCancelationEventsForAllConnectionsLocked(options); } dispatchEventLocked(currentTime, entry, inputTargets); return true; }
|
注释1处说明事件是需要丢弃的,这时就会直接返回true,不会为该事件寻找窗口,这次的分发任务就没有完成,会在下一次InputDispatcherThread的循环中再次尝试分发。注释3和注释4处会对点击形式和非触摸形式的事件进行处理,将事件处理的结果交由injectionResult。后面会判断injectionResult的值,如果injectionResult的值为INPUT_EVENT_INJECTION_PENDING,这说明找到了窗口并且窗口无响应输入事件被挂起,这时就会返回false;如果injectionResult的值不为INPUT_EVENT_INJECTION_SUCCEEDED,这说明没有找到合适的窗口,输入事件没有分发成功,这时就会返回true。
注释5处会将分发的目标添加到inputTargets列表中,最终在注释6处将事件分发给inputTargets列表中的目标。
从注释2处可以看出inputTargets列表中的存储的是InputTarget结构体:
frameworks/native/services/inputflinger/InputDispatcher.h
struct InputTarget { enum { FLAG_FOREGROUND = 1 << 0, FLAG_WINDOW_IS_OBSCURED = 1 << 1, ... }; sp<InputChannel> inputChannel; int32_t flags; float xOffset, yOffset; float scaleFactor; BitSet32 pointerIds; }
|
InputTarget结构体可以说是inputDispatcher与目标窗口的转换器,其分为两大部分,一个是枚举中存储的inputDispatcher与目标窗口交互的标记,另一部分是inputDispatcher与目标窗口交互参数,比如注释1处的inputChannel,它实际上是一个SocketPair,SocketPair用于进程间双向通信,这非常适合inputDispatcher与目标窗口之间的通信,因为inputDispatcher不仅要将事件分发到目标窗口,同时inputDispatcher也需要得到目标窗口对事件的响应。注释2处的xOffset和yOffset,屏幕坐标系相对于目标窗口坐标系的偏移量,MotionEntry(MotionEvent)中的存储的坐标是屏幕坐标系,因此就需要注释2和注释3处的参数,来将屏幕坐标系转换为目标窗口的坐标系。
2. 处理点击形式的事件
在InputDispatcher的dispatchMotionLocked函数的注释3和注释4处,分别对Motion事件中的点击形式事件和非触摸形式事件做了处理,由于非触摸形式事件不是很常见,这里对点击形式事件进行解析。InputDispatcher的findTouchedWindowTargetsLocked函数如有400多行,这里截取了需要了解的部分,并且分两个部分来讲解。
frameworks/native/services/inputflinger/InputDispatcher.cpp
1.findTouchedWindowTargetsLocked函数part1:
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) { ... if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { 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)); sp<InputWindowHandle> newTouchedWindowHandle; bool isTouchModal = false; 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; } 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; } } if (maskedAction == AMOTION_EVENT_ACTION_DOWN && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) { mTempTouchState.addOrUpdateWindow( windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, BitSet32(0)); } } }
|
开头先从MotionEntry中获取坐标,为了后面筛选窗口用。注释1处获取列表mWindowHandles的InputWindowHandle数量,InputWindowHandle中存储保存了InputWindowInfo,InputWindowInfo中又包含了WindowManager.LayoutParams定义的窗口标志,关于窗口标志见Android解析WindowManager(二)Window的属性这篇文章。除了窗口标志,InputWindowInfo中还包含了InputChannel和窗口各种属性,InputWindowInfo描述了可以接收输入事件的窗口的属性。这么看来,InputWindowHandle和WMS中的WindowState很相似。通俗来讲,WindowState用来代表WMS中的窗口,而InputWindowHandle用来代表输入系统中的窗口。
那么输入系统是如何得到窗口信息的呢?这是因为mWindowHandles列表就是WMS更新到InputDispatcher中的。
注释2处开始遍历mWindowHandles列表中的窗口,找到触摸过的窗口和窗口之外的外部目标。注释3处,如果窗口是focusable或者flag不为FLAG_NOT_FOCUSABLE,则说明该窗口是”可触摸模式“。经过层层的筛选,如果窗口是”可触摸模式“或者坐标点落在窗口之上,会在注释4处,将windowHandle赋值给newTouchedWindowHandle。最后在注释5处,将newTouchedWindowHandle添加到TempTouchState中,以便后续处理。
2.findTouchedWindowTargetsLocked函数part2:
... for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { const TouchedWindow& touchedWindow = mTempTouchState.windows[i]; if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) { String8 reason = checkWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry, "touched"); if (!reason.isEmpty()) { injectionResult = handleTargetsNotReadyLocked(currentTime, entry, NULL, touchedWindow.windowHandle, nextWakeupTime, reason.string()); goto Unresponsive; } } } ... 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); } mTempTouchState.filterNonAsIsTouchWindows(); ... Unresponsive: mTempTouchState.reset(); nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime); updateDispatchStatisticsLocked(currentTime, entry, injectionResult, timeSpentWaitingForApplication); #if DEBUG_FOCUS ALOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, " "timeSpentWaitingForApplication=%0.1fms", injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0); #endif return injectionResult; }
|
注释1处用于检查窗口是否准备好接收更多的输入,并将结果赋值给reason。注释2处,如果reason的值不为空,说明该窗口无法接收更多的输入,注释3处的handleTargetsNotReadyLocked函数会得到无法接收更多输入的原因,赋值给injectionResult,其函数内部会计算窗口处理的时间,如果超时(默认为5秒),就会报ANR,并设置nextWakeupTime的值为LONG_LONG_MIN,强制InputDispatcherThread在下一次循环中立即被唤醒,InputDispatcher会重新开始分发输入事件。这个时候,injectionResult的值为INPUT_EVENT_INJECTION_PENDING。因为窗口无法接收更多的输入,因此会在注释4处,调用goto语句跳到Unresponsive标签,Unresponsive标签中会调用TempTouchState的reset函数来重置TempTouchState。
如果代码已经走到了注释5处,说明窗口已经查找成功,会遍历TempTouchState中的窗口,在注释6处为每个TempTouchState中 的窗口生成inputTargets。
在第一小节,InputDispatcher的dispatchMotionLocked函数的注释6处,会调用InputDispatcher的dispatchEventLocked函数
将事件分发给inputTargets列表中的分发目标,接下来我们来查看下是如何实现的。
3. 向目标窗口发送事件
InputDispatcher的dispatchEventLocked函数如下所示。
frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) { #if DEBUG_DISPATCH_CYCLE ALOGD("dispatchEventToCurrentInputTargets"); #endif ALOG_ASSERT(eventEntry->dispatchInProgress); 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); } 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().string()); #endif } } }
|
遍历inputTargets列表,获取每一个inputTarget,注释1处,根据inputTarget内部的inputChannel来获取Connection的索引,再根据这个索引作为Key值来获取mConnectionsByFd容器中的Connection。Connection可以理解为InputDispatcher和目标窗口的连接,其内部包含了连接的状态、InputChannel、InputWindowHandle和事件队列等等。注释2处调用prepareDispatchCycleLocked函数根据当前的inputTarget,开始事件发送循环。最终会通过inputTarget中的inputChannel来和窗口进行进程间通信,最终将Motion事件发送给目标窗口。
4. Motion事件分发过程总结
结合Android输入系统(二)IMS的启动过程和输入事件的处理和Android输入系统(三)InputReader的加工类型和InputDispatcher的分发过程这两篇文章,可以总结一下Motion事件分发过程,简化为下图。
- Motion事件在InputReaderThread线程中的InputReader进行加工,加工完毕后会判断是否要唤醒InputDispatcherThread,如果需要唤醒,会在InputDispatcherThread的线程循环中不断的用InputDispatcher来分发 Motion事件。
- 将Motion事件交由InputFilter过滤,如果返回值为false,这次Motion事件就会被忽略掉。
- InputReader对Motion事件加工后的数据结构为NotifyMotionArgs,在InputDispatcher的notifyMotion函数中,用NotifyMotionArgs中的事件参数信息构造一个MotionEntry对象。这个MotionEntry对象会被添加到InputDispatcher的mInboundQueue队列的末尾。
- 如果mInboundQueue不为空,取出mInboundQueue队列头部的EventEntry赋值给mPendingEvent。
- 根据mPendingEvent的值,进行事件丢弃处理。
- 调用InputDispatcher的findTouchedWindowTargetsLocked函数,在mWindowHandles窗口列表中为Motion事件找到目标窗口,并为该窗口生成inputTarget。
- 根据inputTarget获取一个Connection,依赖Connection将输入事件发送给目标窗口。
这里只是简单的总结了Motion事件分发过程,和Motion事件类似的还有key事件,就需要读者自行去阅读源码了。
后记
实际上输入系统还有很多内容需要去讲解,比如inputChannel如何和窗口进行进程间通信,InputDispatcher如何得到窗口的反馈,这些内容会在本系列的后续文章中进行讲解。
感谢
《深入理解Android》卷三