Input系統(tǒng)分發(fā)策略及其應(yīng)用示例詳解
引言
Input系統(tǒng): 按鍵事件分發(fā) 從整體上描繪了通用的事件分發(fā)過程,其中有兩個(gè)比較的環(huán)節(jié),一個(gè)是截?cái)嗖呗?,一個(gè)是分發(fā)策略。Input系統(tǒng):截?cái)嗖呗缘姆治雠c應(yīng)用 分析了截?cái)嗖呗约捌鋺?yīng)用,本文來分析分發(fā)策略及其應(yīng)用。
在正式開始分析前,讀者務(wù)必仔細(xì)地閱讀 Input系統(tǒng): 按鍵事件分發(fā) ,了解截?cái)嗖呗院头职l(fā)策略的執(zhí)行時(shí)機(jī)。否則,閱讀本文沒有意義,反而是浪費(fèi)時(shí)間。
分發(fā)策略原理
根據(jù) Input系統(tǒng): 按鍵事件分發(fā) 可知,分發(fā)策略發(fā)生在事件分發(fā)的過程中,并且發(fā)生在事件分發(fā)循環(huán)前,如下
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { // ... if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { // ... } if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { if (INPUTDISPATCHER_SKIP_EVENT_KEY != 0) { // ... } // 創(chuàng)建一個(gè)命令,當(dāng)命令被執(zhí)行的時(shí)候, // 回調(diào) doInterceptKeyBeforeDispatchingLockedInterruptible() std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>( &InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible); sp<IBinder> focusedWindowToken = mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry)); commandEntry->connectionToken = focusedWindowToken; commandEntry->keyEntry = entry; // 把剛創(chuàng)建的命令,加入到隊(duì)列 mCommandQueue 中 postCommandLocked(std::move(commandEntry)); // 返回 false 等待命令執(zhí)行 return false; // wait for the command to run } else { // ... } } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { // ... } // ... // 啟動(dòng)分發(fā)循環(huán),把事件分發(fā)給目標(biāo)窗口 dispatchEventLocked(currentTime, entry, inputTargets); return true; }
如代碼所示,事件在分發(fā)給窗口前,會(huì)先執(zhí)行分發(fā)策略。而執(zhí)行分發(fā)策略的方式是創(chuàng)建一個(gè)命令 CommandEntry,然后保存到命令隊(duì)列中。
當(dāng)命令被執(zhí)行的時(shí)候,會(huì)執(zhí)行 InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible() 函數(shù)。
那么,為何要執(zhí)行分發(fā)策略呢?有如下兩點(diǎn)原因
- 截?cái)嗍录o系統(tǒng)一個(gè)優(yōu)先處理事件的機(jī)會(huì)。
- 實(shí)現(xiàn)組合按鍵功能。
例如,導(dǎo)航欄上的 home, app switch 按鍵的功能就是在這里實(shí)現(xiàn)的,分發(fā)策略會(huì)截?cái)嗨鼈儭?/p>
從 Input系統(tǒng):截?cái)嗖呗缘姆治雠c應(yīng)用 可知,截?cái)嗖呗砸部梢越財(cái)嗍录屜到y(tǒng)優(yōu)先處理事件。那么截?cái)嗖呗耘c分發(fā)策略有什么區(qū)別呢?
由 Input系統(tǒng): 按鍵事件分發(fā) 可知,截?cái)嗖呗允翘幚硪恍┫到y(tǒng)級(jí)的事件,例如 power 鍵亮滅屏,這些事件的處理必須讓用戶感覺沒有延時(shí)。假如 power 鍵的事件是在分發(fā)流程中處理的,那么必須等到 power 事件前面的所有事件都處理完畢,才能輪到 power 事件被處理,這就可能讓用戶感覺系統(tǒng)有點(diǎn)不流暢。
而分發(fā)策略處理一些優(yōu)先級(jí)相對(duì)較低的系統(tǒng)事件,例如 home,app switch 事件。由于分發(fā)策略處于分發(fā)過程中,因此當(dāng)一個(gè) app 在發(fā)生 anr 期間,無論我們按多少次 home, app switch 按鍵,系統(tǒng)都會(huì)沒有響應(yīng)。
好,回歸正題,如上面代碼所示,為了執(zhí)行分發(fā)策略,創(chuàng)建了一個(gè)命令,并保存到命令隊(duì)列,然后就返回了。由 Input系統(tǒng): 按鍵事件分發(fā) 可知,返回到了 InputDispatcher 的線程循環(huán),如下
void InputDispatcher::dispatchOnce() { nsecs_t nextWakeupTime = LONG_LONG_MAX; { // acquire lock std::scoped_lock _l(mLock); mDispatcherIsAlive.notify_all(); // 1. 如果沒有命令,分發(fā)一次事件 if (!haveCommandsLocked()) { dispatchOnceInnerLocked(&nextWakeupTime); } // 2. 執(zhí)行命令 // 這個(gè)命令來自于前一步的事件分發(fā) if (runCommandsLockedInterruptible()) { // 馬上開始下一次的線程循環(huán) nextWakeupTime = LONG_LONG_MIN; } // 處理 ANR ,并返回下一次線程喚醒的時(shí)間。 const nsecs_t nextAnrCheck = processAnrsLocked(); nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck); if (nextWakeupTime == LONG_LONG_MAX) { mDispatcherEnteredIdle.notify_all(); } } // release lock nsecs_t currentTime = now(); int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime); // 3. 線程休眠 timeoutMillis 毫秒 mLooper->pollOnce(timeoutMillis); }
第1步,執(zhí)行事件分發(fā),不過事件為了執(zhí)行分發(fā)策略,創(chuàng)建了一個(gè)命令并保存到命令隊(duì)列中。
第2步,執(zhí)行命令隊(duì)列中的命令。根據(jù)前面創(chuàng)建命令時(shí)所分析的,會(huì)調(diào)用如下函數(shù)
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( CommandEntry* commandEntry) { // 取出命令中保存的按鍵事件 KeyEntry& entry = *(commandEntry->keyEntry); KeyEvent event = createKeyEvent(entry); mLock.unlock(); android::base::Timer t; const sp<IBinder>& token = commandEntry->connectionToken; // 執(zhí)行分發(fā)策略 nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(token, &event, entry.policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms", std::to_string(t.duration().count()).c_str()); } mLock.lock(); // 分發(fā)策略的結(jié)果保存到 KeyEntry::interceptKeyResult 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; } }
果然,命令在執(zhí)行時(shí)候,為事件 KeyEntry 查詢了分發(fā)策略,并把分發(fā)策略的結(jié)果保存到 KeyEntry::interceptKeyResult。
注意,分發(fā)策略最終是由上層執(zhí)行的,如果要截?cái)嗍录?,那么需要返回?fù)值,如果不截?cái)?,返?,如果暫時(shí)不知道如何處理事件,那么返回正值。
第2步執(zhí)行完畢后,會(huì)立刻開始下一次的線程循環(huán)。如果要理解這一點(diǎn),需要理解底層的消息機(jī)制,讀者可能參考我寫的 深入理解Native層的消息機(jī)制。
在下一次線程循環(huán)時(shí),執(zhí)行第1步時(shí),在事件分發(fā)給窗口前,需要根據(jù)分發(fā)策略的結(jié)果,對(duì)事件做進(jìn)一步的處理,如下
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { // ... // 1. 分發(fā)策略的結(jié)果表示稍后再嘗試分發(fā)事件 if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { // 還沒到超時(shí)的時(shí)間,計(jì)算線程休眠的時(shí)間,讓線程休眠 if (currentTime < entry->interceptKeyWakeupTime) { if (entry->interceptKeyWakeupTime < *nextWakeupTime) { *nextWakeupTime = entry->interceptKeyWakeupTime; } return false; // wait until next wakeup } // 重置分發(fā)策略的結(jié)果,為了再一次查詢分發(fā)策略 // 當(dāng)再次查詢分發(fā)策略時(shí),分發(fā)策略會(huì)給出是否截?cái)嗟慕Y(jié)果 entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; entry->interceptKeyWakeupTime = 0; } // Give the policy a chance to intercept the key. if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { // 執(zhí)行分發(fā)策略 // ... } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { // 2. 分發(fā)策略的結(jié)果表示路過這個(gè)事件,也就是丟棄這個(gè)事件 // 這里設(shè)置了丟棄的原因,下面會(huì)根據(jù)這個(gè)原因,丟棄事件,不會(huì)分發(fā)給窗口 if (*dropReason == DropReason::NOT_DROPPED) { *dropReason = DropReason::POLICY; } } // 事件有原因需要丟棄,不執(zhí)行后面的分發(fā)循環(huán) if (*dropReason != DropReason::NOT_DROPPED) { setInjectionResult(*entry, *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED : InputEventInjectionResult::FAILED); mReporter->reportDroppedKey(entry->id); return true; } // ... // 啟動(dòng)分發(fā)循環(huán),把事件分發(fā)給目標(biāo)窗口 dispatchEventLocked(currentTime, entry, inputTargets); return true; }
對(duì)各種分發(fā)結(jié)果的處理如下
- INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER : 上層暫時(shí)不知道如何處理這個(gè)事件,所以告訴底層等一會(huì)再看看。底層收到這個(gè)結(jié)果,會(huì)讓線程休眠指定時(shí)間。當(dāng)時(shí)間到了后,會(huì)把重置分發(fā)策略結(jié)果為 INTERCEPT_KEY_RESULT_UNKNOWN,然后再次查詢分發(fā)策略,此時(shí)分發(fā)策略會(huì)給出一個(gè)明確的結(jié)果,到底是截?cái)噙€是不截?cái)唷?/li>
- INTERCEPT_KEY_RESULT_SKIP :上層截?cái)嗔诉@個(gè)事件,因此讓底層跳過這個(gè)事件,也就是不丟棄這個(gè)事件。
- INTERCEPT_KEY_RESULT_CONTINUE : 源碼中沒有明確處理這個(gè)結(jié)果,很簡單嘛,那就是繼續(xù)后面的事件分發(fā)流程。
那么,什么時(shí)候上層不知道如何處理一個(gè)事件呢?這是為了實(shí)現(xiàn)組合鍵的功能。
當(dāng)?shù)谝粋€(gè)按鍵按下時(shí),分發(fā)策略不知道用戶到底會(huì)不會(huì)按下第二個(gè)按鍵,因此它會(huì)告訴底層再等等吧,底層因此休眠了。
如果在底層休眠期間,如果用戶按下了第二個(gè)按鍵,那么成功觸發(fā)組合鍵的功能,當(dāng)?shù)讓有褋頃r(shí),再次為第一個(gè)按鍵的事件查詢分發(fā)策略,此時(shí)分發(fā)策略知道第一個(gè)按鍵的事件已經(jīng)觸發(fā)了組合鍵功能,因此告訴底層,第一個(gè)按鍵事件截?cái)嗔?,也就是被上層處理了,那么底層就不?huì)分發(fā)這第一個(gè)按鍵的事件。
如果在底層休眠期間,如果沒有用戶按下了第二個(gè)按鍵。當(dāng)?shù)讓有褋頃r(shí),再次為第一個(gè)按鍵的事件查詢分發(fā)策略,此時(shí)分發(fā)策略知道第一個(gè)按鍵事件沒有觸發(fā)組合鍵的功能,因此告訴底層這個(gè)事件不截?cái)?,繼續(xù)分發(fā)處理吧。
下面以一個(gè)具體的組合鍵以例,來理解分發(fā)策略,因此讀者務(wù)必仔細(xì)理解上面所分析的。
分發(fā)策略的應(yīng)用 - 組合鍵
以手機(jī)上最常見的截?cái)嘟M合鍵為例,也就是 電源鍵 + 音量下鍵,來理解分發(fā)策略。但是,請(qǐng)讀者務(wù)必,先仔細(xì)理解上面所分析的。
組合鍵的功能是由 KeyCombinationManager 管理,它在 PhoneWindowManager 的初始化如下
// PhoneWindowManager.java private void initKeyCombinationRules() { // KeyCombinationManager 是用來實(shí)現(xiàn)組合按鍵功能的類 mKeyCombinationManager = new KeyCombinationManager(); // 配置默認(rèn)為 true final boolean screenshotChordEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableScreenshotChord); if (screenshotChordEnabled) { // 添加 電源鍵 + 音量下鍵 組合按鍵規(guī)則 mKeyCombinationManager.addRule( new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { @Override void execute() { mPowerKeyHandled = true; // 截屏 interceptScreenshotChord(); } @Override void cancel() { cancelPendingScreenshotChordAction(); } }); } // ... 省略其它組合鍵的規(guī)則 }
很簡單,創(chuàng)建一個(gè)規(guī)則用于實(shí)現(xiàn)截屏,并保存到了 KeyCombinationManager#mRules 中。
當(dāng)按下電源鍵,首先會(huì)經(jīng)過截?cái)嗖呗蕴幚?,注意不是分發(fā)策略
// PhoneWindowManager.java public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { // ... if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { // 1. 處理按鍵手勢 // 包括組合鍵 handleKeyGesture(event, interactiveAndOn); } switch (keyCode) { // ... case KeyEvent.KEYCODE_POWER: { // 2. power 按鍵事件是不傳遞給用戶的 result &= ~ACTION_PASS_TO_USER; // .. break; } // ... } // ... return result; }
第2步,截?cái)嗖呗詴?huì)截?cái)嚯娫窗存I事件。
第1步,截?cái)嗖呗蕴幚戆存I手勢,這其中就包括組合鍵
// PhoneWindowManager.java private void handleKeyGesture(KeyEvent event, boolean interactive) { if (mKeyCombinationManager.interceptKey(event, interactive)) { // handled by combo keys manager. mSingleKeyGestureDetector.reset(); return; } // ... }
現(xiàn)在來看下 KeyCombinationManager 如何處理截屏功能的第一個(gè)按鍵事件,也就是電源事件
boolean interceptKey(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); final long eventTime = event.getEventTime(); // 交互狀態(tài),一般指亮屏的狀態(tài) // 從這里可以看出,組合鍵的功能,必須在交互狀態(tài)下執(zhí)行 if (interactive && down) { if (mDownTimes.size() > 0) { // ... } if (mDownTimes.get(keyCode) == 0) { // 1. 記錄按鍵按下的時(shí)間 mDownTimes.put(keyCode, eventTime); } else { // ignore old key, maybe a repeat key. return false; } if (mDownTimes.size() == 1) { mTriggeredRule = null; // 2. 獲取所有與按鍵相關(guān)的規(guī)則,保存到 mActiveRules forAllRules(mRules, (rule)-> { if (rule.shouldInterceptKey(keyCode)) { mActiveRules.add(rule); } }); } else { // ... } } else { // ... } return false; }
KeyCombinationManager 處理組合鍵的第一個(gè)按鍵事件很簡單,保存了按鍵按下的時(shí)間,并找到與這個(gè)按鍵相關(guān)的規(guī)則并保存。
由于電源按鍵事件被截?cái)啵?dāng)執(zhí)行到分發(fā)策略時(shí),如下
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { // ... if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { // ... } if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { // ...不被截?cái)嗟氖录艜?huì)創(chuàng)建命令,用于執(zhí)行分發(fā)策略... return false; // wait for the command to run } else { // 1. 被截?cái)嗟氖录?,繼續(xù)后面的分發(fā)流程,最終會(huì)被丟棄 entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; } } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { // ... } // 2. 如果事件被截?cái)嗔耍蜁?huì)在這里被丟棄 if (*dropReason != DropReason::NOT_DROPPED) { setInjectionResult(*entry, *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED : InputEventInjectionResult::FAILED); mReporter->reportDroppedKey(entry->id); return true; } // ... // 啟動(dòng)分發(fā)循環(huán),把事件分發(fā)給窗口 dispatchEventLocked(currentTime, entry, inputTargets); return true; }
被截?cái)嗖呗越財(cái)嗟氖录粫?huì)經(jīng)過分發(fā)策略的處理,并且直接被丟棄。這就是窗口為何收不到 power 按鍵事件的根本原因。
截?cái)嗟牡谝粋€(gè)事件,電源事件,已經(jīng)分析完畢?,F(xiàn)在假設(shè)用戶在很短的時(shí)間內(nèi),按鍵下了音量下鍵。經(jīng)過截?cái)嗖呗詴r(shí),仍然首先經(jīng)過手勢處理,此時(shí) KeyCombinationManager 處理第二個(gè)按鍵的過程如下
boolean interceptKey(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); final long eventTime = event.getEventTime(); if (interactive && down) { if (mDownTimes.size() > 0) { if (count > 0 && eventTime > mDownTimes.valueAt(0) + COMBINE_KEY_DELAY_MILLIS) { // 第二個(gè)按鍵按下超時(shí) forAllRules(mActiveRules, (rule)-> rule.cancel()); mActiveRules.clear(); return false; } else if (count == 0) { // has some key down but no active rule exist. return false; } } if (mDownTimes.get(keyCode) == 0) { // 保存第二個(gè)按鍵按下的時(shí)間 mDownTimes.put(keyCode, eventTime); } else { // ignore old key, maybe a repeat key. return false; } if (mDownTimes.size() == 1) { // ... } else { // Ignore if rule already triggered. if (mTriggeredRule != null) { return true; } // check if second key can trigger rule, or remove the non-match rule. forAllActiveRules((rule) -> { // 需要在規(guī)則的時(shí)間內(nèi)按下第二個(gè)按鍵,才能觸發(fā)規(guī)則 if (!rule.shouldInterceptKeys(mDownTimes)) { return false; } Log.v(TAG, "Performing combination rule : " + rule); // 觸發(fā)組合鍵規(guī)則 rule.execute(); // 保存已經(jīng)觸發(fā)的規(guī)則 mTriggeredRule = rule; return true; }); // 清空 mActiveRules,保存已經(jīng)觸發(fā)的規(guī)則 mActiveRules.clear(); if (mTriggeredRule != null) { mActiveRules.add(mTriggeredRule); return true; } } } else { // ... } return false; }
根據(jù)代碼可知,只有組合鍵的第二個(gè)按鍵在規(guī)定的時(shí)間內(nèi)按下(150ms),才能觸發(fā)規(guī)則。對(duì)于 電源鍵 + 音量下鍵,就是觸發(fā)截屏。
截?cái)嗖呗栽谔幚戆存I手勢時(shí),現(xiàn)在已經(jīng)觸發(fā)截屏,那么它是否截?cái)嘁袅肯骆I呢?如果音量下鍵不用來掛斷電話,那就不截?cái)?,這段代碼請(qǐng)讀者自行分析。
我們假設(shè)音量下鍵沒有被截?cái)嗖呗越財(cái)啵敲串?dāng)它經(jīng)過分發(fā)策略時(shí),如何處理呢?如下
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { // ... if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { // ... } if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { // 1. 對(duì)于不被截?cái)嗟氖录?,?chuàng)建命令執(zhí)行分發(fā)策略 if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>( &InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible); sp<IBinder> focusedWindowToken = mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry)); commandEntry->connectionToken = focusedWindowToken; commandEntry->keyEntry = entry; postCommandLocked(std::move(commandEntry)); return false; // wait for the command to run } else { // ... } } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { // ... } // ... // 啟動(dòng)分發(fā)循環(huán),分發(fā)事件 dispatchEventLocked(currentTime, entry, inputTargets); return true; }
音量下鍵事件要執(zhí)行分發(fā)策略,分發(fā)策略最終由上層的 PhoneWindowManager 實(shí)現(xiàn),如下
// PhoneWindowManager.java public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags) { // ... final long key_consumed = -1; if (mKeyCombinationManager.isKeyConsumed(event)) { // 返回 -1,表示截?cái)嗍录? return key_consumed; } } // KeyCombinationManager.java boolean isKeyConsumed(KeyEvent event) { if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { return false; } // 在觸發(fā)組合鍵功能時(shí),mTriggeredRule 保存了觸發(fā)的規(guī)則 return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode()); }
由于已經(jīng)觸發(fā)了截屏功能,因此分發(fā)策略對(duì)音量下鍵的處理結(jié)果是 -1,也就是截?cái)嗨?/p>
底層收到這個(gè)截?cái)嘈畔r(shí),就會(huì)丟棄音量下鍵這個(gè)事件,如下
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { // ... if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { // ... } // Give the policy a chance to intercept the key. if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { // ... } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { // 1. 分發(fā)策略的結(jié)果是事件被截?cái)? if (*dropReason == DropReason::NOT_DROPPED) { *dropReason = DropReason::POLICY; } } // 2. 丟棄被截?cái)嗟氖录? if (*dropReason != DropReason::NOT_DROPPED) { setInjectionResult(*entry, *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED : InputEventInjectionResult::FAILED); mReporter->reportDroppedKey(entry->id); return true; } // ... // 啟動(dòng)分發(fā)循環(huán),發(fā)送事件給窗口 dispatchEventLocked(currentTime, entry, inputTargets); return true; }
由于音量下鍵事件被丟棄,因此窗口也收不到這個(gè)事件。其實(shí),組合鍵功能只要觸發(fā),兩個(gè)按鍵事件,窗口都收不到。
截屏功能不是只能通過 電源鍵 + 音量下鍵 觸發(fā),還可以通過 音量下鍵 + 電源鍵觸發(fā),但是分析過程卻和上面不一樣。如果音量下鍵先按,那么分發(fā)策略會(huì)返回一個(gè)稍后再試的結(jié)果,如果讀者有興趣,可以自行分析。
結(jié)束
通過學(xué)習(xí)本文,我們要達(dá)到學(xué)以致用的目的,其實(shí)最主要的,就是要學(xué)會(huì)如何自定義組合鍵。對(duì)于硬件上新增的按鍵事件,如果要截?cái)?,可以在截?cái)嗖呗裕部梢栽诜职l(fā)策略,根據(jù)自己所認(rèn)為的重要性級(jí)別來決定。
以上就是Input系統(tǒng)分發(fā)策略及其應(yīng)用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Input系統(tǒng)分發(fā)策略的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入淺析Android手機(jī)衛(wèi)士保存密碼時(shí)進(jìn)行md5加密
一般的手機(jī)沒有root權(quán)限,進(jìn)不去data/data目錄,當(dāng)手機(jī)刷機(jī)了后,擁有root權(quán)限,就可以進(jìn)入data/data目錄,查看我們保存的密碼文件,因此我們需要對(duì)存入的密碼進(jìn)行MD5加密,接下來通過本文給大家介紹Android手機(jī)衛(wèi)士保存密碼時(shí)進(jìn)行md5加密,需要的朋友一起學(xué)習(xí)吧2016-04-04基于flutter?sound插件實(shí)現(xiàn)錄音與播放功能
這篇文章主要介紹了基于flutter?sound插件實(shí)現(xiàn)錄音與播放功能,介紹了如何錄音,如何播放本地和遠(yuǎn)程音頻文件,以及如何實(shí)現(xiàn)動(dòng)畫,在錄制完音頻文件后如何上傳,這些都是我們平常使用這個(gè)功能會(huì)遇到的問題。在使用的過程中遇到的問題也有列出,需要的朋友可以參考下2022-05-05Android 檢測鍵盤顯示或隱藏鍵盤的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 檢測鍵盤顯示或隱藏鍵盤的實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-07-07Android自定義控件之水平圓點(diǎn)加載進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android自定義控件之水平圓點(diǎn)加載進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06Android中TextView實(shí)現(xiàn)部分文字可點(diǎn)擊跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了Android中TextView實(shí)現(xiàn)部分文字可點(diǎn)擊跳轉(zhuǎn)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android 中NumberPicker,DatePicker與DatePickerDialog中分割顏色的修改實(shí)例代
這篇文章主要介紹了Android 中NumberPicker,DatePicker與DatePickerDialog中分割顏色的修改實(shí)例代碼的相關(guān)資料,這里提供實(shí)例代碼,需要的朋友可以參考下2017-03-03