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

Input系統(tǒng)按鍵事件的分發(fā)處理示例詳解

 更新時間:2023年01月17日 11:27:42   作者:大胃粥  
這篇文章主要為大家介紹了Input系統(tǒng)按鍵事件的分發(fā)處理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

前面一篇文章分析了 InputReader 對按鍵事件的流程流程,大致上就是根據(jù)配置文件把按鍵的掃描碼(scan code)轉換為按鍵碼(key code),并且同時會從配置文件中獲取策略標志位(policy flag),用于控制按鍵的行為,例如亮屏。然后把按鍵事件進行包裝,分發(fā)給 InputDispatcher。本文就接著來分析 InputDispatcher 對按鍵事件的處理。

1. InputDispatcher 收到事件

從前面一篇文章可知,InputDispatcher 收到的按鍵事件的來源如下

void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,
                                     int32_t usageCode) {
    // ...
    // 按鍵事件包裝成 NotifyKeyArgs
    NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                       getDisplayId(), policyFlags,
                       down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                       AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    // 加入到 QueuedInputListener 緩存隊列中
    getListener()->notifyKey(&args);
}

InputReader 把按鍵事件交給 KeyboardInputMapper 處理,KeyboardInputMapper 把按鍵事件包裝成 NotifyKeyArgs,然后加入到 QueuedInputListener 的緩存隊列。

然后,當 InputReader 處理完所有事件后,會刷新 QueuedInputListener 的緩存隊列,如下

void InputReader::loopOnce() {
    // ...
    { // acquire lock
        // ...
        if (count) {
            // 處理事件
            processEventsLocked(mEventBuffer, count);
        }
        // ...
    } // release lock
    // ...
    // 刷新緩存隊列
    mQueuedListener->flush();
}

QueuedInputListener 會把緩存隊列中的所有事件,分發(fā)給 InputClassifier

// framework/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
    // 交給 InputClassifier 
    listener->notifyKey(this);
}

InputClassifier 收到 NotifyKeyArgs 事件后,其實什么也沒做,就交給了 InputDispatcher

void InputClassifier::notifyKey(const NotifyKeyArgs* args) {
    // 直接交給 InputDispatcher
    mListener->notifyKey(args);
}

現(xiàn)在明白了按鍵事件的來源,接下來分析 InputDispatcher 如何處理按鍵事件

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    // 檢測 action,action 只能為 AKEY_EVENT_ACTION_DOWN/AKEY_EVENT_ACTION_UP
    if (!validateKeyEvent(args->action)) {
        return;
    }
    // 策略標志位,一般來源于配置文件
    uint32_t policyFlags = args->policyFlags;
    int32_t flags = args->flags;
    int32_t metaState = args->metaState;
    // InputDispatcher tracks and generates key repeats on behalf of
    // whatever notifies it, so repeatCount should always be set to 0
    constexpr int32_t repeatCount = 0;
    if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
        policyFlags |= POLICY_FLAG_VIRTUAL;
        flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
    }
    if (policyFlags & POLICY_FLAG_FUNCTION) {
        metaState |= AMETA_FUNCTION_ON;
    }
    // 來自 InputClassifier 的事件都是受信任的
    policyFlags |= POLICY_FLAG_TRUSTED;
    int32_t keyCode = args->keyCode;
    accelerateMetaShortcuts(args->deviceId, args->action, keyCode, metaState);
    // 創(chuàng)建 KeyEvent,這個對象主要用于,在事件加入到 InputDispatcher 隊列前,執(zhí)行策略截斷查詢
    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. 詢問策略,在按鍵事件加入到 InputDispatcher 隊列前,是否截斷事件
    // 如果上層不截斷事件,policyFlags 添加 POLICY_FLAG_PASS_TO_USER,表示事件需要傳遞給用戶
    // 如果上層截斷事件,那么不會添加 policyFlags 添加 POLICY_FLAG_PASS_TO_USER,事件最終不會傳遞給用戶
    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
    // 記錄處理時間
    // 如果是Power鍵的截斷處理時間過長,那么亮屏或者滅屏可能會讓用戶感覺到有延遲
    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();
        // 通常系統(tǒng)沒有輸入過濾器(input filter)
        if (shouldSendKeyToInputFilterLocked(args)) {
            // ...
        }
        // 創(chuàng)建 KeyEntry,這個對象是 InputDispatcher 用于分發(fā)循環(huán)的
        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);
        // 2. 把 KeyEntry 加入到 InputDispatcher 的收件箱 mInboundQueue 中
        needWake = enqueueInboundEventLocked(std::move(newEntry));
        mLock.unlock();
    } // release lock
    // 3. 如有必要,喚醒 InputDispatcher 線程處理事件
    if (needWake) {
        mLooper->wake();
    }
}

InputDispatcher 處理按鍵事件的過程如下

  • 把按鍵事件包裝成 KeyEvent 對象,然后查詢截斷策略,看看策略是否截斷該事件。如果策略不截斷,那么會在參數(shù) policyFlags 添加 POLICY_FLAG_PASS_TO_USER 標志位,表示事件要發(fā)送給用戶。 否則不會添加這個標志位,InputDispatcher 后面會丟棄這個事件,也就是不會分發(fā)給用戶。參考【1.1 截斷策略查詢】
  • 把按鍵事件包裝成 KeyEntry 對象,然后加入到 InputDispatcher 的收件箱 InputDispatcher::mInboundQueue。參考【1.2 InputDispatcher 收件箱接收事件】
  • 如有必要,喚醒 InputDispatcher 線程處理事件。通常,InputDispatcher 線程處于休眠狀態(tài)時,如果收到事件,那么需要喚醒線程來處理事件。

注意,這里的所有操作,不是發(fā)生在 InputDispatcher 線程,而是發(fā)生在 InputReader 線程,這個線程是負責不斷地讀取事件,因此這里的查詢策略是否截斷事件的過程,時間不能太長,否則影響了輸入系統(tǒng)讀取事件。

另外,在執(zhí)行完截斷策略后,會記錄處理的時長,如果時長超過一定的閾值,會收到一個警告信息。我曾經聽到其他項目的人在談論 power 鍵亮屏慢的問題,那么可以在這里排查下。

1.1 截斷策略查詢

// framework/base/services/core/jni/com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
        uint32_t& policyFlags) {
    // ...
    // 如果處于交互狀態(tài),policyFlags 添加 POLICY_FLAG_INTERACTIVE 標志位
    bool interactive = mInteractive.load();
    if (interactive) {
        policyFlags |= POLICY_FLAG_INTERACTIVE;
    }
    // 受信任的按鍵事件,才會執(zhí)行策略查詢
    // 來自 InputClassifier 的事件都是受信任的
    if ((policyFlags & POLICY_FLAG_TRUSTED)) {
        nsecs_t when = keyEvent->getEventTime();
        JNIEnv* env = jniEnv();
        // 1. 創(chuàng)建上層的 KeyEvent 對象
        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
        jint wmActions;
        if (keyEventObj) {
            // 2. 調用上層的 InputManangerService#interceptMotionBeforeQueueingNonInteractive() 
            // 最終是通過 PhoneWindowManager 完成截斷策略查詢的            
            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;
        }
        // 3. 處理策略查詢的結果
        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
    } else {
        // 不受信任的事件,不會執(zhí)行截斷策略查詢,而且只有在設備處于交互狀態(tài)下,才能發(fā)送給用戶
        if (interactive) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;
        }
    }
}
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
        uint32_t& policyFlags) {
    // 4. 如果策略不截斷事件,那么在策略標志位 policyFlags 中添加 POLICY_FLAG_PASS_TO_USER 標志位
    // 策略查詢的結果中有 WM_ACTION_PASS_TO_USER 標志位,表示需要把事件傳遞給用戶
    if (wmActions & WM_ACTION_PASS_TO_USER) {
        policyFlags |= POLICY_FLAG_PASS_TO_USER;
    }
}

事件截斷策略的查詢過程,就是就是通過 JNI 調用上層 InputManagerService 的方法,而這個策略最終是由 PhoneWindowManager 實現(xiàn)的。如果策略不截斷,如果策略不截斷事件,那么在參數(shù)的策略標志位 policyFlags 中添加 POLICY_FLAG_PASS_TO_USER 標志位。

為何需要這個截斷策略? 或者這樣問,如果沒有截斷策略,那么會有什么問題呢? 假想我們正在處于通話,此時按下掛斷電話按鍵,如果輸入系統(tǒng)還有很多事件沒有處理完,或者說,處理事件的時間較長,那么掛斷電話的按鍵事件不能得到及時處理,這就相當影響用戶體驗。而如果有了截斷策略,在輸入系統(tǒng)正式處理事件前,就可以處理掛斷電話按鍵事件。

因此,截斷策略的作用就是及時處理系統(tǒng)一些重要的功能。這給我們一個什么提示呢?當硬件上添加了一個按鍵,如果想要快速響應這個按鍵的事件,那么就在截斷策略中處理。

關于截斷策略,以及后面的分發(fā)策略,是一個比較好的課題,我會在后面一篇文章中詳細分析。

下面,理解幾個概念

  • 什么是交互狀態(tài)?什么是非交互狀態(tài)?簡單理解,亮屏就是交互狀態(tài),滅屏就是非交互狀態(tài)。但是,嚴格來說,并不準確,如果讀者想知道具體的定義,可以查看我寫的 PowerManagerService 的文章。
  • 什么是受信任的事件?來自物理輸入設備的事件都是受信任的,另外像 SystemUI ,由于申請了 android.permission.INJECT_EVENTS 權限, 因此它注入的 BACK, HOME 按鍵事件也都是受信任。
  • 什么是注入事件?簡單來說,不是由物理設備產生的事件。例如,導航欄上的 BACK, HOME 按鍵,它們的事件都是通過注入產生的,因此它們是注入事件。

1.2 InputDispatcher 收件箱接收事件

bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
    // mInboundQueue 隊列為空,需要喚醒 InputDispatcher 線程來處理事件
    bool needWake = mInboundQueue.empty();
    // 加入到 mInboundQueue 中
    mInboundQueue.push_back(std::move(newEntry));
    EventEntry& entry = *(mInboundQueue.back());
    traceInboundQueueLengthLocked();
    switch (entry.type) {
        case EventEntry::Type::KEY: {
            // Optimize app switch latency.
            // If the application takes too long to catch up then we drop all events preceding
            // the app switch key.
            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
            if (isAppSwitchKeyEvent(keyEntry)) {
                if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {
                    mAppSwitchSawKeyDown = true;
                } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
                    // app 切換按鍵抬起時,需要做如下事情
                    // 計算切換超時時間
                    // 需要立即喚醒 InputDispatcher 線程來處理,因為這個事件很重要
                    if (mAppSwitchSawKeyDown) {
                        mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
                        mAppSwitchSawKeyDown = false;
                        // 需要喚醒線程,立即處理按鍵事件
                        needWake = true;
                    }
                }
            }
            break;
        }
        // ...
    }
    // 返回值表明是否需要喚醒 InputDispatcher 線程
    return needWake;
}
bool InputDispatcher::isAppSwitchKeyEvent(const KeyEntry& keyEntry) {
    return !(keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) && isAppSwitchKeyCode(keyEntry.keyCode) &&
            (keyEntry.policyFlags & POLICY_FLAG_TRUSTED) &&
            (keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER);
}
// AKEYCODE_HOME 是 HOME 按鍵,AKEYCODE_APP_SWITCH 是 RECENTS 按鍵
static bool isAppSwitchKeyCode(int32_t keyCode) {
    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL ||
            keyCode == AKEYCODE_APP_SWITCH;
}

InputDispatcher::mInboundQueue 是 InputDispatcher 的事件收件箱,所有的事件,包括注入事件,都會加入這個收件箱。

如果收件箱之前沒有"郵件",當接收到"郵件"后,就需要喚醒 InputDispatcher 線程來處理"郵件",這個邏輯很合理吧?

另外,聊一下這里提到的 app switch 按鍵。從上面的代碼可知,HOME, RECENT, ENDCALL 按鍵都是 app switch 按鍵。當 app switch 按鍵抬起時,會計算一個超時時間,并且立即喚醒 InputDispatcher 線程來處理事件,因為這個事件很重要,需要及時處理,但是處理時間也不能太長,因此需要設置一個超時時間。

為何要給 app switch 按鍵設置一個超時時間? 假如我在操作一個界面,此時由于某些原因,例如 CPU 占用率過高,導致界面事件處理比較緩慢,也就是常說的卡頓現(xiàn)象。此時我覺得這個 app 太渣了,想殺掉它,怎么辦呢? 按下導航欄的 RECENT 按鍵,然后干掉它。但是由于界面處理事件比較緩慢,因此 RECENT 按鍵事件可能不能得到及時處理,這就會讓我很惱火,我很可能扔掉這個手機。因此需要給 app switch 按鍵設置一個超時時間,如果超時了,那么就會丟棄 app switch 按鍵之前的所有事件,來讓 app switch 事件得到及時的處理。

2. InputDispatcher 處理按鍵事件

現(xiàn)在收件箱已 InputDispatcher::mInboundQueue 已經收到了按鍵事件,那么來看下 InputDisaptcher 線程如何處理按鍵事件的。由前面的文章可知,InputDisaptcher 線程循環(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(&amp;nextWakeupTime);
        }
        // 2. 執(zhí)行命令,并且立即喚醒線程
        // 這個命令來自于前一步的事件分發(fā)
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
        // 3. 處理 ANR ,并返回下一次線程喚醒的時間。
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } // release lock
    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    // 4. 線程休眠 timeoutMillis 毫秒
    // 注意,休眠的過程可能會被打破
    // 例如,窗口返回處理事件的結果時,會被喚醒
    // 又例如,收件箱接收到了事件,也會被喚醒
    mLooper-&gt;pollOnce(timeoutMillis);
}

InputDispatcher 的一次線程循環(huán),做了如下幾件事

  • 執(zhí)行一次事件分發(fā)。其實就是從收件箱中獲取一個事件進行分發(fā)。注意,此過程只分發(fā)一個事件。也就是說,線程循環(huán)一次,只處理了一個事件。參考【2.1 分發(fā)事件】
  • 執(zhí)行命令。 這個命令是哪里來的呢?是上一步事件分發(fā)中產生的。事件在發(fā)送給窗口前,會執(zhí)行一次分發(fā)策略查詢,而這個查詢的方式就是創(chuàng)建一個命令來執(zhí)行。
  • 處理 ANR,并返回下一次線程喚醒的時間。窗口在接收到事件后,需要在規(guī)定的時間內處理,否則會發(fā)生 ANR。這里利用 ANR 的超時時間計算線程下次喚醒的時間,以便能及時處理 ANR。
  • 線程休眠。線程被喚醒有很多種可能,例如窗口及時地返回了事件的處理結果,或者窗口處理事件超時了。

2.1 分發(fā)事件

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();
    // Reset the key repeat timer whenever normal dispatch is suspended while the
    // device is in a non-interactive state.  This is to ensure that we abort a key
    // repeat if the device is just coming out of sleep.
    // 系統(tǒng)沒有啟動完成,或者正在關機,mDispatchEnabled 為 false
    if (!mDispatchEnabled) {
        // 重置生成重復按鍵的計時
        resetKeyRepeatLocked();
    }
    // Activity 發(fā)生旋轉時,會凍結
    if (mDispatchFrozen) {
        // 被凍結時,事件不會執(zhí)行分發(fā),等到被解凍后,再執(zhí)行分發(fā)
        return;
    }
    // 判斷 app 切換是否超時
    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
    // 如果下次線程喚醒的時間大于app切換超時時間,那么下次喚醒時間需要重置為app切換超時時間
    // 以便處理app切換超時的問題
    if (mAppSwitchDueTime < *nextWakeupTime) {
        *nextWakeupTime = mAppSwitchDueTime;
    }
    // mPendingEvent 表示正在處理的事件
    if (!mPendingEvent) {
        if (mInboundQueue.empty()) { // 收件箱為空
            // 收件箱為空,并且發(fā)生了 app 切換超時
            // 也就是說,目前只有一個 app 切換按鍵事件,并且還超時了
            // 處理這種情況很簡單,就是重置狀態(tài)即可,因此沒有其他事件需要丟棄
            if (isAppSwitchDue) {
                // The inbound queue is empty so the app switch key we were waiting
                // for will never arrive.  Stop waiting for it.
                resetPendingAppSwitchLocked(false);
                isAppSwitchDue = false;
            }
            // Synthesize a key repeat if appropriate.
            // 這里處理的情況是,輸入設備不支持重復按鍵的生成,那么當用戶按下一個按鍵后,長時間不松手,因此就需要合成一個重復按鍵事件
            if (mKeyRepeatState.lastKeyEntry) {
                if (currentTime >= mKeyRepeatState.nextRepeatTime) {
                    // 合成一個重復按鍵事件給 mPendingEvent
                    mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
                } else {
                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
                    }
                }
            }
            // app 
            // Nothing to do if there is no pending event.
            // 如果此時 mPendingEvent 還是為 null,那么表示真的沒有事件需要處理,
            // 因此此次的分發(fā)循環(huán)就結束了
            if (!mPendingEvent) {
                return;
            }
        } else {
            // 1. 從收件箱中取出事件
            // mPendingEvent 表示正在處理的事件
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }
        // 如果這個事件需要傳遞給用戶,那么需要通知上層的 PowerManagerService,此時有用戶行為,
        // 例如,按下音量鍵,可以延長亮屏的時間
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(*mPendingEvent);
        }
    }
    // Now we have an event to dispatch.
    // All events are eventually dequeued and processed this way, even if we intend to drop them.
    ALOG_ASSERT(mPendingEvent != nullptr);
    bool done = false;
    // 如果事件需要被丟棄,那么丟棄的原因保存到 dropReason
    DropReason dropReason = DropReason::NOT_DROPPED;
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
        // 事件被截斷策略截斷了
        dropReason = DropReason::POLICY;
    } else if (!mDispatchEnabled) {
        // 系統(tǒng)沒有啟動完成,或者正在關機
        dropReason = DropReason::DISABLED;
    }
    if (mNextUnblockedEvent == mPendingEvent) {
        mNextUnblockedEvent = nullptr;
    }
    switch (mPendingEvent->type) {
        // ...
        case EventEntry::Type::KEY: {
            std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
            if (isAppSwitchDue) { // app 切換超時
                if (isAppSwitchKeyEvent(*keyEntry)) {
                    resetPendingAppSwitchLocked(true);
                    isAppSwitchDue = false;
                } else if (dropReason == DropReason::NOT_DROPPED) {
                    // app switch 事件超時,導致事件被丟棄
                    dropReason = DropReason::APP_SWITCH;
                }
            }
            // 按鍵事件發(fā)生在10秒之前,丟棄
            if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *keyEntry)) {
                dropReason = DropReason::STALE;
            }
            // mNextUnblockedEvent 與觸摸事件有關
            // 舉一個例子,如果有兩個窗口,當?shù)谝粋€窗口無響應時,如果用戶此時操作第二個窗口
            // 系統(tǒng)需要及時把事件發(fā)送給第二個窗口,因為此時第二個窗口是一個焦點窗口
            // 那么就需要系統(tǒng)把無響應窗口的事件丟棄,以免影響第二個窗口事件的分發(fā)
            if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
                dropReason = DropReason::BLOCKED;
            }
            // 2. 分發(fā)按鍵事件
            done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
            break;
        }
       // ...
    }
    // 3. 處理事件分發(fā)的結果
    // done 為 true,有兩種情況,一種是事件已經發(fā)送給指定窗口,二是事件已經被丟棄
    // done 為 false,表示暫時不止如何處理這個事件,組合鍵的第一個按鍵按下時,就是其中一種情況
    if (done) {
        // 處理事件被丟棄的情況
        if (dropReason != DropReason::NOT_DROPPED) {
            // 這里處理的一種情況是,如果窗口收到 DOWN 事件,但是 UP 事件由于某種原因被丟棄,那么需要補發(fā)一個 CANCEL 事件
            dropInboundEventLocked(*mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        // 無論事件發(fā)送給窗口,或者丟棄,都表示事件被處理了,因此重置 mPendingEvent
        releasePendingEventLocked();
        // 既然當前事件已經處理完,那么立即喚醒,處理下一個事件
        *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
    }
}

InputDispatcher 線程的一次事件分發(fā)的過程如下

  • 從收件箱取出事件。
  • 分發(fā)事件。參考【3. 按鍵事件的分發(fā)】
  • 處理事件分發(fā)后的結果。 事件被丟棄或者發(fā)送給指定窗口,都會返回 true,表示事件被處理了,因此會重置 mPendingEvent。而如果事件分發(fā)的結果返回 false,表示事件沒有被處理,這種情況表示系統(tǒng)暫時不知道如何處理,最常見的情況就是組合鍵的第一個按鍵被按下,例如截屏鍵的 power 鍵按下,此時系統(tǒng)不知道是不是要單獨處理這個按鍵,還要等待組合鍵的另外一個按鍵,在超時前按下,因此系統(tǒng)不知道如何處理,需要等等看。

3. 按鍵事件的分發(fā)

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // Preprocessing.
    if (!entry->dispatchInProgress) {
        // ...省略生成重復按鍵的代碼...
        // 表明事件處于分發(fā)中
        entry->dispatchInProgress = true;
        logOutboundKeyDetails("dispatchKey - ", *entry);
    }
    // 分發(fā)策略讓我們稍后再試,這是為了等待另外一個組合鍵的按鍵事件到來
    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
        // 還沒到等待的超時時間,那么繼續(xù)等待
        if (currentTime < entry->interceptKeyWakeupTime) {
            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
                *nextWakeupTime = entry->interceptKeyWakeupTime;
            }
            return false; // wait until next wakeup
        }
        // 這里表示已經超時了,因此需要再次詢問分發(fā)策略,看看結果
        // 例如,當截屏的power鍵超時時,再次詢問分發(fā)策略是否音量下鍵已經按下,如果按下了,
        // 那么這個 power 事件就不再分發(fā)給用戶
        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) {
        // 1. 如果事件需要分發(fā)給用戶,那么先查詢分發(fā)策略
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            if (INPUTDISPATCHER_SKIP_EVENT_KEY != 0) {
                if(entry->keyCode == 0 && entry->scanCode == INPUTDISPATCHER_SKIP_EVENT_KEY) {
                    entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
                    *dropReason = DropReason::POLICY;
                    ALOGI("Intercepted the key %i", INPUTDISPATCHER_SKIP_EVENT_KEY);
                    return true;
                }
            }
            // 創(chuàng)建一個命令
            std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
                    &InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
            sp<IBinder> focusedWindowToken =
                    mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));
            commandEntry->connectionToken = focusedWindowToken;
            commandEntry->keyEntry = entry;
            // mCommandQueue 中加入一個命令
            postCommandLocked(std::move(commandEntry));
            // 返回 false,表示需要運行命令看看這個事件是否需要觸發(fā)組合鍵的功能
            return false; // wait for the command to run
        } else {
            // 如果事件不傳遞給用戶,那么不會查詢分發(fā)策略是否截斷,而是自己接著處理,后面會丟棄它
            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
        }
    } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
        // 查詢分發(fā)策略得到的結果是讓我們跳過這個事件,不處理
        // 其中一種情況是,組合鍵的另外一個按鍵被消費了,因此是一個無效事件,讓我們丟棄它
        // 另外一種情況是,分發(fā)策略直接消費了這個事件,讓我們不要聲張,丟棄它
        if (*dropReason == DropReason::NOT_DROPPED) {
            *dropReason = DropReason::POLICY;
        }
    }
    // Clean up if dropping the event.
    // 如果事件有足夠的原因需要被丟棄,那么不執(zhí)行后面的事件分發(fā),而是直接保存事件注入的結果
    if (*dropReason != DropReason::NOT_DROPPED) {
        setInjectionResult(*entry,
                           *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                             : InputEventInjectionResult::FAILED);
        mReporter->reportDroppedKey(entry->id);
        return true;
    }
    // Identify targets.
    // 2. 找到目標輸入窗口,保存到 inputTargets
    std::vector<InputTarget> inputTargets;
    InputEventInjectionResult injectionResult =
            findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
    // 處理 InputEventInjectionResult::PENDING 結果
    // 表示現(xiàn)在處理事件的時機不成熟,例如窗口還在啟動中,那么直接結束此時的事件分發(fā),等待時機合適再處理
    if (injectionResult == InputEventInjectionResult::PENDING) {
        // 返回 false,那么會導致線程休眠一段時間,等再次喚醒時,再來處理事件
        return false;
    }
    // 保存注入結果
    setInjectionResult(*entry, injectionResult);
    // 處理 InputEventInjectionResult::FAILED 和 InputEventInjectionResult::PERMISSION_DENIED 結果
    // 表示沒有找到事件的輸入目標窗口
    if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
        // 返回 true,那么事件即將被丟棄
        return true;
    }
    // 走到這里,表示成功找到焦點窗口
    // Add monitor channels from event's or focused display.
    // 添加監(jiān)聽所有事件的通道
    // TODO:這個暫時不知道有何用
    addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
    // Dispatch the key.
    // 處理 InputEventInjectionResult::SUCCEEDED 結果,表明找到了事件輸入目標
    // 3. 事件分發(fā)按鍵給目標窗口
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

按鍵事件分發(fā)的主要過程如下

  • 執(zhí)行分發(fā)策略。分發(fā)策略的作用,一方面是實現(xiàn)組合按鍵的功能,另外一方面是為了在事件分發(fā)給窗口前,給系統(tǒng)一個優(yōu)先處理的機會。
  • 尋找處理按鍵事件的焦點窗口。參考【3.1 尋找焦點窗口】
  • 只有成功尋找到焦點窗口,才進行按鍵事件分發(fā)。參考【3.2 分發(fā)按鍵事件給目標窗口】

分發(fā)策略涉及到組合按鍵的實現(xiàn),因此是一個非常復雜的話題,我們將在后面的文章中,把它和截斷策略一起分析。

3.1 尋找焦點窗口

InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
        nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
        nsecs_t* nextWakeupTime) {
    std::string reason;
    int32_t displayId = getTargetDisplayId(entry);
    // 1. 獲取焦點窗口
    sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
    // 獲取焦點app
    std::shared_ptr<InputApplicationHandle> focusedApplicationHandle =
            getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
    // 沒有焦點窗口,也沒有焦點app,那么丟棄事件
    if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
        ALOGI("Dropping %s event because there is no focused window or focused application in "
              "display %" PRId32 ".",
              NamedEnum::string(entry.type).c_str(), displayId);
        return InputEventInjectionResult::FAILED;
    }
    // Drop key events if requested by input feature
    // 窗口feature為 DROP_INPUT 或 DROP_INPUT_IF_OBSCURED,那么丟棄事件
    if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
        return InputEventInjectionResult::FAILED;
    }
    // 沒有焦點窗口,但是有焦點app
    // 這里處理的情況是,app正在啟動,但是還沒有顯示即將獲得焦點的窗口
    if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) {
        if (!mNoFocusedWindowTimeoutTime.has_value()) {
            // 啟動一個 ANR 計時器,超時時間默認5秒
            std::chrono::nanoseconds timeout = focusedApplicationHandle->getDispatchingTimeout(
                    DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            mNoFocusedWindowTimeoutTime = currentTime + timeout.count();
            // 保存正在等待焦點窗口的app
            mAwaitedFocusedApplication = focusedApplicationHandle;
            mAwaitedApplicationDisplayId = displayId;
            ALOGW("Waiting because no window has focus but %s may eventually add a "
                  "window when it finishes starting up. Will wait for %" PRId64 "ms",
                  mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
            // 線程喚醒時間修改為超時時間,以保證能及時處理 ANR
            *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
            // 由于焦點窗口正在啟動,暫時不處理事件
            return InputEventInjectionResult::PENDING;
        } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
            // Already raised ANR. Drop the event
            ALOGE("Dropping %s event because there is no focused window",
                  NamedEnum::string(entry.type).c_str());
            // 等待焦點窗口超時,丟棄這個事件
            return InputEventInjectionResult::FAILED;
        } else {
            // Still waiting for the focused window
            // 等待焦點窗口還沒有超時,繼續(xù)等待,暫時不處理事件
            return InputEventInjectionResult::PENDING;
        }
    }
    // 走到這里,表示已經有了一個有效的焦點窗口
    // we have a valid, non-null focused window
    // 因為有了有效焦點窗口,重置 mNoFocusedWindowTimeoutTime 和 mAwaitedFocusedApplication
    resetNoFocusedWindowTimeoutLocked();
    // 對于注入事件,如果注入者的 UID 與窗口所屬的 UDI 不同,并且注入者沒有 android.Manifest.permission.INJECT_EVENTS 權限
    // 那么注入事件將會被丟棄
    if (!checkInjectionPermission(focusedWindowHandle, entry.injectionState)) {
        return InputEventInjectionResult::PERMISSION_DENIED;
    }
    // 窗口處于暫停狀態(tài),暫時不處理當前按鍵事件
    if (focusedWindowHandle->getInfo()->paused) {
        ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
        return InputEventInjectionResult::PENDING;
    }
    // 如果前面還有事件沒有處理完畢,那么需要等待前面事件處理完畢,才能發(fā)送按鍵事件
    // 因為前面的事件,可能影響焦點窗口,所以按鍵事件需要等待前面事件處理完畢才能發(fā)送
    if (entry.type == EventEntry::Type::KEY) {
        if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
            *nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
            // 前面有時間沒有處理完畢,因此暫不處理當前的按鍵事件
            return InputEventInjectionResult::PENDING;
        }
    }
    // Success!  Output targets.
    // 走到這里,表示事件可以成功發(fā)送焦點窗口
    // 2. 根據(jù)焦點窗口創(chuàng)建 InputTarget,并保存到參數(shù) inputTargets
    // 注意第二個參數(shù),后面把事件加入到一個輸入通道鏈接的收件箱時,會用到
    addWindowTargetLocked(focusedWindowHandle,
                          InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
                          BitSet32(0), inputTargets);
    // Done.
    return InputEventInjectionResult::SUCCEEDED;
}

尋找目標窗口的過程其實就是找到焦點窗口,然后根據(jù)焦點窗口創(chuàng)建 InputTarget,保存到參數(shù) inputTargets 中。

結合前面的代碼分析,尋找焦點窗口返回的結果,會影響事件的處理,總結如下

尋找焦點窗口的結果結果的說明如何影響事件的處理
InputEventInjectionResult::SUCCEEDED成功為事件找到焦點窗口事件會被分發(fā)到焦點窗口
InputEventInjectionResult::FAILED沒有找到可用的焦點窗口事件會被丟棄
InputEventInjectionResult::PERMISSION_DENIED沒有權限把事件發(fā)送到焦點窗口事件會被丟棄
InputEventInjectionResult::PENDING有焦點窗口,但是暫時不可用于接收事件線程會休眠,等待時機被喚醒,發(fā)送事件到焦點窗口

在工作中,有時候需要我們分析按鍵事件為何沒有找到焦點窗口,這里列舉一下所有的情況,以供大家工作或面試時使用

  • 沒有焦點app,并且沒有焦點窗口。這種情況應該比較極端,應該整個surface系統(tǒng)都出問題了。
  • 窗口的 Feature 表示要丟棄事件。這個丟棄事件的 Feature 是 Surface 系統(tǒng)給窗口設置的,目前我還沒有搞清楚這里面的邏輯。
  • 焦點app啟動焦點窗口,超時了。
  • 對于注入事件,如果注入者的 UID 與焦點窗口的 UID 不同,并且注入者沒有申請 android.Manifest.permission.INJECT_EVENTS 權限。

沒有找到焦點窗口的所有情況,都有日志對應輸出,可幫助我們定位問題。

現(xiàn)在來看一下,找到焦點窗口后,創(chuàng)建并保存 InputTarget 的過程

// 注意,參數(shù) targetFlags 的值為 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
// InputTarget::FLAG_FOREGROUND 表示事件正在發(fā)送給前臺窗口
// InputTarget::FLAG_DISPATCH_AS_IS 表示事件不加工,直接按照原樣進行發(fā)送
void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                            int32_t targetFlags, BitSet32 pointerIds,
                                            std::vector<InputTarget>& inputTargets) {
    std::vector<InputTarget>::iterator it =
            std::find_if(inputTargets.begin(), inputTargets.end(),
                         [&windowHandle](const InputTarget& inputTarget) {
                             return inputTarget.inputChannel->getConnectionToken() ==
                                     windowHandle->getToken();
                         });
    const WindowInfo* windowInfo = windowHandle->getInfo();
    if (it == inputTargets.end()) {
        // 創(chuàng)建 InputTarget
        InputTarget inputTarget;
        // 獲取窗口通道
        std::shared_ptr<InputChannel> inputChannel =
                getInputChannelLocked(windowHandle->getToken());
        if (inputChannel == nullptr) {
            ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
            return;
        }
        // 保存輸入通道,通過這個通道,事件才能發(fā)送給指定窗口
        inputTarget.inputChannel = inputChannel;
        // 注意,值為 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
        inputTarget.flags = targetFlags;
        inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
        inputTarget.displayOrientation = windowInfo->displayOrientation;
        inputTarget.displaySize =
                int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
        // 保存到 inputTargets中
        inputTargets.push_back(inputTarget);
        it = inputTargets.end() - 1;
    }
    ALOG_ASSERT(it->flags == targetFlags);
    ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
    // InputTarget 保存窗口的 Transform 信息,這會把顯示屏的坐標,轉換到窗口的坐標系上
    // 對于按鍵事件,不需要把顯示屏坐標轉換到窗口坐標
    // 因此,對于按鍵事件,pointerIds 為0,這里只是簡單保存一個默認的 Transfrom 而已
    it->addPointers(pointerIds, windowInfo->transform);
}

3.2 分發(fā)按鍵事件給目標窗口

現(xiàn)在,處理按鍵事件的焦點窗口已經找到,并且已經保存到 inputTargets,是時候來分發(fā)按鍵事件了

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
                                          std::shared_ptr<EventEntry> eventEntry,
                                          const std::vector<InputTarget>& inputTargets) {
    updateInteractionTokensLocked(*eventEntry, inputTargets);
    pokeUserActivityLocked(*eventEntry);
    for (const InputTarget& inputTarget : inputTargets) {
        // 獲取目標窗口的連接
        sp<Connection> connection =
                getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
        if (connection != nullptr) {
            // 準備分發(fā)循環(huán)
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
        } else {
            if (DEBUG_FOCUS) {
                ALOGD("Dropping event delivery to target with channel '%s' because it "
                      "is no longer registered with the input dispatcher.",
                      inputTarget.inputChannel->getName().c_str());
            }
        }
    }
}

焦點窗口只有一個,為何需要一個 inputTargets 集合來保存所有的目標窗口,因為根據(jù)前面的分析,除了焦點窗口以外,還有一個全局的監(jiān)聽事件的輸入目標。

WindowManagerService 會在創(chuàng)建窗口時,創(chuàng)建一個連接,其中一端給窗口,另外一端給輸入系統(tǒng)。當輸入系統(tǒng)需要發(fā)送事件給窗口時,就會通過這個連接進行發(fā)送。至于連接的建立過程,有點小復雜,本分不分析,后面如果寫 WMS 的文章,再來細致分析一次。

找到這個窗口的連接后,就準備分發(fā)循環(huán) ? 問題來了,什么是分發(fā)循環(huán) ? InputDispatcher 把一個事件發(fā)送給窗口,窗口處理完事件,然后返回結果為 InputDispatcher,這就是一個循環(huán)。但是注意,分發(fā)事件給窗口,窗口返回處理事件結果,這兩個是互為異步過程。

現(xiàn)在來看下分發(fā)循環(huán)之前的準備

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
                                                 const sp<Connection>& connection,
                                                 std::shared_ptr<EventEntry> eventEntry,
                                                 const InputTarget& inputTarget) {
    // ...
    // 連接處理異常狀態(tài),丟棄事件
    if (connection->status != Connection::STATUS_NORMAL) {
#if DEBUG_DISPATCH_CYCLE
        ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
              connection->getInputChannelName().c_str(), connection->getStatusLabel());
#endif
        return;
    }
    // Split a motion event if needed.
    // 針對觸摸事件的split
    if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
        // ...
    }
    // Not splitting.  Enqueue dispatch entries for the event as is.
    // 把事件加入到連接的發(fā)件箱中,然后啟動分發(fā)循環(huán)
    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
                                                   const sp<Connection>& connection,
                                                   std::shared_ptr<EventEntry> eventEntry,
                                                   const InputTarget& inputTarget) {
    // ...
    bool wasEmpty = connection->outboundQueue.empty();
    // Enqueue dispatch entries for the requested modes.
    // 1. 保存事件到連接的發(fā)件箱 Connection::outboundQueue 
    // 注意最后一個參數(shù),它的窗口的分發(fā)模式,定義了事件如何分發(fā)到指定窗口
    // 根據(jù)前面的代碼分析,目前保存的目標窗口的分發(fā)模式只支持下面列舉的 InputTarget::FLAG_DISPATCH_AS_IS 
    // InputTarget::FLAG_DISPATCH_AS_IS 表示事件按照原樣進行發(fā)送
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_IS);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
    // If the outbound queue was previously empty, start the dispatch cycle going.
    // 連接的發(fā)件箱突然有事件了,那得啟動分發(fā)循環(huán),把事件發(fā)送到指定窗口
    if (wasEmpty && !connection->outboundQueue.empty()) {
        // 2. 啟動分發(fā)循環(huán)
        startDispatchCycleLocked(currentTime, connection);
    }
}

分發(fā)循環(huán)前的準備工作,其實就是根據(jù)窗口所支持的分發(fā)模式(dispatche mode),調用enqueueDispatchEntryLocked() 創(chuàng)建并保存事件到連接的收件箱。前面分析過,焦點窗口的的分發(fā)模式為 InputTarget::FLAG_DISPATCH_AS_IS | InputTarget::FLAG_FOREGROUND,而此時只用到了InputTarget::FLAG_DISPATCH_AS_IS。 參考【3.2.1 根據(jù)分發(fā)模式,添加事件到連接收件箱】

如果連接的收件箱之前沒有事件,那么證明連接沒有處于發(fā)送事件的狀態(tài)中,而現(xiàn)在有事件了,那就啟動分發(fā)循環(huán)來發(fā)送事件。參考 【3.2.2 啟動分發(fā)循環(huán)】

3.2.1 根據(jù)分發(fā)模式,添加事件到連接收件箱

void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
                                                 std::shared_ptr<EventEntry> eventEntry,
                                                 const InputTarget& inputTarget,
                                                 int32_t dispatchMode) {
    // ...
    // 前面保存的 InputTarget,它的 flags 為 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
    int32_t inputTargetFlags = inputTarget.flags;
    // 窗口不支持請求的dispatcher mode,那么不添加事件到連接的發(fā)件箱中
    // 對于按鍵事件,dispatchMode 只能是 InputTarget::FLAG_DISPATCH_AS_IS
    if (!(inputTargetFlags & dispatchMode)) {
        return;
    }
    // 1. 為每一個窗口所支持的 dispatche mode,創(chuàng)建一個 DispatchEntry
    inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
    std::unique_ptr<DispatchEntry> dispatchEntry =
            createDispatchEntry(inputTarget, eventEntry, inputTargetFlags);
    // Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
    // different EventEntry than what was passed in.
    EventEntry& newEntry = *(dispatchEntry->eventEntry);
    // Apply target flags and update the connection's input state.
    switch (newEntry.type) {
        case EventEntry::Type::KEY: {
            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(newEntry);
            dispatchEntry->resolvedEventId = keyEntry.id;
            dispatchEntry->resolvedAction = keyEntry.action;
            dispatchEntry->resolvedFlags = keyEntry.flags;
            if (!connection->inputState.trackKey(keyEntry, dispatchEntry->resolvedAction,
                                                 dispatchEntry->resolvedFlags)) {
#if DEBUG_DISPATCH_CYCLE
                ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
                      connection->getInputChannelName().c_str());
#endif
                return; // skip the inconsistent event
            }
            break;
        }
        // ...
    }
    // Remember that we are waiting for this dispatch to complete.
    // 檢測事件是否正在發(fā)送到前臺窗應用,根據(jù)前面的代碼分析,目標窗口的flags包括 FLAG_FOREGROUND
    // 因此,條件成立
    if (dispatchEntry->hasForegroundTarget()) {
        // EventEntry::injectionState::pendingForegroundDispatches +1
        incrementPendingForegroundDispatches(newEntry);
    }
    // 2. 把 DispatchEntry 加入到連接的發(fā)件箱中
    connection->outboundQueue.push_back(dispatchEntry.release());
    traceOutboundQueueLength(*connection);
}

根據(jù)前面創(chuàng)建 InputTarget 的代碼可知,InputTarget::flags 的值為 InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS。

InputTarget::FLAG_FOREGROUND 表明事件正在發(fā)送給前臺應用,InputTarget::FLAG_DISPATCH_AS_IS 表示事件按照原樣進行發(fā)送。

而參數(shù) dispatchMode 只使用了 InputTarget::FLAG_DISPATCH_AS_IS,因此,對于按鍵事件,只會創(chuàng)建并添加一個 DispatchEntry 到 Connection::outboundQueue。

3.2.2 啟動分發(fā)循環(huán)

現(xiàn)在,焦點窗口連接的發(fā)件箱中已經有事件了,此時真的到了發(fā)送事件給焦點窗口的時候了

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
                                               const sp<Connection>& connection) {
    // ...
    // 遍歷連接發(fā)件箱中的所有事件,逐個發(fā)送給目標窗口
    while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.front();
        dispatchEntry->deliveryTime = currentTime;
        // 計算事件分發(fā)的超時時間
        const std::chrono::nanoseconds timeout =
                getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
        dispatchEntry->timeoutTime = currentTime + timeout.count();
        // Publish the event.
        status_t status;
        const EventEntry& eventEntry = *(dispatchEntry->eventEntry);
        switch (eventEntry.type) {
            case EventEntry::Type::KEY: {
                const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
                std::array<uint8_t, 32> hmac = getSignature(keyEntry, *dispatchEntry);
                // 1. 發(fā)送按鍵事件
                status = connection->inputPublisher
                                 .publishKeyEvent(dispatchEntry->seq,
                                                  dispatchEntry->resolvedEventId, keyEntry.deviceId,
                                                  keyEntry.source, keyEntry.displayId,
                                                  std::move(hmac), dispatchEntry->resolvedAction,
                                                  dispatchEntry->resolvedFlags, keyEntry.keyCode,
                                                  keyEntry.scanCode, keyEntry.metaState,
                                                  keyEntry.repeatCount, keyEntry.downTime,
                                                  keyEntry.eventTime);
                break;
            }
            // ...
        }
        // Check the result.
        if (status) {
            // 發(fā)送異常
            if (status == WOULD_BLOCK) {
                // ...
            }
            return;
        }
        // 走到這里,表示按鍵事件發(fā)送成功
        // 2. 按鍵事件發(fā)送成功,那么從連接的發(fā)件箱中移除
        connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
                                                    connection->outboundQueue.end(),
                                                    dispatchEntry));
        traceOutboundQueueLength(*connection);
        // 3. 把已經發(fā)送的事件,加入到連接的等待隊列中 Connection::waitQueue
        // 連接在等待什么呢?當然是等到窗口的處理結果
        connection->waitQueue.push_back(dispatchEntry);
        // 連接可響應,那么會記錄事件處理的超時時間,一旦超時,會引發(fā) ANR
        // 因為我們不可能無限等待窗口處理完事件,后面還有好多事件要處理呢
        // 4. 用 AnrTracker 記錄事件處理的超時時間
        if (connection->responsive) {
            mAnrTracker.insert(dispatchEntry->timeoutTime,
                               connection->inputChannel->getConnectionToken());
        }
        traceWaitQueueLength(*connection);
    }
}

事件分發(fā)循環(huán)的過程如下

  • 通過窗口連接,把事件發(fā)送給窗口,并從連接的發(fā)件箱 Connection::outboundQueue 中移除。
  • 把剛剛發(fā)送的事件,保存到連接的等待隊列 Connection::waitQueue。連接在等待什么呢?當然是等到窗口的處理結果。
  • 用 AnrTracker 記錄事件處理的超時時間,如果事件處理超時,會引發(fā) ANR。

既然叫做一個循環(huán),現(xiàn)在事件已經發(fā)送出去了,那么如何接收處理結果呢? InputDispatcher 線程使用了底層的 Looper 機制,當窗口與輸入系統(tǒng)建立連接時,Looper 通過 epoll 機制監(jiān)聽連接的輸入端的文件描述符,當窗口通過連接反饋處理結果時,epoll 就會收到可讀事件,因此 InputDispatcher 線程會被喚醒來讀取窗口的事件處理結果,而這個過程就是下面的下面的回調函數(shù)

如果讀者想了解底層的 Looper 機制,可以參考我寫的 深入理解Native層消息機制

int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
    std::scoped_lock _l(mLock);
    // 1. 獲取對應的連接
    sp<Connection> connection = getConnectionLocked(connectionToken);
    if (connection == nullptr) {
        ALOGW("Received looper callback for unknown input channel token %p.  events=0x%x",
              connectionToken.get(), events);
        return 0; // remove the callback
    }
    bool notify;
    if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
        if (!(events & ALOOPER_EVENT_INPUT)) {
            ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
                  "events=0x%x",
                  connection->getInputChannelName().c_str(), events);
            return 1;
        }
        nsecs_t currentTime = now();
        bool gotOne = false;
        status_t status = OK;
        // 通過一個無限循環(huán)讀取,盡可能讀取所有的反饋結果
        for (;;) {
            // 2. 讀取窗口的處理結果
            Result<InputPublisher::ConsumerResponse> result =
                    connection->inputPublisher.receiveConsumerResponse();
            if (!result.ok()) {
                status = result.error().code();
                break;
            }
            if (std::holds_alternative<InputPublisher::Finished>(*result)) {
                const InputPublisher::Finished& finish =
                        std::get<InputPublisher::Finished>(*result);
                // 3. 完成分發(fā)循環(huán)
                finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,
                                          finish.consumeTime);
            } else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
                // ...
            }
            gotOne = true;
        }
        if (gotOne) {
            // 4. 執(zhí)行第三步發(fā)送的命令
            runCommandsLockedInterruptible();
            if (status == WOULD_BLOCK) {
                return 1;
            }
        }
        notify = status != DEAD_OBJECT || !connection->monitor;
        if (notify) {
            ALOGE("channel '%s' ~ Failed to receive finished signal.  status=%s(%d)",
                  connection->getInputChannelName().c_str(), statusToString(status).c_str(),
                  status);
        }
    } else {
        // ...
    }
    // Remove the channel.
    // 連接的所有事件都發(fā)送完畢了,從 mAnrTracker 和 mConnectionsByToken 移除相應的數(shù)據(jù)
    // TODO: 為何一定要移除呢?
    removeInputChannelLocked(connection->inputChannel->getConnectionToken(), notify);
    return 0; // remove the callback
}

處理窗口反饋的事件處理結果的過程如下

  • 根據(jù)連接的 token 獲取連接。
  • 從連接中讀取窗口返回的事件處理結果。
  • 完成這個事件的分發(fā)循環(huán)。此過程會創(chuàng)建一個命令,并加如到命令隊列中,然后在第四步執(zhí)行。
  • 執(zhí)行第三步創(chuàng)建的命令,以完成分發(fā)循環(huán)。

當監(jiān)聽到窗口的連接有事件到來時,會從連接讀取窗口對事件的處理結果,然后創(chuàng)建一個即將執(zhí)行的命令,保存到命令隊列中,如下

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
                                                const sp<Connection>& connection, uint32_t seq,
                                                bool handled, nsecs_t consumeTime) {
    if (connection->status == Connection::STATUS_BROKEN ||
        connection->status == Connection::STATUS_ZOMBIE) {
        return;
    }
    // Notify other system components and prepare to start the next dispatch cycle.
    onDispatchCycleFinishedLocked(currentTime, connection, seq, handled, consumeTime);
}
void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime,
                                                    const sp<Connection>& connection, uint32_t seq,
                                                    bool handled, nsecs_t consumeTime) {
    // 創(chuàng)建命令并加入到命令隊列 mCommandQueue 中
    // 當命令調用時,會執(zhí)行 doDispatchCycleFinishedLockedInterruptible 函數(shù)
    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
            &InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
    commandEntry->connection = connection;
    commandEntry->eventTime = currentTime;
    commandEntry->seq = seq;
    commandEntry->handled = handled;
    commandEntry->consumeTime = consumeTime;
    postCommandLocked(std::move(commandEntry));
}

命令是用來完成事件分發(fā)循環(huán)的,那么命令什么時候執(zhí)行呢?這就是第四步執(zhí)行的,最終調用如下函數(shù)來執(zhí)行命令

void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) {
    sp<Connection> connection = commandEntry->connection;
    const nsecs_t finishTime = commandEntry->eventTime;
    uint32_t seq = commandEntry->seq;
    const bool handled = commandEntry->handled;
    // Handle post-event policy actions.
    // 1. 根據(jù)序號seq,從連接的等待隊列中獲取事件
    std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
    if (dispatchEntryIt == connection->waitQueue.end()) {
        return;
    }
    // 獲取對應的事件
    DispatchEntry* dispatchEntry = *dispatchEntryIt;
    // 如果事件處理的有一點點慢,但是沒超過超時事件,那么這里會給一個警告
    // 這也說明,窗口處理事件,不要執(zhí)行耗時的代碼
    const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
    if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
        ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
              ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
    }
    if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
        mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
                                           connection->inputChannel->getConnectionToken(),
                                           dispatchEntry->deliveryTime, commandEntry->consumeTime,
                                           finishTime);
    }
    bool restartEvent;
    if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
        KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry->eventEntry));
        restartEvent =
                afterKeyEventLockedInterruptible(connection, dispatchEntry, keyEntry, handled);
    } else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
        MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry->eventEntry));
        restartEvent = afterMotionEventLockedInterruptible(connection, dispatchEntry, motionEntry,
                                                           handled);
    } else {
        restartEvent = false;
    }
    // Dequeue the event and start the next cycle.
    // Because the lock might have been released, it is possible that the
    // contents of the wait queue to have been drained, so we need to double-check
    // a few things.
    dispatchEntryIt = connection->findWaitQueueEntry(seq);
    if (dispatchEntryIt != connection->waitQueue.end()) {
        dispatchEntry = *dispatchEntryIt;
        // 2. 從 Connection::waitQueue 中移除等待反饋的事件
        connection->waitQueue.erase(dispatchEntryIt);
        const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
        // 3. 既然事件處理結果已經反饋了,那么就不用再記錄它的處理超時時間了
        mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
        // 連接從無響應變?yōu)榭身憫敲赐V?ANR
        if (!connection->responsive) {
            connection->responsive = isConnectionResponsive(*connection);
            if (connection->responsive) {
                // The connection was unresponsive, and now it's responsive.
                processConnectionResponsiveLocked(*connection);
            }
        }
        traceWaitQueueLength(*connection);
        if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
            connection->outboundQueue.push_front(dispatchEntry);
            traceOutboundQueueLength(*connection);
        } else {
            releaseDispatchEntry(dispatchEntry);
        }
    }
    // Start the next dispatch cycle for this connection.
    // 4. 既然通過連接收到反饋,那趁這個機會,如果發(fā)件箱還有事件,繼續(xù)啟動分發(fā)循環(huán)來發(fā)送事件
    startDispatchCycleLocked(now(), connection);
}

分發(fā)循環(huán)的完成過程如下

  • 檢查連接中是否有對應的正在的等待的事件。
  • 既然窗口已經反饋的事件的處理結果,那么從連接的等待隊列 Connection::waitQueue 中移除。
  • 既然窗口已經反饋的事件的處理結果,那么就不必處理這個事件的 ANR,因此移除事件的 ANR 超時時間。
  • 既然此時窗口正在反饋事件的處理結果,那趁熱打鐵,那么開啟下一次分發(fā)循環(huán),發(fā)送連接發(fā)件箱中的事件。當然,如果發(fā)件箱沒有事件,那么什么也不做。

完成分發(fā)循環(huán),其實最主要的就是把按鍵事件從連接的等待隊列中移除,以及解除 ANR 的觸發(fā)。

總結

本文雖然分析的只是按鍵事件的分發(fā)過程,但是從整體上剖析了所有事件的分發(fā)過程。我們將以此為基礎去分析觸摸事件(motion event)的分發(fā)過程。

現(xiàn)在總結下一個按鍵事件的基本發(fā)送流程

  • InputReader 線程把按鍵事件加入到 InputDispatcher 的收件箱之前,會詢問截斷策略,如果策略截斷了,那么事件最終不會發(fā)送給窗口。
  • InputDispatcher 通過一次線程循環(huán)來發(fā)送按鍵事件
  • 事件在發(fā)送之前,會循環(huán)分發(fā)策略,主要是為了實現(xiàn)組合按鍵功能。
  • 如果截斷策略和分發(fā)策略都不截斷按鍵事件,那么會尋找能處理按鍵事件的焦點窗口。
  • 焦點窗口找到了,那么會把按鍵事件加入到窗口連接的發(fā)件箱中。
  • 執(zhí)行分發(fā)循環(huán),從窗口連接的發(fā)件箱中獲取事件,然后發(fā)送給窗口。然后把事件從發(fā)件箱中移除,并加入到連接的等待隊列中。最后,記錄 ANR 時間。
  • 窗口返回事件的處理結果,InputDispatcher 會讀取結果,然后把事件從連接的等待隊列中移除,然后解除 ANR 的觸發(fā)。
  • 繼續(xù)發(fā)送連接中的事件,并重復上述過程,直至連接中沒有事件為止。

感想

我是一個注重實際效果的人,我花這么大力氣去分析事件的分發(fā)流程,是否值得? 從長期的考慮看,肯定是值得的,從短期看,我們可以從 trace log 中分析出 ANR 的原因是否是因為事件處理超時。

以上就是Input系統(tǒng)按鍵事件的分發(fā)處理示例詳解的詳細內容,更多關于Input系統(tǒng)按鍵事件分發(fā)的資料請關注腳本之家其它相關文章!

相關文章

  • Android 三級NestedScroll嵌套滾動實踐

    Android 三級NestedScroll嵌套滾動實踐

    這篇文章主要介紹了Android 三級NestedScroll嵌套滾動實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-02-02
  • Android Studio 3.6 新特性一覽(推薦)

    Android Studio 3.6 新特性一覽(推薦)

    這篇文章主要介紹了Android Studio 3.6 新特性一覽,本文圖文并茂給大家介紹的非常詳細,對大家的工作或學習具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • Android自定義ViewPager實例

    Android自定義ViewPager實例

    這篇文章主要介紹了Android自定義ViewPager的方法,結合完整實例形式分析了Android基于ViewGroup類自定義ViewPager的具體實現(xiàn)技巧,需要的朋友可以參考下
    2016-02-02
  • 淺析Android位置權限以及數(shù)組尋找索引的坑

    淺析Android位置權限以及數(shù)組尋找索引的坑

    這篇文章給大家分享了Android位置權限以及數(shù)組尋找索引的坑的相關知識點內容,有興趣的朋友可以參考學習下。
    2018-07-07
  • Android底部菜單欄(RadioGroup+Fragment)美化

    Android底部菜單欄(RadioGroup+Fragment)美化

    這篇文章主要介紹了Android底部菜單欄RadioGroup+Fragment美化,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • Android使用Volley框架定制PostUploadRequest上傳文件

    Android使用Volley框架定制PostUploadRequest上傳文件

    這篇文章主要為大家詳細介紹了Android使用Volley框架定制PostUploadRequest上傳文件或圖片,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • Android 完全退出當前應用程序的四種方法

    Android 完全退出當前應用程序的四種方法

    Android程序有很多Activity,比如說主窗口A,調用了子窗口B,如果在B中直接finish(), 接下里顯示的是A。在B中如何關閉整個Android應用程序呢?本人總結了幾種比較簡單的實現(xiàn)方法
    2016-02-02
  • Android 在子線程中更新UI的幾種方法示例

    Android 在子線程中更新UI的幾種方法示例

    本篇文章主要介紹了Android 在子線程中更新UI的幾種方法示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-08-08
  • Android實現(xiàn)自動輪播圖效果

    Android實現(xiàn)自動輪播圖效果

    這篇文章主要為大家詳細介紹了Android實現(xiàn)自動輪播圖效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • Action獲取請求參數(shù)的三種方式

    Action獲取請求參數(shù)的三種方式

    這篇文章主要介紹了Action獲取請求參數(shù)的三種方式的,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-06-06

最新評論