Input系統(tǒng)分發(fā)策略及其應(yīng)用示例詳解
引言
Input系統(tǒng): 按鍵事件分發(fā) 從整體上描繪了通用的事件分發(fā)過(guò)程,其中有兩個(gè)比較的環(huán)節(jié),一個(gè)是截?cái)嗖呗裕粋€(gè)是分發(fā)策略。Input系統(tǒng):截?cái)嗖呗缘姆治雠c應(yīng)用 分析了截?cái)嗖呗约捌鋺?yīng)用,本文來(lái)分析分發(fā)策略及其應(yīng)用。
在正式開始分析前,讀者務(wù)必仔細(xì)地閱讀 Input系統(tǒng): 按鍵事件分發(fā) ,了解截?cái)嗖呗院头职l(fā)策略的執(zhí)行時(shí)機(jī)。否則,閱讀本文沒(méi)有意義,反而是浪費(fèi)時(shí)間。
分發(fā)策略原理
根據(jù) Input系統(tǒng): 按鍵事件分發(fā) 可知,分發(fā)策略發(fā)生在事件分發(fā)的過(guò)程中,并且發(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)嗍录?,給系統(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)嗍录?,讓系統(tǒng)優(yōu)先處理事件。那么截?cái)嗖呗耘c分發(fā)策略有什么區(qū)別呢?
由 Input系統(tǒng): 按鍵事件分發(fā) 可知,截?cái)嗖呗允翘幚硪恍┫到y(tǒng)級(jí)的事件,例如 power 鍵亮滅屏,這些事件的處理必須讓用戶感覺(jué)沒(méi)有延時(shí)。假如 power 鍵的事件是在分發(fā)流程中處理的,那么必須等到 power 事件前面的所有事件都處理完畢,才能輪到 power 事件被處理,這就可能讓用戶感覺(jué)系統(tǒng)有點(diǎn)不流暢。
而分發(fā)策略處理一些優(yōu)先級(jí)相對(duì)較低的系統(tǒng)事件,例如 home,app switch 事件。由于分發(fā)策略處于分發(fā)過(guò)程中,因此當(dāng)一個(gè) app 在發(fā)生 anr 期間,無(wú)論我們按多少次 home, app switch 按鍵,系統(tǒng)都會(huì)沒(méi)有響應(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. 如果沒(méi)有命令,分發(fā)一次事件
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// 2. 執(zhí)行命令
// 這個(gè)命令來(lái)自于前一步的事件分發(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ā),不過(guò)事件為了執(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) {
// 還沒(méi)到超時(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é)果表示路過(guò)這個(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è)事件,因此讓底層跳過(guò)這個(gè)事件,也就是不丟棄這個(gè)事件。
- INTERCEPT_KEY_RESULT_CONTINUE : 源碼中沒(méi)有明確處理這個(gè)結(jié)果,很簡(jiǎn)單嘛,那就是繼續(xù)后面的事件分發(fā)流程。
那么,什么時(shí)候上層不知道如何處理一個(gè)事件呢?這是為了實(shí)現(xiàn)組合鍵的功能。
當(dāng)?shù)谝粋€(gè)按鍵按下時(shí),分發(fā)策略不知道用戶到底會(huì)不會(huì)按下第二個(gè)按鍵,因此它會(huì)告訴底層再等等吧,底層因此休眠了。
如果在底層休眠期間,如果用戶按下了第二個(gè)按鍵,那么成功觸發(fā)組合鍵的功能,當(dāng)?shù)讓有褋?lái)時(shí),再次為第一個(gè)按鍵的事件查詢分發(fā)策略,此時(shí)分發(fā)策略知道第一個(gè)按鍵的事件已經(jīng)觸發(fā)了組合鍵功能,因此告訴底層,第一個(gè)按鍵事件截?cái)嗔耍簿褪潜簧蠈犹幚砹?,那么底層就不?huì)分發(fā)這第一個(gè)按鍵的事件。
如果在底層休眠期間,如果沒(méi)有用戶按下了第二個(gè)按鍵。當(dāng)?shù)讓有褋?lái)時(shí),再次為第一個(gè)按鍵的事件查詢分發(fā)策略,此時(shí)分發(fā)策略知道第一個(gè)按鍵事件沒(méi)有觸發(fā)組合鍵的功能,因此告訴底層這個(gè)事件不截?cái)?,繼續(xù)分發(fā)處理吧。
下面以一個(gè)具體的組合鍵以例,來(lái)理解分發(fā)策略,因此讀者務(wù)必仔細(xì)理解上面所分析的。
分發(fā)策略的應(yīng)用 - 組合鍵
以手機(jī)上最常見(jiàn)的截?cái)嘟M合鍵為例,也就是 電源鍵 + 音量下鍵,來(lái)理解分發(fā)策略。但是,請(qǐng)讀者務(wù)必,先仔細(xì)理解上面所分析的。
組合鍵的功能是由 KeyCombinationManager 管理,它在 PhoneWindowManager 的初始化如下
// PhoneWindowManager.java
private void initKeyCombinationRules() {
// KeyCombinationManager 是用來(lái)實(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ī)則
}
很簡(jiǎn)單,創(chuàng)建一個(gè)規(guī)則用于實(shí)現(xiàn)截屏,并保存到了 KeyCombinationManager#mRules 中。
當(dāng)按下電源鍵,首先會(huì)經(jīng)過(guò)截?cái)嗖呗蕴幚?,注意不是分發(fā)策略
// PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// ...
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// 1. 處理按鍵手勢(shì)
// 包括組合鍵
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手勢(shì),這其中就包括組合鍵
// PhoneWindowManager.java
private void handleKeyGesture(KeyEvent event, boolean interactive) {
if (mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
return;
}
// ...
}
現(xiàn)在來(lái)看下 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è)按鍵事件很簡(jiǎn)單,保存了按鍵按下的時(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)過(guò)分發(fā)策略的處理,并且直接被丟棄。這就是窗口為何收不到 power 按鍵事件的根本原因。
截?cái)嗟牡谝粋€(gè)事件,電源事件,已經(jīng)分析完畢?,F(xiàn)在假設(shè)用戶在很短的時(shí)間內(nèi),按鍵下了音量下鍵。經(jīng)過(guò)截?cái)嗖呗詴r(shí),仍然首先經(jīng)過(guò)手勢(shì)處理,此時(shí) KeyCombinationManager 處理第二個(gè)按鍵的過(guò)程如下
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ì)時(shí),現(xiàn)在已經(jīng)觸發(fā)截屏,那么它是否截?cái)嘁袅肯骆I呢?如果音量下鍵不用來(lái)掛斷電話,那就不截?cái)?,這段代碼請(qǐng)讀者自行分析。
我們假設(shè)音量下鍵沒(méi)有被截?cái)嗖呗越財(cái)?,那么?dāng)它經(jīng)過(guò)分發(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è)按鍵事件,窗口都收不到。
截屏功能不是只能通過(guò) 電源鍵 + 音量下鍵 觸發(fā),還可以通過(guò) 音量下鍵 + 電源鍵觸發(fā),但是分析過(guò)程卻和上面不一樣。如果音量下鍵先按,那么分發(fā)策略會(huì)返回一個(gè)稍后再試的結(jié)果,如果讀者有興趣,可以自行分析。
結(jié)束
通過(guò)學(xué)習(xí)本文,我們要達(dá)到學(xué)以致用的目的,其實(shí)最主要的,就是要學(xué)會(huì)如何自定義組合鍵。對(duì)于硬件上新增的按鍵事件,如果要截?cái)?,可以在截?cái)嗖呗?,也可以在分發(fā)策略,根據(jù)自己所認(rèn)為的重要性級(jí)別來(lái)決定。
以上就是Input系統(tǒng)分發(fā)策略及其應(yīng)用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Input系統(tǒng)分發(fā)策略的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android打包上傳AAR文件到Maven倉(cāng)庫(kù)的示例
這篇文章主要介紹了Android打包上傳AAR文件到Maven倉(cāng)庫(kù)的示例,幫助大家更好的理解和學(xué)習(xí)使用Android開發(fā),感興趣的朋友可以了解下2021-03-03
Android自定義漸變式炫酷ListView下拉刷新動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了Android自定義漸變式炫酷ListView下拉刷新動(dòng)畫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
a2sd+狀態(tài)下應(yīng)用程序丟失的解決方法詳細(xì)解析
用了a2sd+和SD分區(qū)方案的朋友可能會(huì)遇到突然某次開機(jī)之后,a2sd+失效,同時(shí)發(fā)生丟失若干應(yīng)用程序的現(xiàn)象或者安裝軟件提示空間不足2013-09-09
解析Android開發(fā)優(yōu)化之:對(duì)Bitmap的內(nèi)存優(yōu)化詳解
在Android應(yīng)用里,最耗費(fèi)內(nèi)存的就是圖片資源。而且在Android系統(tǒng)中,讀取位圖Bitmap時(shí),分給虛擬機(jī)中的圖片的堆棧大小只有8M,如果超出了,就會(huì)出現(xiàn)OutOfMemory異常。所以,對(duì)于圖片的內(nèi)存優(yōu)化,是Android應(yīng)用開發(fā)中比較重要的內(nèi)容2013-05-05

