<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Input系統: InputReader 處理觸控事件 分析了 InputReader 對觸控事件的處理流程,最終的結果是把觸控事件包裝成 NotifyMotionArgs,然後分發給下一環。根據 Input系統: InputManagerService的建立與啟動 可知,下一環是 InputClassifier。然而系統目前並不支援 InputClassifier 的功能,因此事件會被直接傳送到 InputDispatcher。
Input系統: 按鍵事件分發 分析了按鍵事件的分發流程,雖然分析的目標是按鍵事件,但是也從整體上,描繪了事件分發的框架。而本文分析觸控事件的分發流程,也會用到這個框架,因此重複內容不再贅述。
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount, args->pointerProperties)) { return; } uint32_t policyFlags = args->policyFlags; // 來自InputReader/InputClassifier的 motion 事件,都是受信任的 policyFlags |= POLICY_FLAG_TRUSTED; android::base::Timer t; // 1. 對觸控事件執行截斷策略 // 觸控事件入隊前,查詢截斷策略,查詢的結果儲存到引數 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(); if (shouldSendMotionToInputFilterLocked(args)) { // ... } // 包裝成 MotionEntry // Just enqueue a new motion event. std::unique_ptr<MotionEntry> newEntry = std::make_unique<MotionEntry>(args->id, 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->xCursorPosition, args->yCursorPosition, args->downTime, args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0); // 2. 把觸控事件加入收件箱 needWake = enqueueInboundEventLocked(std::move(newEntry)); mLock.unlock(); } // release lock // 3. 如果有必要,喚醒執行緒處理觸控事件 if (needWake) { mLooper->wake(); } }
InputDispatcher 收到觸控事件後的處理流程,與收到按鍵事件的處理流程非常相似
void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when, uint32_t& policyFlags) { bool interactive = mInteractive.load(); 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; } } } void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags) { if (wmActions & WM_ACTION_PASS_TO_USER) { policyFlags |= POLICY_FLAG_PASS_TO_USER; } }
一個觸控事件,必須滿足下面三種情況,才執行截斷策略
另外還需要關注的是,事件在什麼時候是不需要經過截斷策略,有兩種情況
最後還要注意一件事,如果一個觸控事件是不受信任的事件,或者是注入事件,當裝置處於非互動狀態下(通常指滅屏),那麼它不經過截斷策略,也不會傳送給使用者,也就是會被丟棄。
在實際工作中處理的觸控事件,通常都是來自輸入裝置,它肯定是受信任的,而且非注入的,因此它只有在裝置處於非互動狀態下(一般指滅屏)下,非會執行截斷策略,而如果裝置處於互動狀態(通常指亮屏),會被直接分發給視窗。
現在來看下截斷策略的具體實現
// PhoneWindowManager.java public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos, int policyFlags) { // 1. 如果策略要求喚醒螢幕,那麼截斷這個觸控事件 // 一般來說,喚醒螢幕的策略取決於裝置的組態檔 if ((policyFlags & FLAG_WAKE) != 0) { if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion, PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) { // 返回 0,表示截斷觸控事件 return 0; } } // 2. 判斷非互動狀態下,是否截斷事件 if (shouldDispatchInputWhenNonInteractive(displayId, KEYCODE_UNKNOWN)) { // 返回這個值,表示不截斷事件,也就是事件分發給使用者 return ACTION_PASS_TO_USER; } // 忽略 theater mode if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) { wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming, PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION"); } // 3. 預設截斷觸控事件 // 返回0,表示截斷事件 return 0; } private boolean shouldDispatchInputWhenNonInteractive(int displayId, int keyCode) { // Apply the default display policy to unknown displays as well. final boolean isDefaultDisplay = displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY; final Display display = isDefaultDisplay ? mDefaultDisplay : mDisplayManager.getDisplay(displayId); final boolean displayOff = (display == null || display.getState() == STATE_OFF); if (displayOff && !mHasFeatureWatch) { return false; } // displayOff 表示螢幕處於 off 狀態,但是非 off 狀態,並不表示一定是亮屏狀態 // 對於 doze 狀態,螢幕處於 on 狀態,但是螢幕可能仍然是黑的 // 因此,只要螢幕處於 on 狀態,並且顯示了鎖屏,觸控事件不會截斷 if (isKeyguardShowingAndNotOccluded() && !displayOff) { return true; } // 對於觸控事件,keyCode 的值為 KEYCODE_UNKNOWN if (mHasFeatureWatch && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_STEM_PRIMARY || keyCode == KeyEvent.KEYCODE_STEM_1 || keyCode == KeyEvent.KEYCODE_STEM_2 || keyCode == KeyEvent.KEYCODE_STEM_3)) { return false; } // 對於預設螢幕,如果裝置處於夢境狀態,那麼觸控事件不截斷 // 因為 doze 元件需要接收觸控事件,可能會喚醒螢幕 if (isDefaultDisplay) { IDreamManager dreamManager = getDreamManager(); try { if (dreamManager != null && dreamManager.isDreaming()) { return true; } } catch (RemoteException e) { Slog.e(TAG, "RemoteException when checking if dreaming", e); } } // Otherwise, consume events since the user can't see what is being // interacted with. return false; }
截斷策略是否截斷觸控事件,取決於策略的返回值,有兩種情況
下面列舉觸控事件截斷與否的情況,但是要注意一個前提,裝置處於非互動狀態(一般就是指滅屏狀態)
從上面的分析可以總結出了兩條結論
由 Input系統: InputManagerService的建立與啟動 可知,InputDispatcher 通過執行緒迴圈來處理收件箱中的事件,而且一次迴圈只能處理一個事件
void InputDispatcher::dispatchOnce() { nsecs_t nextWakeupTime = LONG_LONG_MAX; { // acquire lock std::scoped_lock _l(mLock); mDispatcherIsAlive.notify_all(); if (!haveCommandsLocked()) { // 1. 分發一個觸控事件 dispatchOnceInnerLocked(&nextWakeupTime); } // 觸控事件的分發過程不會產生命令 if (runCommandsLockedInterruptible()) { nextWakeupTime = LONG_LONG_MIN; } // 2. 計算執行緒下次喚醒的時間點,以便處理 anr const nsecs_t nextAnrCheck = processAnrsLocked(); nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck); if (nextWakeupTime == LONG_LONG_MAX) { mDispatcherEnteredIdle.notify_all(); } } // release lock // 3. 執行緒休眠指定的時長 nsecs_t currentTime = now(); int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime); mLooper->pollOnce(timeoutMillis); }
一次執行緒迴圈處理觸控事件的過程如下
現在來看看如何分發一個觸控事件
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { nsecs_t currentTime = now(); if (!mDispatchEnabled) { resetKeyRepeatLocked(); } if (mDispatchFrozen) { return; } // 這裡是優化 app 切換的延遲 // mAppSwitchDueTime 是 app 切換的超時時間,如果小於當前時間,那麼表明app切換超時了 // 如果app切換超時,那麼在app切換按鍵事件之前的未處理的事件,都將會被丟棄 bool isAppSwitchDue = mAppSwitchDueTime <= currentTime; if (mAppSwitchDueTime < *nextWakeupTime) { *nextWakeupTime = mAppSwitchDueTime; } // mPendingEvent 表示正在處理的事件 if (!mPendingEvent) { if (mInboundQueue.empty()) { // ... } else { // 1. 從收件箱佇列中取出事件 mPendingEvent = mInboundQueue.front(); mInboundQueue.pop_front(); traceInboundQueueLengthLocked(); } // 如果這個事件需要傳遞給使用者,那麼需要同上層的 PowerManagerService,此時有使用者行為,這個作用就是延長亮屏的時間 if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) { pokeUserActivityLocked(*mPendingEvent); } } ALOG_ASSERT(mPendingEvent != nullptr); bool done = false; // 檢測丟棄事件的原因 DropReason dropReason = DropReason::NOT_DROPPED; if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) { // 被截斷策略截斷 dropReason = DropReason::POLICY; } else if (!mDispatchEnabled) { // 一般是由於系統正在系統或者正在關閉 dropReason = DropReason::DISABLED; } if (mNextUnblockedEvent == mPendingEvent) { mNextUnblockedEvent = nullptr; } switch (mPendingEvent->type) { // .... case EventEntry::Type::MOTION: { std::shared_ptr<MotionEntry> motionEntry = std::static_pointer_cast<MotionEntry>(mPendingEvent); if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) { // app 切換超時,導致觸控事件被丟棄 dropReason = DropReason::APP_SWITCH; } if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) { // 10s 之前的事件,已經過期 dropReason = DropReason::STALE; } // 這裡是優化應用無響應的一個措施,會丟棄mNextUnblockedEvent之前的所有觸控事件 if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) { dropReason = DropReason::BLOCKED; } // 2. 分發觸控事件 done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime); break; } // ... } // 3. 如果事件被處理,重置一些狀態,例如 mPendingEvent // 返回 true,就表示已經處理了事件 // 事件被丟棄,或者傳送完畢,都會返回 true // 返回 false,表示暫時不知道如何處理事件,因此執行緒會休眠 // 然後,執行緒再次被喚醒時,再來處理這個事件 if (done) { if (dropReason != DropReason::NOT_DROPPED) { dropInboundEventLocked(*mPendingEvent, dropReason); } mLastDropReason = dropReason; // 重置 mPendingEvent releasePendingEventLocked(); // 立即喚醒,處理下一個事件 *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately } }
Input系統: 按鍵事件分發 已經分析過 InputDispatcher 的執行緒迴圈。而對於觸控事件,是通過 InputDispatcher::dispatchMotionLocked() 進行分發
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { if (!entry->dispatchInProgress) { entry->dispatchInProgress = true; } // 1. 觸控事件有原因需要丟棄,那麼不走後面的分發流程 if (*dropReason != DropReason::NOT_DROPPED) { setInjectionResult(*entry, *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED : InputEventInjectionResult::FAILED); return true; } bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; std::vector<InputTarget> inputTargets; bool conflictingPointerActions = false; InputEventInjectionResult injectionResult; if (isPointerEvent) { // 尋找觸控的視窗,視窗儲存到 inputTargets // 2. 為觸控事件,尋找觸控的視窗 // 觸控的視窗儲存到 inputTargets 中 injectionResult = findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime, &conflictingPointerActions); } else { // ... } if (injectionResult == InputEventInjectionResult::PENDING) { // 返回 false,表示暫時不知道如何處理這個事件,這會導致執行緒休眠 // 等執行緒下次被喚醒時,再來處理這個事件 return false; } // 走到這裡,表示觸控事件已經被處理,因此儲存處理的結果 // 只要返回的不是 InputEventInjectionResult::PENDING // 都表示事件被處理,無論是許可權拒絕還是失敗,或是成功 setInjectionResult(*entry, injectionResult); if (injectionResult == InputEventInjectionResult::PERMISSION_DENIED) { ALOGW("Permission denied, dropping the motion (isPointer=%s)", toString(isPointerEvent)); return true; } if (injectionResult != InputEventInjectionResult::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; } // 走到這裡,表示觸控事件已經成功找到觸控的視窗 // Add monitor channels from event's or focused display. // 3. 觸控事件找到了觸控視窗,在分發給視窗前,儲存 global monitor 到 inputTargets 中 // 開發者選項中的 Show taps 和 Pointer location,利用的 global monitor addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); if (isPointerEvent) { // ... 省略 portal window 處理的程式碼 } if (conflictingPointerActions) { // ... } // 4. 分發事件給 inputTargets 中的所有視窗 dispatchEventLocked(currentTime, entry, inputTargets); return true; }
一個觸控事件的分發過程,可以大致總結為以下幾個過程
InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) { // ... // 6. 對於非 DOWN 事件,獲取已經 DOWN 事件儲存的 TouchState // TouchState 儲存了接收 DOWN 事件的視窗 const TouchState* oldState = nullptr; TouchState tempTouchState; std::unordered_map<int32_t, TouchState>::iterator oldStateIt = mTouchStatesByDisplay.find(displayId); if (oldStateIt != mTouchStatesByDisplay.end()) { oldState = &(oldStateIt->second); tempTouchState.copyFrom(*oldState); } // ... // 第一個條件 newGesture 表示第一個手指按下 // 後面一個條件,表示當前視窗支援 split motion,並且此時有另外一個手指按下 if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ // 觸控點的獲取 x, y 座標 int32_t x; int32_t y; int32_t pointerIndex = getMotionEventActionPointerIndex(action); if (isFromMouse) { // ... } else { x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X)); y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)); } // 這裡檢測是否是第一個手指按下 bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN; // 1. 對於 DOWN 事件,根據觸控事件的x,y座標,尋找觸控視窗 // 引數 addOutsideTargets 表示,只有在第一個手指按下時,如果沒有找到觸控的視窗, // 那麼需要儲存那些可以接受 OUTSIZE 事件的視窗到 tempTouchState newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isDown /*addOutsideTargets*/, true /*addPortalWindows*/); // 省略 ... 處理視窗異常的情況 ... // 2. 獲取所有的 getsture monitor const std::vector<TouchedMonitor> newGestureMonitors = isDown ? selectResponsiveMonitorsLocked( findTouchedGestureMonitorsLocked(displayId, tempTouchState.portalWindows)) : tempTouchState.gestureMonitors; // 既沒有找到觸控點所在的視窗,也沒有找到 gesture monitor,那麼此次尋找觸控視窗的任務就失敗了 if (newTouchedWindowHandle == nullptr && newGestureMonitors.empty()) { ALOGI("Dropping event because there is no touchable window or gesture monitor at " "(%d, %d) in display %" PRId32 ".", x, y, displayId); injectionResult = InputEventInjectionResult::FAILED; goto Failed; } // 走到這裡,表示找到了觸控的視窗,或者找到 gesture monitor if (newTouchedWindowHandle != nullptr) { // 馬上要儲存視窗了,現在獲取視窗的 flag int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS; if (isSplit) { targetFlags |= InputTarget::FLAG_SPLIT; } if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) { targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; } else if (isWindowObscuredLocked(newTouchedWindowHandle)) { targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED; } // Update hover state. if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { newHoverWindowHandle = nullptr; } else if (isHoverAction) { newHoverWindowHandle = newTouchedWindowHandle; } // Update the temporary touch state. // 如果視窗支援 split,那麼用 tempTouchState 儲存視窗的時候,要特別儲存 pointer id BitSet32 pointerIds; if (isSplit) { uint32_t pointerId = entry.pointerProperties[pointerIndex].id; pointerIds.markBit(pointerId); } // 3. tempTouchState 儲存找到的觸控的視窗 // 如果是真的找到的觸控視窗,那麼這裡就是儲存,如果是找到可以接受 OUTSIDE 的視窗,那麼這裡是更新 tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds); } else if (tempTouchState.windows.empty()) { // If no window is touched, set split to true. This will allow the next pointer down to // be delivered to a new window which supports split touch. tempTouchState.split = true; } if (isDown) { // tempTouchState 儲存所有的 gesture monitor // 4. 第一個手指按下時,tempTouchState 儲存 gesture monitor tempTouchState.addGestureMonitors(newGestureMonitors); } } else { // ... } if (newHoverWindowHandle != mLastHoverWindowHandle) { // .... } { // 許可權檢測 ... } // 儲存接收 AMOTION_EVENT_ACTION_OUTSIDE 的視窗 if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // ... } // 第一個手指按下時,儲存桌布視窗 if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // // ... } // 走到這裡,表示沒有異常情況了 injectionResult = InputEventInjectionResult::SUCCEEDED; // 5. 把 tempTouchState 儲存了觸控視窗和gesture monitor,儲存到 inputTargets 中 for (const TouchedWindow& touchedWindow : tempTouchState.windows) { addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.pointerIds, inputTargets); } for (const TouchedMonitor& touchedMonitor : tempTouchState.gestureMonitors) { addMonitoringTargetLocked(touchedMonitor.monitor, touchedMonitor.xOffset, touchedMonitor.yOffset, inputTargets); } // Drop the outside or hover touch windows since we will not care about them // in the next iteration. tempTouchState.filterNonAsIsTouchWindows(); Failed: // ... // 6. 快取 tempTouchState if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { if (tempTouchState.displayId >= 0) { mTouchStatesByDisplay[displayId] = tempTouchState; } else { mTouchStatesByDisplay.erase(displayId); } } return injectionResult; }
為觸控事件尋找觸控視窗的過程,極其複雜。雖然這段程式碼被我省略了很多過程,但是我估計讀者也會看得頭暈。
對於 DOWN 事件
gesture monitor 是為了實現手勢功能而新增的一個視窗。什麼是手勢功能? 例如在螢幕的左邊/右邊,向螢幕中央滑動,會觸發返回手勢。這個手勢功能用來替代導航鍵。在下一篇文章中,我會剖析這個手勢功能的原理。
對於非 DOWN 事件,一般為 MOVE, UP 事件
當分析的程式碼量很大的時候,我們需要有一個整體的觀念。為觸控事件尋找觸控視窗,最終的結果就是把找到的視窗儲存到引數 inputTargets 中,後面會把事件分發給 inputTargets 儲存的視窗。
// addOutsideTargets 在第一個手指按下是為 true // addPortalWindows 值為 true // ignoreDragWindow 預設為 false sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, TouchState* touchState, bool addOutsideTargets, bool addPortalWindows, bool ignoreDragWindow) { if ((addPortalWindows || addOutsideTargets) && touchState == nullptr) { LOG_ALWAYS_FATAL( "Must provide a valid touch state if adding portal windows or outside targets"); } // Traverse windows from front to back to find touched window. // 從前到後,遍歷視窗 const std::vector<sp<InputWindowHandle>>& windowHandles = getWindowHandlesLocked(displayId); for (const sp<InputWindowHandle>& windowHandle : windowHandles) { // ignoreDragWindow 預設為 false if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) { continue; } // 獲取視窗資訊 const InputWindowInfo* windowInfo = windowHandle->getInfo(); // 匹配屬於特定螢幕的視窗 if (windowInfo->displayId == displayId) { auto flags = windowInfo->flags; // 視窗要可見 if (windowInfo->visible) { // 視窗要可觸控 if (!flags.test(InputWindowInfo::Flag::NOT_TOUCHABLE)) { // 檢測是否為觸控模型: 可獲取焦點,並且不允許視窗之外的觸控事件傳送到它後面的視窗 bool isTouchModal = !flags.test(InputWindowInfo::Flag::NOT_FOCUSABLE) && !flags.test(InputWindowInfo::Flag::NOT_TOUCH_MODAL); // 視窗是觸控模型,或者觸控的座標點落在視窗上 if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) { int32_t portalToDisplayId = windowInfo->portalToDisplayId; // 如果是 portal window if (portalToDisplayId != ADISPLAY_ID_NONE && portalToDisplayId != displayId) { if (addPortalWindows) { // For the monitoring channels of the display. // touchState 儲存 portal window touchState->addPortalWindow(windowHandle); } // 遞迴呼叫,獲取 portal display id 下的觸控視窗 return findTouchedWindowAtLocked(portalToDisplayId, x, y, touchState, addOutsideTargets, addPortalWindows); } // 不是 portal window,直接返回找到的視窗 return windowHandle; } } // 走到這裡,表示沒有找到觸控視窗。也就是說,既沒有找到觸控模型的視窗,也沒有找到包含觸控點的視窗 // 當第一個手指按下是,addOutsideTargets 值為 true // NOT_TOUCH_MODAL 和 WATCH_OUTSIDE_TOUCH 一起使用,當第一個手指按下時,如果落在視窗之外 // 視窗會收到 MotionEvent.ACTION_OUTSIDE 事件 if (addOutsideTargets && flags.test(InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH)) { touchState->addOrUpdateWindow(windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, BitSet32(0)); } } } } return nullptr; }
這裡涉及一個 portal window 的概念,由於我沒有找到具體使用的地方,我大致猜測它的意思就是,裝置外接一個螢幕,然後在主螢幕上顯示一個視窗來操作這個外接螢幕。後面的分析,我將略過 portal window 的部分。當然,觸控掌握了觸控事件的分發流程,以後遇到了 portal window 的事情,再來分析,應該沒問題的。
尋找觸控點所在的視窗,其實就是從上到下遍歷所有視窗,然後找到滿足條件的視窗。
視窗首先要滿足前置條件
滿足了所有的前置條件後,只要滿足以下任意一個條件,那麼就找到了觸控點所在的視窗
// InputDispatcher 儲存觸控視窗 void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle, int32_t targetFlags, BitSet32 pointerIds, std::vector<InputTarget>& inputTargets) { std::vector<InputTarget>::iterator it = std::find_if(inputTargets.begin(), inputTargets.end(), [&windowHandle](const InputTarget& inputTarget) { return inputTarget.inputChannel->getConnectionToken() == windowHandle->getToken(); }); const InputWindowInfo* windowInfo = windowHandle->getInfo(); // 建立 InputTarget,並儲存到引數 inputTargets if (it == inputTargets.end()) { InputTarget inputTarget; std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken()); if (inputChannel == nullptr) { ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str()); return; } inputTarget.inputChannel = inputChannel; inputTarget.flags = targetFlags; inputTarget.globalScaleFactor = windowInfo->globalScaleFactor; inputTarget.displaySize = int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight); inputTargets.push_back(inputTarget); it = inputTargets.end() - 1; } ALOG_ASSERT(it->flags == targetFlags); ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor); // 儲存 InputTarget 後,在儲存視窗的座標轉換引數, // 這個引數可以把顯示屏的座標,轉換為視窗的座標 it->addPointers(pointerIds, windowInfo->transform); } // InputDispatcher 儲存 gesture monitor void InputDispatcher::addMonitoringTargetLocked(const Monitor& monitor, float xOffset, float yOffset, std::vector<InputTarget>& inputTargets) { InputTarget target; target.inputChannel = monitor.inputChannel; target.flags = InputTarget::FLAG_DISPATCH_AS_IS; ui::Transform t; t.set(xOffset, yOffset); target.setDefaultPointerTransform(t); inputTargets.push_back(target); }
對於觸控事件,無論是觸控視窗,還是 gesture monitor,都會被轉化為 InputTarget,然後儲存到引數 inputTargets 中。當後面啟動分發迴圈後,觸控事件就會傳送到 inputTargets 儲存的視窗中。
本文從整體上分析了觸控事件的分發過程,很多細節並沒有深入去分析,例如,當視窗無響應時,如何優化事件分發。但是,只要你掌握了基本的流程,這些細節你可以自行分析。
本文的某些分析過程,跨度可能很大,那是因為這些知識已經在前面的文章中講過,如果你閱讀本文,感覺有點困難,那麼請先閱讀前面的文章,打好基礎。
理論的文章總有一些枯燥,但是不妨礙我繼續向前,下一篇文章,將以此為基礎,分析那個代替系統導航欄的手勢功能是如何實現的,這也將作為 Input 系統的收官之作。
以上就是Android開發Input系統觸控事件分發的詳細內容,更多關於Android Input觸控事件分發的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45