内容简介:在我们先来回顾上一篇文章讲解的InputDispatcher的dispatchOnceInnerLocked函数:dispatchOnceInnerLocked函数中主要做了5件事,这里只截取了其中的一件事:事件的丢弃。 注释1处的dropReason代表了事件丢弃的原因,它的默认值为DROP_REASON_NOT_DROPPED,代表事件不被丢弃。 注释2处根据mPendingEvent的type做区分处理,这里主要截取了对Motion类型的处理。经过条件语句过滤,会调用注释3处的dispatchMoti
在 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;//1 ... switch (mPendingEvent->type) {//2 ... 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);//3 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); } // 如果事件是需要丢弃的,则返回true,不会去为该事件寻找合适的窗口 if (*dropReason != DROP_REASON_NOT_DROPPED) {//1 setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED); return true; } bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; // 目标窗口信息列表会存储在inputTargets中 Vector<InputTarget> inputTargets;//2 bool conflictingPointerActions = false; int32_t injectionResult; if (isPointerEvent) { //处理点击形式的事件,比如触摸屏幕 injectionResult = findTouchedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime, &conflictingPointerActions);//3 } else { //处理非触摸形式的事件,比如轨迹球 injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime);//4 } //输入事件被挂起,说明找到了窗口并且窗口无响应 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; } //分发目标添加到inputTargets列表中 addMonitoringTargetsLocked(inputTargets);//5 // Dispatch the motion. if (conflictingPointerActions) { CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, "conflicting pointer actions"); synthesizeCancelationEventsForAllConnectionsLocked(options); } //将事件分发给inputTargets列表中的目标 dispatchEventLocked(currentTime, entry, inputTargets);//6 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, //此标记指示MotionEvent位于目标区域内 FLAG_WINDOW_IS_OBSCURED = 1 << 1, ... }; //inputDispatcher与目标窗口的通信管道 sp<InputChannel> inputChannel;//1 //事件派发的标记 int32_t flags; //屏幕坐标系相对于目标窗口坐标系的偏移量 float xOffset, yOffset;//2 //屏幕坐标系相对于目标窗口坐标系的缩放系数 float scaleFactor;//3 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)) { //从MotionEntry中获取坐标点 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();//1 // 遍历窗口,找到触摸过的窗口和窗口之外的外部目标 for (size_t i = 0; i < numWindows; i++) {//2 //获取InputDispatcher中代表窗口的windowHandle sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i); //得到窗口信息windowInfo const InputWindowInfo* windowInfo = windowHandle->getInfo(); if (windowInfo->displayId != displayId) { //如果displayId不匹配,开始下一次循环 continue; } //获取窗口的flag int32_t flags = windowInfo->layoutParamsFlags; //如果窗口时可见的 if (windowInfo->visible) { //如果窗口的flag不为FLAG_NOT_TOUCHABLE(窗口是touchable) if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) { // 如果窗口是focusable或者flag不为FLAG_NOT_FOCUSABLE,则说明该窗口是”可触摸模式“ isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;//3 //如果窗口是”可触摸模式或者坐标点落在窗口之上 if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) { newTouchedWindowHandle = windowHandle;//4 break; // found touched window, exit window loop } } if (maskedAction == AMOTION_EVENT_ACTION_DOWN && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) { //将符合条件的窗口放入TempTouchState中,以便后续处理。 mTempTouchState.addOrUpdateWindow( windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, BitSet32(0));//5 } } } 复制代码
开头先从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");//1 if (!reason.isEmpty()) {//2 //如果窗口没有准备好,则将原因赋值给injectionResult injectionResult = handleTargetsNotReadyLocked(currentTime, entry, NULL, touchedWindow.windowHandle, nextWakeupTime, reason.string());//3 //不做后续的处理,直接跳到Unresponsive标签 goto Unresponsive;//3 } } } ... //代码走到这里,说明窗口已经查找成功 injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;//5 //遍历TempTouchState中的窗口 for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i); //为每个mTempTouchState中的窗口生成InputTargets addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.pointerIds, inputTargets);//6 } //在下一次迭代中,删除外部窗口或悬停触摸窗口 mTempTouchState.filterNonAsIsTouchWindows(); ... Unresponsive: //重置TempTouchState 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); // should already have been set to true pokeUserActivityLocked(eventEntry); //遍历inputTargets列表 for (size_t i = 0; i < inputTargets.size(); i++) { const InputTarget& inputTarget = inputTargets.itemAt(i); //根据inputTarget内部的inputChannel来获取Connection的索引 ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);//1 if (connectionIndex >= 0) { //获取保存在mConnectionsByFd容器中的Connection sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); //根据inputTarget,开始事件发送循环 prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);//2 } 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》卷三
分享大前端、 Java 、跨平台等技术,关注职业发展和行业动态。
以上所述就是小编给大家介绍的《Android输入系统(四)输入事件是如何分发到目标窗口的?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。