欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Input系統(tǒng)截斷策略的分析與應(yīng)用詳解

 更新時間:2023年02月03日 09:27:58   作者:大胃粥  
這篇文章主要為大家介紹了Input系統(tǒng)截斷策略的分析與應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

上一篇文章 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)文章

最新評論