網(wǎng)站首頁 編程語言 正文
引言
Input系統(tǒng): 按鍵事件分發(fā) 從整體上描繪了通用的事件分發(fā)過程,其中有兩個比較的環(huán)節(jié),一個是截斷策略,一個是分發(fā)策略。Input系統(tǒng):截斷策略的分析與應(yīng)用 分析了截斷策略及其應(yīng)用,本文來分析分發(fā)策略及其應(yīng)用。
在正式開始分析前,讀者務(wù)必仔細地閱讀 Input系統(tǒng): 按鍵事件分發(fā) ,了解截斷策略和分發(fā)策略的執(zhí)行時機。否則,閱讀本文沒有意義,反而是浪費時間。
分發(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)建一個命令,當命令被執(zhí)行的時候,
// 回調(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)建的命令,加入到隊列 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) {
// ...
}
// ...
// 啟動分發(fā)循環(huán),把事件分發(fā)給目標窗口
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
如代碼所示,事件在分發(fā)給窗口前,會先執(zhí)行分發(fā)策略。而執(zhí)行分發(fā)策略的方式是創(chuàng)建一個命令 CommandEntry,然后保存到命令隊列中。
當命令被執(zhí)行的時候,會執(zhí)行 InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible() 函數(shù)。
那么,為何要執(zhí)行分發(fā)策略呢?有如下兩點原因
- 截斷事件,給系統(tǒng)一個優(yōu)先處理事件的機會。
- 實現(xiàn)組合按鍵功能。
例如,導航欄上的 home, app switch 按鍵的功能就是在這里實現(xiàn)的,分發(fā)策略會截斷它們。
從 Input系統(tǒng):截斷策略的分析與應(yīng)用 可知,截斷策略也可以截斷事件,讓系統(tǒng)優(yōu)先處理事件。那么截斷策略與分發(fā)策略有什么區(qū)別呢?
由 Input系統(tǒng): 按鍵事件分發(fā) 可知,截斷策略是處理一些系統(tǒng)級的事件,例如 power 鍵亮滅屏,這些事件的處理必須讓用戶感覺沒有延時。假如 power 鍵的事件是在分發(fā)流程中處理的,那么必須等到 power 事件前面的所有事件都處理完畢,才能輪到 power 事件被處理,這就可能讓用戶感覺系統(tǒng)有點不流暢。
而分發(fā)策略處理一些優(yōu)先級相對較低的系統(tǒng)事件,例如 home,app switch 事件。由于分發(fā)策略處于分發(fā)過程中,因此當一個 app 在發(fā)生 anr 期間,無論我們按多少次 home, app switch 按鍵,系統(tǒng)都會沒有響應(yīng)。
好,回歸正題,如上面代碼所示,為了執(zhí)行分發(fā)策略,創(chuàng)建了一個命令,并保存到命令隊列,然后就返回了。由 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í)行命令
// 這個命令來自于前一步的事件分發(fā)
if (runCommandsLockedInterruptible()) {
// 馬上開始下一次的線程循環(huán)
nextWakeupTime = LONG_LONG_MIN;
}
// 處理 ANR ,并返回下一次線程喚醒的時間。
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)建了一個命令并保存到命令隊列中。
第2步,執(zhí)行命令隊列中的命令。根據(jù)前面創(chuàng)建命令時所分析的,會調(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í)行時候,為事件 KeyEntry 查詢了分發(fā)策略,并把分發(fā)策略的結(jié)果保存到 KeyEntry::interceptKeyResult。
注意,分發(fā)策略最終是由上層執(zhí)行的,如果要截斷事件,那么需要返回負值,如果不截斷,返回0,如果暫時不知道如何處理事件,那么返回正值。
第2步執(zhí)行完畢后,會立刻開始下一次的線程循環(huán)。如果要理解這一點,需要理解底層的消息機制,讀者可能參考我寫的 深入理解Native層的消息機制。
在下一次線程循環(huán)時,執(zhí)行第1步時,在事件分發(fā)給窗口前,需要根據(jù)分發(fā)策略的結(jié)果,對事件做進一步的處理,如下
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) {
// 還沒到超時的時間,計算線程休眠的時間,讓線程休眠
if (currentTime < entry->interceptKeyWakeupTime) {
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
return false; // wait until next wakeup
}
// 重置分發(fā)策略的結(jié)果,為了再一次查詢分發(fā)策略
// 當再次查詢分發(fā)策略時,分發(fā)策略會給出是否截斷的結(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é)果表示路過這個事件,也就是丟棄這個事件
// 這里設(shè)置了丟棄的原因,下面會根據(jù)這個原因,丟棄事件,不會分發(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;
}
// ...
// 啟動分發(fā)循環(huán),把事件分發(fā)給目標窗口
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
對各種分發(fā)結(jié)果的處理如下
- INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER : 上層暫時不知道如何處理這個事件,所以告訴底層等一會再看看。底層收到這個結(jié)果,會讓線程休眠指定時間。當時間到了后,會把重置分發(fā)策略結(jié)果為 INTERCEPT_KEY_RESULT_UNKNOWN,然后再次查詢分發(fā)策略,此時分發(fā)策略會給出一個明確的結(jié)果,到底是截斷還是不截斷。
- INTERCEPT_KEY_RESULT_SKIP :上層截斷了這個事件,因此讓底層跳過這個事件,也就是不丟棄這個事件。
- INTERCEPT_KEY_RESULT_CONTINUE : 源碼中沒有明確處理這個結(jié)果,很簡單嘛,那就是繼續(xù)后面的事件分發(fā)流程。
那么,什么時候上層不知道如何處理一個事件呢?這是為了實現(xiàn)組合鍵的功能。
當?shù)谝粋€按鍵按下時,分發(fā)策略不知道用戶到底會不會按下第二個按鍵,因此它會告訴底層再等等吧,底層因此休眠了。
如果在底層休眠期間,如果用戶按下了第二個按鍵,那么成功觸發(fā)組合鍵的功能,當?shù)讓有褋頃r,再次為第一個按鍵的事件查詢分發(fā)策略,此時分發(fā)策略知道第一個按鍵的事件已經(jīng)觸發(fā)了組合鍵功能,因此告訴底層,第一個按鍵事件截斷了,也就是被上層處理了,那么底層就不會分發(fā)這第一個按鍵的事件。
如果在底層休眠期間,如果沒有用戶按下了第二個按鍵。當?shù)讓有褋頃r,再次為第一個按鍵的事件查詢分發(fā)策略,此時分發(fā)策略知道第一個按鍵事件沒有觸發(fā)組合鍵的功能,因此告訴底層這個事件不截斷,繼續(xù)分發(fā)處理吧。
下面以一個具體的組合鍵以例,來理解分發(fā)策略,因此讀者務(wù)必仔細理解上面所分析的。
分發(fā)策略的應(yīng)用 - 組合鍵
以手機上最常見的截斷組合鍵為例,也就是 電源鍵 + 音量下鍵,來理解分發(fā)策略。但是,請讀者務(wù)必,先仔細理解上面所分析的。
組合鍵的功能是由 KeyCombinationManager 管理,它在 PhoneWindowManager 的初始化如下
// PhoneWindowManager.java
private void initKeyCombinationRules() {
// KeyCombinationManager 是用來實現(xiàn)組合按鍵功能的類
mKeyCombinationManager = new KeyCombinationManager();
// 配置默認為 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)建一個規(guī)則用于實現(xiàn)截屏,并保存到了 KeyCombinationManager#mRules 中。
當按下電源鍵,首先會經(jīng)過截斷策略處理,注意不是分發(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步,截斷策略會截斷電源按鍵事件。
第1步,截斷策略處理按鍵手勢,這其中就包括組合鍵
// 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 如何處理截屏功能的第一個按鍵事件,也就是電源事件
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. 記錄按鍵按下的時間
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 處理組合鍵的第一個按鍵事件很簡單,保存了按鍵按下的時間,并找到與這個按鍵相關(guān)的規(guī)則并保存。
由于電源按鍵事件被截斷,當執(zhí)行到分發(fā)策略時,如下
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) {
// ...不被截斷的事件,才會創(chuàng)建命令,用于執(zhí)行分發(fā)策略...
return false; // wait for the command to run
} else {
// 1. 被截斷的事件,繼續(xù)后面的分發(fā)流程,最終會被丟棄
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
}
} else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
// ...
}
// 2. 如果事件被截斷了,就會在這里被丟棄
if (*dropReason != DropReason::NOT_DROPPED) {
setInjectionResult(*entry,
*dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
: InputEventInjectionResult::FAILED);
mReporter->reportDroppedKey(entry->id);
return true;
}
// ...
// 啟動分發(fā)循環(huán),把事件分發(fā)給窗口
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
被截斷策略截斷的事件,不會經(jīng)過分發(fā)策略的處理,并且直接被丟棄。這就是窗口為何收不到 power 按鍵事件的根本原因。
截斷的第一個事件,電源事件,已經(jīng)分析完畢。現(xiàn)在假設(shè)用戶在很短的時間內(nèi),按鍵下了音量下鍵。經(jīng)過截斷策略時,仍然首先經(jīng)過手勢處理,此時 KeyCombinationManager 處理第二個按鍵的過程如下
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) {
// 第二個按鍵按下超時
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) {
// 保存第二個按鍵按下的時間
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ī)則的時間內(nèi)按下第二個按鍵,才能觸發(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ù)代碼可知,只有組合鍵的第二個按鍵在規(guī)定的時間內(nèi)按下(150ms),才能觸發(fā)規(guī)則。對于 電源鍵 + 音量下鍵,就是觸發(fā)截屏。
截斷策略在處理按鍵手勢時,現(xiàn)在已經(jīng)觸發(fā)截屏,那么它是否截斷音量下鍵呢?如果音量下鍵不用來掛斷電話,那就不截斷,這段代碼請讀者自行分析。
我們假設(shè)音量下鍵沒有被截斷策略截斷,那么當它經(jīng)過分發(fā)策略時,如何處理呢?如下
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. 對于不被截斷的事件,創(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) {
// ...
}
// ...
// 啟動分發(fā)循環(huán),分發(fā)事件
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
音量下鍵事件要執(zhí)行分發(fā)策略,分發(fā)策略最終由上層的 PhoneWindowManager 實現(xiàn),如下
// PhoneWindowManager.java
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
// ...
final long key_consumed = -1;
if (mKeyCombinationManager.isKeyConsumed(event)) {
// 返回 -1,表示截斷事件
return key_consumed;
}
}
// KeyCombinationManager.java
boolean isKeyConsumed(KeyEvent event) {
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
return false;
}
// 在觸發(fā)組合鍵功能時,mTriggeredRule 保存了觸發(fā)的規(guī)則
return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode());
}
由于已經(jīng)觸發(fā)了截屏功能,因此分發(fā)策略對音量下鍵的處理結(jié)果是 -1,也就是截斷它。
底層收到這個截斷信息時,就會丟棄音量下鍵這個事件,如下
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é)果是事件被截斷
if (*dropReason == DropReason::NOT_DROPPED) {
*dropReason = DropReason::POLICY;
}
}
// 2. 丟棄被截斷的事件
if (*dropReason != DropReason::NOT_DROPPED) {
setInjectionResult(*entry,
*dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
: InputEventInjectionResult::FAILED);
mReporter->reportDroppedKey(entry->id);
return true;
}
// ...
// 啟動分發(fā)循環(huán),發(fā)送事件給窗口
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
由于音量下鍵事件被丟棄,因此窗口也收不到這個事件。其實,組合鍵功能只要觸發(fā),兩個按鍵事件,窗口都收不到。
截屏功能不是只能通過 電源鍵 + 音量下鍵 觸發(fā),還可以通過 音量下鍵 + 電源鍵觸發(fā),但是分析過程卻和上面不一樣。如果音量下鍵先按,那么分發(fā)策略會返回一個稍后再試的結(jié)果,如果讀者有興趣,可以自行分析。
結(jié)束
通過學習本文,我們要達到學以致用的目的,其實最主要的,就是要學會如何自定義組合鍵。對于硬件上新增的按鍵事件,如果要截斷,可以在截斷策略,也可以在分發(fā)策略,根據(jù)自己所認為的重要性級別來決定。
原文鏈接:https://juejin.cn/post/7195577258906878007
相關(guān)推薦
- 2022-03-17 c++智能指針unique_ptr的使用_C 語言
- 2022-10-25 搭建Redis集群遇到的問題:Waiting for the cluster to join~~~
- 2022-03-20 ubuntu開機自啟動服務(wù)設(shè)置_Linux
- 2022-04-03 Docker?部署RocketMQ的詳細操作_docker
- 2023-01-18 你不知道的C++中namespace和using的用法實例_C 語言
- 2022-07-20 Rust?搭建一個小程序運行環(huán)境的方法詳解_相關(guān)技巧
- 2022-06-17 Python正則表達式的小練習分享_python
- 2022-06-02 redis?sentinel監(jiān)控高可用集群實現(xiàn)的配置步驟_Redis
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支