Android Input 4

這篇筆記主要記錄Android Input的intercept, Fallback key, Joystick的方向鍵

先來一張overview

Keyevent.png

1. interceptKeyBeforeQueueing

Keyboard產(chǎn)生按鍵事件后,會通過notifyKey開始傳遞,至于前面的流程就不在這里啰嗦了。

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    ...
    uint32_t policyFlags = args->policyFlags; //只關(guān)注policyFlags特別重要
    ...
    policyFlags |= POLICY_FLAG_TRUSTED; //指明這個input事件是來自于trusted source

    KeyEvent event;
    event.initialize(args->deviceId, args->source, args->action,
            flags, keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);

    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
    bool needWake;
    { // acquire lock
        if (shouldSendKeyToInputFilterLocked(args)) {
            policyFlags |= POLICY_FLAG_FILTERED;
            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
                return; // 如果event被InputFilter消費掉了,直接返回,結(jié)束Input事件分發(fā)流程
            }
        }
       //將處理后的policy 保存到event里
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);

        needWake = enqueueInboundEventLocked(newEntry);
        mLock.unlock();
    } // release lock
    ...
}

上面的代碼有兩個比較重要, 一個是interceptKeyBeforeQueueing, 另一個filterInputEvent

先來看filterInputEvent吧
filterInputEvent被調(diào)用的前提是shouldSendKeyToInputFilterLocked,也就是說Java端的IMS通過nativeSetInputFilterEnabled設(shè)置了InputFilter, 即在Java層做Input filter動作,所以如果Java層filterInputEvent即消費了Input事件,此時Input分發(fā)事件就結(jié)束掉.

在這里不深究InputFilter的情況

下面來看interceptKeyBeforeQueueing,故名思義,這個intercept是在將input Event enqueue到InputDispatcher之前做的攔截.

void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
        uint32_t& policyFlags) {
      ...
    if ((policyFlags & POLICY_FLAG_TRUSTED)) {
          ...
        if (keyEventObj) {
            wmActions = env->CallIntMethod(mServiceObj,
                    gServiceClassInfo.interceptKeyBeforeQueueing,
                    keyEventObj, policyFlags);
            if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
                wmActions = 0;
            }
        } else {
            ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
            wmActions = 0;
        }

        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
    } else {
      ...
    }
}

interceptKeyBeforeQueueing在native層基本上沒做什么, 只是call Java層也就是IMS的interceptKeyBeforeQueueing, 然后將攔截結(jié)果傳遞給 handleInterceptActions

void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
        uint32_t& policyFlags) {
    if (wmActions & WM_ACTION_PASS_TO_USER) { //WM_ACTION_PASS_TO_USER=1
        policyFlags |= POLICY_FLAG_PASS_TO_USER;
    } else {
#if DEBUG_INPUT_DISPATCHER_POLICY
        ALOGD("handleInterceptActions: Not passing key to user.");
#endif
    }
}

如果interceptKeyBeforeQueueing攔截結(jié)果為1的話,在JAVA層對應(yīng)的是ACTION_PASS_TO_USER, 意思是攔截的結(jié)果是沒有設(shè)置該bit, 即表明JAVA層IMS消費了該事件。但是特別注意,這里并沒有結(jié)束Input事件傳遞。 而是將policy保存到input event里,繼續(xù)分發(fā)流程。
那何時處理呢?

InputDispatcher在有event事件發(fā)生后,會觸發(fā)dispatchOnceInnerLocked

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
...
    DropReason dropReason = DROP_REASON_NOT_DROPPED;
    //mPendingEvent就是上面所說的按鍵事件
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
        dropReason = DROP_REASON_POLICY;  //如果event被IMS消費了,此時在這里會設(shè)置dropReason
    } else if (!mDispatchEnabled) {
        dropReason = DROP_REASON_DISABLED;
    }
    switch (mPendingEvent->type) {
      ...
    case EventEntry::TYPE_KEY: {
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
        ...
        if (dropReason == DROP_REASON_NOT_DROPPED
                && isStaleEventLocked(currentTime, typedEntry)) {
            dropReason = DROP_REASON_STALE;
        }
        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
            dropReason = DROP_REASON_BLOCKED;
        }
        //即使攔截了,也要調(diào)用dispatchKeyLocked. 多此一舉么???奇怪
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }
   }
    if (done) { 
        if (dropReason != DROP_REASON_NOT_DROPPED) {
           //進(jìn)入清理工作,最終調(diào)用synthesizeCancelationEventsForAllConnectionsLocked向所有的
           //input client端發(fā)送cancel事件,即一個ACTION_UP事件, keycode還是被攔截的keycode
            dropInboundEventLocked(mPendingEvent, dropReason); 
        }
        mLastDropReason = dropReason;
      ...
    }
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ...
    //默認(rèn)是UNKNOWN
    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { //由于被攔截了,這里不會再調(diào)用了
            CommandEntry* commandEntry = postCommandLocked(
                    & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
            if (mFocusedWindowHandle != NULL) {
                commandEntry->inputWindowHandle = mFocusedWindowHandle;
            }
            return false; // wait for the command to run
        } else {
            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
        }
    } 

    // Clean up if dropping the event.
    if (*dropReason != DROP_REASON_NOT_DROPPED) {
        setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
                ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
       //原來在這里結(jié)果input事件分發(fā)啊
        return true;
    }
    ...
}

原來是在dispatchKeyLocked里結(jié)束了事件分發(fā), 繞了一大圈啊. 最后由dropInboundEventLocked向所有的input client發(fā)送 cancel 的事件,即一個ACTION_UP事件,還是被攔截的keycode.

好了,interceptKeyBeforeQueueing 在這里就結(jié)束了

2. interceptKeyBeforeDispatching

如果interceptKeyBeforeQueueing沒有攔截成功,那么就該輪著interceptKeyBeforeDispatching

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {

    // Give the policy a chance to intercept the key.
    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { //這里是1沒有攔截
            CommandEntry* commandEntry = postCommandLocked(
                    & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
            if (mFocusedWindowHandle != NULL) {
                commandEntry->inputWindowHandle = mFocusedWindowHandle;
            }
            commandEntry->keyEntry = entry;
            entry->refCount += 1;
            logOutboundKeyDetailsLocked("dispatchKey return 1 - ", entry);
            return false; // wait for the command to run
        } else {
            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
        }
    } ...

input event第一次進(jìn)來interceptKeyResult默認(rèn)為INTERCEPT_KEY_RESULT_UNKNOWN, 而且interceptKeyBeforeDispatching并沒有攔截,所以entry->policyFlags&POLICY_FLAG_PASS_TO_USER=true 這沒什么好說的, 如上代碼所示,dispatchKeyLocked函數(shù)在post一個command后直接返回了,并沒有繼續(xù)往下發(fā)送輸入事件了。

postCommandLocked將待執(zhí)行的函數(shù)指針保存到mCommandQueue隊列中。
那doInterceptKeyBeforeDispatchingLockedInterruptible什么時候被執(zhí)行的呢?

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    {  ...
        if (!haveCommandsLocked()) { //檢查mCommandQueue隊列是否為空,
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        if (runCommandsLockedInterruptible()) { //執(zhí)行 mCommandQueue 里的command函數(shù)
            nextWakeupTime = LONG_LONG_MIN;
        }
    } // release lock
}

InputDispatcher::dispatchOnce函數(shù)會先檢查 mCommandQueue中隊列是否為空,如果不為空會優(yōu)先執(zhí)行mCommandQueue里的函數(shù),所以此時就開始執(zhí)行
doInterceptKeyBeforeDispatchingLockedInterruptible

void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
        CommandEntry* commandEntry) {
    ...
    nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
            &event, entry->policyFlags);

    if (delay < 0) {
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
    } else if (!delay) {
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
    } else {
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
        entry->interceptKeyWakeupTime = now() + delay;
    }
    entry->release();
}

doInterceptKeyBeforeDispatchingLockedInterruptible調(diào)用Java層的interceptKeyBeforeDispatching做攔截操作,然后根據(jù)返回結(jié)果設(shè)置 key event的interceptKeyResult, 如果沒有攔截,設(shè)置interceptKeyResult為INTERCEPT_KEY_RESULT_CONTINUE, 否則設(shè)置為INTERCEPT_KEY_RESULT_SKIP或TRY_AGAIN.

doInterceptKeyBeforeDispatchingLockedInterruptible只是設(shè)置KeyEvent的interceptKeyResult, 那這個key event何時才被處理呢??

再回到 dispatchOnce

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
    } // 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);
    mLooper->pollOnce(timeoutMillis);
}

當(dāng)runCommandsLockedInterruptible返回為true時, 會設(shè)置nextWakeupTime,進(jìn)而設(shè)置timeoutMillis, 然后looper的pollOnce會立即timeout, 然后會再執(zhí)行一次 dispatchOnce,
此時進(jìn)入dispatchOnceInnerLocked

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    ...
    if (! mPendingEvent) { //此時mPendingEvent就是上面?zhèn)鬟f給JAVA層執(zhí)行攔截操作的event.
       ...
    }
    ...

    switch (mPendingEvent->type) {
    ...
    case EventEntry::TYPE_KEY: {
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
         ...
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }

    //清空mPendingEvent
     if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;

        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

dispatchOnceInnerLocked中處理的mPendingEvent正是傳給JAVA層進(jìn)行攔截操作的Event.,然后將mPendingEvent傳遞給dispatchKeyLocked,

為什么mPendingEvent還是原來的那個KeyEvent呢?因為
postCommandLocked( & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible) 后 return false.
下面就自己看了

3. Fallback Key事件

Scenario, 按鍵盤上的 ESC (退出)鍵, 正常的流程是 Input 系統(tǒng)往 App端發(fā)送 KEYCODE_ESCAPE 事件,通常情況下 App是不會處理這個按鍵的,接著Input又向App發(fā)送 KEYCODE_BACK事件, KEYCODE_BACK就是fallback的key, 那整個過程又是怎樣的呢?

Fallback key

如圖所示, 當(dāng)App端在處理KEYCODE_ESCAPE事件后(不管有沒有consume),都會往SystemServer端發(fā)送finish的消息,如果沒有被處理且有fallback的key,將fallback的key event enqueue, 最后通過startDispatcherCycleLocked來啟動下一次input事件dispatch.

圖中綠色的流程是運行于java端.
InputManagerService通過dispatchUnhandledKey往native獲得Fallback的KeyEvent. Fallback的key定義在 /system/usr/keychars

4. Joystick的方向按鍵

Joystick的方向鍵,并不是KeyEvent, 而是MotionEvent, 具體流程如下

Joystick Direction keys

如圖所示,當(dāng)ViewPostImeInputStage也就是App并不consume該事件后,最后事件會被route到SyntheticInputStage中處理,然后根據(jù) axie的值來轉(zhuǎn)換為KEYCODE_DPAD_LEFT/RIGHT/UP/DOWN事件,接著將這些KeyEvent, 通過enqueueInputEvent,重新加入到looper里等待著執(zhí)行。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • http://www.itdecent.cn/p/2bff4ecd86c9本篇博客主要是過一下Android I...
    wbo4958閱讀 8,152評論 4 20
  • ??JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。 ??事件,就是文檔或瀏覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,696評論 1 11
  • 一.input 系統(tǒng)初始化 安卓系統(tǒng)啟動時,會開啟SystemServer進(jìn)程,SystemServer執(zhí)行mai...
    人海中一只羊閱讀 6,900評論 0 10
  • 事件分為按鍵事件分發(fā),觸摸事件分發(fā),還有軌跡球事件,軌跡球已經(jīng)被淘汰,按鍵事件分發(fā)主要是在TV上,使用遙控器做按鍵...
    博為峰51Code教研組閱讀 1,149評論 0 0
  • ¥開啟¥ 【iAPP實現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,335評論 0 17

友情鏈接更多精彩內(nèi)容