Input系統(tǒng)截斷策略的分析與應(yīng)用詳解
引言
上一篇文章 Input系統(tǒng): 按鍵事件分發(fā) 分析了按鍵事件的分發(fā)過程,雖然分析的對象只是按鍵事件,但是也從整體上,描繪了事件分發(fā)的過程。其中比較有意思的一環(huán)是事件截斷策略,本文就來分析它的原理以及應(yīng)用。
其實這篇文章早已經(jīng)寫好,這幾天在完善細(xì)節(jié)的時候,我突然發(fā)現(xiàn)了源碼中的一個 bug,這讓我開始對自己的分析產(chǎn)生質(zhì)疑,最終我拿了一臺公司的樣機(jī) debug,我發(fā)現(xiàn)這確實是源碼的一個 bug。本文在分析的過程中,會逐步揭開這個 bug。
截斷策略的原理
根據(jù) Input系統(tǒng): 按鍵事件分發(fā) 的分析,事件開始分發(fā)前,會執(zhí)行截斷策略,如下
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { // ... uint32_t policyFlags = args->policyFlags; int32_t flags = args->flags; int32_t metaState = args->metaState; // ... // 根據(jù)事件參數(shù)創(chuàng)建 KeyEvent,截斷策略需要使用它 KeyEvent event; event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC, args->action, flags, keyCode, args->scanCode, metaState, repeatCount, args->downTime, args->eventTime); android::base::Timer t; // 1. 執(zhí)行截斷策略,執(zhí)行的結(jié)果保存到參數(shù) policyFlags mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); } bool needWake; { // acquire lock mLock.lock(); if (shouldSendKeyToInputFilterLocked(args)) { // ... } // 2. 創(chuàng)建 KeyEntry , 并加入到 InputDispatcher 的收件箱中 std::unique_ptr<KeyEntry> newEntry = std::make_unique<KeyEntry>(args->id, args->eventTime, args->deviceId, args->source, args->displayId, policyFlags, args->action, flags, keyCode, args->scanCode, metaState, repeatCount, args->downTime); needWake = enqueueInboundEventLocked(std::move(newEntry)); mLock.unlock(); } // release lock // 3. 如果有必要,喚醒 InputDispatcher 線程 if (needWake) { mLooper->wake(); } }
根據(jù) Input系統(tǒng): InputManagerService的創(chuàng)建與啟動 可知,底層的截斷策略實現(xiàn)類是 NativeInputManager
// com_android_server_input_InputManagerService.cpp void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { bool interactive = mInteractive.load(); if (interactive) { policyFlags |= POLICY_FLAG_INTERACTIVE; } // 來自輸入設(shè)備的按鍵事件是受信任的 // 擁有注入權(quán)限的app,注入的按鍵事件也是受信任的,例如 SystemUI 注入 HOME, BACK, RECENT 按鍵事件 if ((policyFlags & POLICY_FLAG_TRUSTED)) { nsecs_t when = keyEvent->getEventTime(); JNIEnv* env = jniEnv(); // 包裝成上層的 KeyEvent 對象 jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); jint wmActions; if (keyEventObj) { // 1. 調(diào)用上層 InputManagerService#interceptKeyBeforeQueueing() 來執(zhí)行截斷策略 wmActions = env->CallIntMethod(mServiceObj, gServiceClassInfo.interceptKeyBeforeQueueing, keyEventObj, policyFlags); if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) { wmActions = 0; } android_view_KeyEvent_recycle(env, keyEventObj); env->DeleteLocalRef(keyEventObj); } else { ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing."); wmActions = 0; } // 2. 處理截斷策略的結(jié)果 // 實際上就是把上層截斷策略的結(jié)果轉(zhuǎn)化為底層的狀態(tài) handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } else { // ... } } void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags) { // 其實就是根據(jù)截斷策略的結(jié)果,決定是否在 policyFlags 添加 POLICY_FLAG_PASS_TO_USER 標(biāo)志位 if (wmActions & WM_ACTION_PASS_TO_USER) { policyFlags |= POLICY_FLAG_PASS_TO_USER; } else { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("handleInterceptActions: Not passing key to user."); #endif } }
首先調(diào)用上層來執(zhí)行截斷策略,然后根據(jù)執(zhí)行的結(jié)果,再決定是否在參數(shù) policyFlags 添加 POLICY_FLAG_PASS_TO_USER 標(biāo)志位。這個標(biāo)志位,就決定了事件是否能傳遞給用戶。
截斷策略經(jīng)過 InputManagerService,最終是由上層的 PhoneWindowManager 實現(xiàn),如下
// PhoneWindowManager.java public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { // ... // Handle special keys. switch (keyCode) { // ... case KeyEvent.KEYCODE_POWER: { // 返回的額結(jié)果去,去掉 ACTION_PASS_TO_USER 標(biāo)志位 result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately if (down) { interceptPowerKeyDown(event, interactiveAndOn); } else { interceptPowerKeyUp(event, canceled); } break; } // ... } // ... return result; }
這里以 power 按鍵為例,它的按鍵事件的截斷策略的處理結(jié)果,是去掉了 ACTION_PASS_TO_USER 標(biāo)志位,也即告訴底層不要把事件發(fā)送給用戶,這就是為何窗口(例如 Activity)無法收到 power 按鍵事件的原因。
現(xiàn)在,如果項目的硬件上新增一個按鍵,并且不想這個按鍵事件被分發(fā)給用戶,你會搞了嗎?
截斷策略的應(yīng)用
根據(jù) Input系統(tǒng): 按鍵事件分發(fā) 可知,截斷策略發(fā)生在事件分發(fā)之前,因此它能及時處理一些系統(tǒng)功能的事件,例如,power 按鍵亮/滅屏沒有延時,掛斷按鍵掛斷電話也沒有延時。
剛才,我們看到了截斷策略的一個作用,阻塞事件發(fā)送給用戶/窗口。然而,它還可以實現(xiàn)按鍵的手勢,手勢包括單擊,多擊,長按,組合鍵(例如截屏組合鍵)。
下面來分析截斷策略是如何實現(xiàn)按鍵手勢中的單擊、多擊、長按。至于組合鍵,由于涉及分發(fā)策略,留到下一篇文章分析。
初始化
按鍵的手勢是用 SingleKeyGestureDetector 來管理的,它在 PhoneWindowManager 中的初始化如下
// PhoneWindowManager.java private void initSingleKeyGestureRules() { mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext); int powerKeyGestures = 0; if (hasVeryLongPressOnPowerBehavior()) { powerKeyGestures |= KEY_VERYLONGPRESS; } if (hasLongPressOnPowerBehavior()) { powerKeyGestures |= KEY_LONGPRESS; } // 增加一個 power 按鍵手勢的規(guī)則 mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures)); if (hasLongPressOnBackBehavior()) { mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS)); } }
SingleKeyGestureDetector 根據(jù)配置,為 Power 鍵和 Back 鍵保存了規(guī)則(rule)。所謂的規(guī)則,就是如何實現(xiàn)單個按鍵的手勢。
所有的規(guī)則的基類都是 SingleKeyGestureDetector.SingleKeyRule,它的使用方式用下面一段代碼解釋
SingleKeyRule rule = new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) { int getMaxMultiPressCount() { // maximum multi press count. } void onPress(long downTime) { // short press behavior. } void onLongPress(long eventTime) { // long press behavior. } void onVeryLongPress(long eventTime) { // very long press behavior. } void onMultiPress(long downTime, int count) { // multi press behavior. } };
- getMaxMultiPressCount() 表示支持的按鍵的最大點(diǎn)擊次數(shù)。如果返回1,表示只支持單擊,如果返回3,表示支持雙擊和三擊,and so on...
- 單擊按鍵會調(diào)用 onPress()。
- 多擊按鍵會調(diào)用 onMultiPress(long downTime, int count),參數(shù) count 表示多擊的次數(shù)。
- 長按按鍵會調(diào)用 onLongPress()。
- 長時間地長按按鍵,會調(diào)用 onVeryLongPress()。
實現(xiàn)按鍵手勢
截斷策略在處理按鍵事件時,會處理按鍵手勢,如下
// PhoneWindowManager.java public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { // ... // 一般來說,軌跡球設(shè)備產(chǎn)生的事件,會設(shè)置 KeyEvent.FLAG_FALLBACK if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { // 處理按鍵手勢 handleKeyGesture(event, interactiveAndOn); } // ... return result; } private void handleKeyGesture(KeyEvent event, boolean interactive) { // KeyCombinationManager 是用于實現(xiàn)組合按鍵功能,如果只按下單個按鍵,不會截斷事件 if (mKeyCombinationManager.interceptKey(event, interactive)) { // handled by combo keys manager. mSingleKeyGestureDetector.reset(); return; } // GestureLauncherService 實現(xiàn)的雙擊打開 camera 功能 // 原理很簡單,就是判斷兩次 power 鍵按下的時間間隔 if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) { mPowerKeyHandled = handleCameraGesture(event, interactive); if (mPowerKeyHandled) { // handled by camera gesture. mSingleKeyGestureDetector.reset(); return; } } // 實現(xiàn)按鍵手勢 mSingleKeyGestureDetector.interceptKey(event, interactive); }
從這里可以看到,有三個類實現(xiàn)按鍵的手勢,如下
- KeyCombinationManager,它是用于實現(xiàn)組合按鍵的功能,例如,power 鍵 + 音量下鍵 實現(xiàn)的截屏功能。它的原理很簡單,就是第一個按鍵按下后,在超時時間內(nèi)等待第二個按鍵事件的到來。
- GestureLauncherService,目前只實現(xiàn)了雙擊打開 Camera 功能,原理也很簡單,當(dāng)?shù)谝淮伟聪?power 鍵,在規(guī)定時間內(nèi)按下第二次 power 鍵,然后由 SystemUI 實現(xiàn)打開 Camera 功能。
- SingleKeyGestureDetector,實現(xiàn)通用的按鍵的手勢功能。
GestureLauncherService 在很多個 Android 版本中,都只實現(xiàn)了雙擊打開 Camera 的功能,它的功能明顯與 SingleKeyGestureDetector 重合了。然而,更不幸的是,SingleKeyGestureDetector 實現(xiàn)的手勢功能還有 bug,Google 的工程師是不是把這個按鍵手勢功能給遺忘了?
KeyCombinationManager 會在下一篇文章中分析,GestureLauncherService 請大家自行分析,現(xiàn)在來看下 SingleKeyGestureDetector 是如何處理按鍵手勢的
// SingleKeyGestureDetector.java void interceptKey(KeyEvent event, boolean interactive) { if (event.getAction() == KeyEvent.ACTION_DOWN) { if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) { // 記錄按鍵按下時,是否是非交互狀態(tài) // 一般來說,滅屏狀態(tài)就是非交互狀態(tài) mBeganFromNonInteractive = !interactive; } interceptKeyDown(event); } else { interceptKeyUp(event); } }
SingleKeyGestureDetector 分別處理了按鍵的 DOWN 事件和 UP 事件,這兩者合起來才實現(xiàn)了整個手勢功能。
首先看下如何處理 DOWN 事件
// SingleKeyGestureDetector.java private void interceptKeyDown(KeyEvent event) { final int keyCode = event.getKeyCode(); // 3. 收到同一個按鍵的長按事件,立即執(zhí)行長按動作 if (mDownKeyCode == keyCode) { if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0 && mActiveRule.supportLongPress() && !mHandledByLongPress) { if (DEBUG) { Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode)); } mHandledByLongPress = true; // 移除長按消息 mHandler.removeMessages(MSG_KEY_LONG_PRESS); mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); // 立即執(zhí)行長按動作 // 注意,是立即,因為系統(tǒng)已經(jīng)表示這是一個長按動作 mActiveRule.onLongPress(event.getEventTime()); } return; } // 表示這里前一個按鍵按下還沒有抬起前,又有另外一個按鍵按下 if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN || (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) { if (DEBUG) { Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode)); } reset(); } // 保存按下的按鍵 keycode mDownKeyCode = keyCode; // 1. 按下首次按下,尋找一個規(guī)則 if (mActiveRule == null) { final int count = mRules.size(); for (int index = 0; index < count; index++) { final SingleKeyRule rule = mRules.get(index); // 找到為按鍵添加規(guī)則 if (rule.shouldInterceptKey(keyCode)) { mActiveRule = rule; // 找到有效的 rule,就退出循環(huán) // 看來對于一個按鍵,只有最先添加的規(guī)則有效 break; } } } // 沒有為按鍵事件找到一條規(guī)則,直接退出 if (mActiveRule == null) { return; } final long eventTime = event.getEventTime(); // 2. 首次按下時,發(fā)送一個長按的延時消息,用于實現(xiàn)按鍵的長按功能 // mKeyPressCounter 記錄的是按鍵按下的次數(shù) if (mKeyPressCounter == 0) { if (mActiveRule.supportLongPress()) { final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0, eventTime); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mLongPressTimeout); } if (mActiveRule.supportVeryLongPress()) { final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0, eventTime); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout); } } // 4. 這里表示之前已經(jīng)按鍵已經(jīng)按下至少一次 else { // 移除長按事件的延時消息 mHandler.removeMessages(MSG_KEY_LONG_PRESS); mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); // 移除單擊事件或多擊事件的延時消息 mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); // Trigger multi press immediately when reach max count.( > 1) // 達(dá)到最大點(diǎn)擊次數(shù),立即執(zhí)行多擊功能 // 注意,這段代碼是一個 bug if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) { if (DEBUG) { Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it" + " reach the max count " + mKeyPressCounter); } mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1); mKeyPressCounter = 0; } } }
SingleKeyGestureDetector 對按鍵 DOWN 事件的處理過程如下
- 按鍵首次按下,為按鍵找到相應(yīng)的規(guī)則,保存到 mActiveRule。
- 按鍵首次按下,會發(fā)送一個延時的長按消息,實現(xiàn)長按功能。當(dāng)超時時,也就是按鍵按下沒有抬起,并且系統(tǒng)也沒有發(fā)送按鍵的長按事件,那么會執(zhí)行 SingleKeyRule#onLongPress() 或/和 SingleKeyRule#onVeryLongPress()。
- 如果收到系統(tǒng)發(fā)送的按鍵的長按事件,那么移除長按消息,并立即SingleKeyRule#onLongPress()。為何要立即執(zhí)行,而不是發(fā)送一個延時消息?因為系統(tǒng)已經(jīng)表示這是一個長按事件,沒有理由再使用一個延時來檢測是否要觸發(fā)長按。
- 如果多次(至少超過1次)點(diǎn)擊按鍵,那么移除長按、單擊/多擊消息,并在點(diǎn)擊次數(shù)達(dá)到最大時,立即執(zhí)行SingleKeyRule#onMultiPress()。
注意,第4點(diǎn)中,當(dāng)達(dá)到最大點(diǎn)擊次數(shù)時,立即執(zhí)行SingleKeyRule#onMultiPress(),并重置按鍵點(diǎn)擊次數(shù) mKeyPressCounter 為 0,這是一個 Bug,這段代碼應(yīng)該去掉。在后面的分析中,我將證明這會造成 bug。
SingleKeyGestureDetector 對按鍵按下事件的處理,確切來說只實現(xiàn)了長按的功能,而按鍵的單擊功能以及多擊功能,是在處理 UP 事件中實現(xiàn)的,如下
// SingleKeyGestureDetector.java private boolean interceptKeyUp(KeyEvent event) { // 按鍵已抬起,就不應(yīng)該觸發(fā)長按事件,所以需要移除延時的長按消息 mHandler.removeMessages(MSG_KEY_LONG_PRESS); mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); // 按鍵抬起,重置 mDownKeyCode mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; // 沒有有效規(guī)則,不處理按鍵的抬起事件 if (mActiveRule == null) { return false; } // 如果已經(jīng)觸發(fā)長按,不處理按鍵的抬起事件 if (mHandledByLongPress) { mHandledByLongPress = false; mKeyPressCounter = 0; return true; } final long downTime = event.getDownTime(); // 抬起按鍵的key code 要與規(guī)則的一樣,否則無法觸發(fā)規(guī)則 if (event.getKeyCode() == mActiveRule.mKeyCode) { // 1. 規(guī)則只支持單擊,那么發(fā)送消息,執(zhí)行單擊操作。 if (mActiveRule.getMaxMultiPressCount() == 1) { // 注意,第三個參數(shù)為 arg2,但并沒有使用 Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, 1, downTime); msg.setAsynchronous(true); mHandler.sendMessage(msg); return true; } // 走到這里,表示規(guī)則支持多擊功能,那么必須記錄多擊的次數(shù),用于實現(xiàn)按鍵多擊功能 mKeyPressCounter++; // 2. 規(guī)則支持多擊,發(fā)送一個延遲消息來實現(xiàn)單擊或者多擊功能 // 既然支持多擊,那么肯定需要一個超時時間來檢測是否有多擊操作,所以這里要設(shè)置一個延時 // 注意,第三個參數(shù)為 arg2,但并沒有使用 Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, mKeyPressCounter, downTime); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); return true; } // 收到其他按鍵的 UP/CANCEL 事件,重置前一個按鍵的規(guī)則 reset(); return false; }
SingleKeyGestureDetector 處理 UP 事件分兩種情況
- 如果規(guī)則只支持單擊,那么發(fā)送一個消息執(zhí)行單擊動作。這里我就有一個疑問了,為何不與前面一樣,直接執(zhí)行單擊動作,反而還要多此一舉地發(fā)送一個消息去執(zhí)行單擊動作?
- 如果規(guī)則支持多擊,那么首先把點(diǎn)擊次數(shù) mKeyPressCounter 加1,然后發(fā)送一個延時消息執(zhí)行單擊或者多擊動作。為何要設(shè)置一個延時?對于單擊操作,需要使用一個延時來檢測沒有再次點(diǎn)擊的操作,對于多擊操作,需要檢測后面是否還有點(diǎn)擊操作。
注意,以上兩種情況下發(fā)送的消息,Message#arg2 的值其實是按鍵的點(diǎn)擊次數(shù)。
現(xiàn)在來看下 MSG_KEY_DELAYED_PRESS 消息的處理流程。
// SingleKeyGestureDetector.java private class KeyHandler extends Handler { @Override public void handleMessage(Message msg) { if (mActiveRule == null) { return; } final int keyCode = msg.arg1; final long eventTime = (long) msg.obj; switch(msg.what) { // ... case MSG_KEY_DELAYED_PRESS: // 雖然在發(fā)送消息的時候,使用了 Message#arg2 參數(shù)來表示按鍵的點(diǎn)擊次數(shù) // 但是,這里仍然使用 mKeyPressCounter 來決定按鍵的點(diǎn)擊次數(shù) if (mKeyPressCounter == 1) { // 當(dāng)規(guī)則支持多擊功能時,單擊功能由這里實現(xiàn) mActiveRule.onPress(eventTime); } else { // 當(dāng)規(guī)則只支持單擊功能時,mKeyPressCounter 永遠(yuǎn)為 0,因此單擊功能由這里實現(xiàn) // 當(dāng)規(guī)則支持多擊功能時,多擊功能由這里實現(xiàn) mActiveRule.onMultiPress(eventTime, mKeyPressCounter); } reset(); break; } } }
這個消息的處理過程,從表面上看,如果是單擊就執(zhí)行 SingleKeyRule#onPress(),如果是多擊就執(zhí)行 SingleKeyRule#onMultiPress()。
然而實際情況,并非如此。由于沒有使用 Message#arg2,而是直接使用 mKeyPressCounter 作為按鍵點(diǎn)擊次數(shù),這就導(dǎo)致兩個問題
- 當(dāng)規(guī)則只支持單擊功能時,mKeyPressCounter 永遠(yuǎn)為0。于是單擊按鍵時,調(diào)用 SingleKeyRule#onMultiPress(),而非 SingleKeyRule#onPress(),這豈不是笑話。
- 當(dāng)規(guī)則支持多擊功能時,如果單擊按鍵,會調(diào)用 SingleKeyRule#onPress(),如果多擊按鍵,會調(diào)用 SingleKeyRule#onMultiPress() 這一切看起來沒有問題,實則不然。對于多擊操作,前面分析過,在處理 DOWN 事件時,當(dāng)達(dá)到最大點(diǎn)擊次數(shù)時,會調(diào)用SingleKeyRule#onMultiPress(),并把 mKeyPressCounter 重置為0。之后再處理 UP 事件時,mKeyPressCounter 加1后變?yōu)榱?,然后發(fā)送消息去執(zhí)行,最后,奇跡般地執(zhí)行了一次 SingleKeyRule#onPress()。對于一個多擊操作,居然執(zhí)行了一次單擊動作,這簡直是國際笑話。
以上兩點(diǎn)問題,我用樣機(jī)進(jìn)行驗證過,確實存在。但是,系統(tǒng)很好地支持了雙擊 power 打開 Camera,以及單擊 power 亮/滅屏。這又是怎么回事呢?
- 雙擊 power 打開 Camera,是由 GestureLauncherService 實現(xiàn)的。
- 系統(tǒng)默認(rèn)配置的 power 規(guī)則,只支持單擊。
既然 power 規(guī)則只支持單擊,理論上應(yīng)該調(diào)用 SingleKeyRule#onMultiPress(long downTime, int count),并且參數(shù) count 為0,這豈不還是錯的。確實如此,不過源碼又巧合地用另外一個Bug避開了這一個Bug。接著往下看
首先看下 power 鍵的規(guī)則
// PhoneWindowManager.java private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule { PowerKeyRule(int gestures) { super(KEYCODE_POWER, gestures); } @Override int getMaxMultiPressCount() { // 默認(rèn)配置返回1 return getMaxMultiPressPowerCount(); } @Override void onPress(long downTime) { powerPress(downTime, 1 /*count*/, mSingleKeyGestureDetector.beganFromNonInteractive()); } @Override void onLongPress(long eventTime) { if (mSingleKeyGestureDetector.beganFromNonInteractive() && !mSupportLongPressPowerWhenNonInteractive) { Slog.v(TAG, "Not support long press power when device is not interactive."); return; } powerLongPress(eventTime); } @Override void onVeryLongPress(long eventTime) { mActivityManagerInternal.prepareForPossibleShutdown(); powerVeryLongPress(); } @Override void onMultiPress(long downTime, int count) { powerPress(downTime, count, mSingleKeyGestureDetector.beganFromNonInteractive()); } }
power 鍵規(guī)則,默認(rèn)支持最大的點(diǎn)擊數(shù)為1,這是在 config.xml 中進(jìn)行配置的,這里不細(xì)講。
power 鍵規(guī)則中,無論是單擊還是多擊,默認(rèn)都調(diào)用同一個函數(shù),如下
private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) { if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) { Slog.i(TAG, "Suppressed redundant power key press while " + "already in the process of turning the screen on."); return; } final boolean interactive = Display.isOnState(mDefaultDisplay.getState()); Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive + " count=" + count + " beganFromNonInteractive=" + beganFromNonInteractive + " mShortPressOnPowerBehavior=" + mShortPressOnPowerBehavior); // 根據(jù)配置,決定power鍵的單擊/多擊行為 if (count == 2) { powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior); } else if (count == 3) { powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior); } // 注意,beganFromNonInteractive 表示是否在非交互狀態(tài)下點(diǎn)擊 power 鍵 // 這里判斷條件的意思是,處于交互狀態(tài),并且不是非交互狀態(tài)下點(diǎn)擊power鍵 // 說簡單點(diǎn),這里只支持在亮屏狀態(tài)下單擊power鍵功能 else if (interactive && !beganFromNonInteractive) { if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) { Slog.i(TAG, "Suppressing power key because the user is interacting with the " + "fingerprint sensor"); return; } switch (mShortPressOnPowerBehavior) { case SHORT_PRESS_POWER_NOTHING: break; case SHORT_PRESS_POWER_GO_TO_SLEEP: // 滅屏,不過要先進(jìn)入 doze 模式 sleepDefaultDisplayFromPowerButton(eventTime, 0); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP: // 跳過 doze 模式,直接進(jìn)入 sleep 模式 sleepDefaultDisplayFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME: // 跳過 doze 模式,進(jìn)入 sleep 模式,并返回 home if (sleepDefaultDisplayFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE)) { launchHomeFromHotKey(DEFAULT_DISPLAY); } break; case SHORT_PRESS_POWER_GO_HOME: // 返回 home shortPressPowerGoHome(); break; case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: { if (mDismissImeOnBackKeyPressed) { // 關(guān)閉輸入法 if (mInputMethodManagerInternal == null) { mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); } if (mInputMethodManagerInternal != null) { mInputMethodManagerInternal.hideCurrentInputMethod( SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME); } } else { // 返回 home shortPressPowerGoHome(); } break; } } } }
從整體看,如果參數(shù) count 為2或者3,會執(zhí)行多擊的行為,否則,執(zhí)行單擊行為。
這個邏輯是不是有點(diǎn)奇怪呀,參數(shù) count 不為2也不為3,難道就是一定為1嗎?正是因為這個邏輯,才導(dǎo)致 count 為0時,也能執(zhí)行單擊動作,小朋友聽了都直呼6。我想起了我一個前同事做的事,用一個 Bug 去解決另外一個 Bug。
power 鍵的亮屏與滅屏
好了,言歸正傳,power 鍵規(guī)則的單擊行為,只包括了滅屏,并沒有包含亮屏,這又是怎么回事呢?因為 power 鍵的亮屏,不是在規(guī)則中實現(xiàn)的,而是在截斷策略中實現(xiàn)的,如下
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { // ... if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { // 處理單個按鍵的手勢 handleKeyGesture(event, interactiveAndOn); } // ... switch (keyCode) { // ... case KeyEvent.KEYCODE_POWER: { // power 按鍵事件不分發(fā)給用戶 result &= ~ACTION_PASS_TO_USER; // 這里表示 power 鍵不是喚醒鍵,是不是很奇怪,因為系統(tǒng)默認(rèn)綁定了 power 鍵亮屏功能 isWakeKey = false; // wake-up will be handled separately if (down) { // power 亮屏在這里實現(xiàn) interceptPowerKeyDown(event, interactiveAndOn); } else { interceptPowerKeyUp(event, canceled); } break; } // ... } // ... return result; } private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { // Hold a wake lock until the power key is released. if (!mPowerKeyWakeLock.isHeld()) { mPowerKeyWakeLock.acquire(); } mWindowManagerFuncs.onPowerKeyDown(interactive); // 設(shè)備處于響鈴狀態(tài),就靜音,處于通話狀態(tài),就掛電話 TelecomManager telecomManager = getTelecommService(); boolean hungUp = false; if (telecomManager != null) { if (telecomManager.isRinging()) { telecomManager.silenceRinger(); } else if ((mIncallPowerBehavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 && telecomManager.isInCall() && interactive) { hungUp = telecomManager.endCall(); } } // 檢測 PowerManagerService 是否正在使用 sensor 滅屏 final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event); sendSystemKeyToStatusBarAsync(event.getKeyCode()); // mPowerKeyHandled 在長按,組合鍵,雙擊打開Camera情況下,會被設(shè)置為 true mPowerKeyHandled = mPowerKeyHandled || hungUp || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted(); if (!mPowerKeyHandled) { // power 事件沒有被其它地方使用,那么在滅屏狀態(tài)下執(zhí)行亮屏 if (!interactive) { wakeUpFromPowerKey(event.getDownTime()); } } else { // power 事件被其它地方使用,那么重置規(guī)則 if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { mSingleKeyGestureDetector.reset(); } } }
截斷策略處理 power 按鍵的 DOWN 事件過程如下
- 如果 power 按鍵事件沒有被其它地方使用,那么,在非交互狀態(tài)下,一般指滅屏狀態(tài),會執(zhí)行亮屏。
- 如果 power 按鍵事件被其它地方使用,那么重置按鍵手勢。
這里我又有一個疑問,為何要把 power 亮屏的代碼的在這里單獨(dú)處理?在創(chuàng)建規(guī)則時設(shè)置一個回調(diào),是不是更好呢?
結(jié)束
我在支援公司的某個項目時,偶然發(fā)現(xiàn)有人在截斷策略中,要實現(xiàn)一個新的雙擊power功能,以及添加三擊power的功能,可謂是把源碼修改得"雞飛狗跳",我當(dāng)時還嗤之以鼻,現(xiàn)在發(fā)現(xiàn)我錯怪他了。但是呢,由于他不知道如何修復(fù)這個源碼的 bug,所以他實現(xiàn)的過程還是非常丑陋。
最后,給看我文章的人一個小福利,你可以參考 Input系統(tǒng): InputReader 處理按鍵事件 ,學(xué)會從底層映射一個按鍵到上層,然后根據(jù)本文,實現(xiàn)按鍵的手勢,這絕壁是一個非常叼的事情。當(dāng)然,如果手勢中要包括多擊功能,還得解決本文提出的 Bug,這個不難,小小思考下就可以了。
以上就是Input系統(tǒng)截斷策略的分析與應(yīng)用詳解的詳細(xì)內(nèi)容,更多關(guān)于Input系統(tǒng)截斷策略的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android ViewModel與Lifecycles和LiveData組件用法詳細(xì)講解
JetPack是一個開發(fā)組件工具集,他的主要目的是幫助我們編寫出更加簡潔的代碼,并簡化我們的開發(fā)過程。JetPack中的組件有一個特點(diǎn),它們大部分不依賴于任何Android系統(tǒng)版本,這意味者這些組件通常是定義在AndroidX庫當(dāng)中的,并且擁有非常好的向下兼容性2023-01-01Android?Hilt?Retrofit?Paging3使用實例
這篇文章主要介紹了Android?Hilt依賴注入的使用,首先,某個類的成員變量稱為依賴,如若此變量想要實例化引用其類的方法,可以通過構(gòu)造函數(shù)傳參或者通過某個方法獲取對象,此等通過外部方法獲取對象實例的稱為依賴注入2023-01-01Android實現(xiàn)與Apache Tomcat服務(wù)器數(shù)據(jù)交互(MySql數(shù)據(jù)庫)
本篇文章主要介紹了Android實現(xiàn)與Apache Tomcat服務(wù)器數(shù)據(jù)交互(MySql數(shù)據(jù)庫),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06分享10個很棒的學(xué)習(xí)Android開發(fā)的網(wǎng)站
我推薦的網(wǎng)站,都是我在學(xué)習(xí)Android 開發(fā)過程中發(fā)現(xiàn)的好網(wǎng)站,給初學(xué)者一些建議,少走一些彎路2015-03-03用Flutter做桌上彈球(繪圖(Canvas&CustomPaint)API)
這篇文章主要介紹了用Flutter做桌上彈球 聊聊繪圖(Canvas&CustomPaint)API,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07Android 中okhttp自定義Interceptor(緩存攔截器)
這篇文章主要介紹了Android 中okhttp自定義Interceptor(緩存攔截器)的相關(guān)資料,需要的朋友可以參考下2017-03-03