Android Input 4

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

先來一張overview

Keyevent.png

1. interceptKeyBeforeQueueing

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

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    ...
    uint32_t policyFlags = args->policyFlags; //只關注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消費掉了,直接返回,結束Input事件分發流程
            }
        }
       //將處理后的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被調用的前提是shouldSendKeyToInputFilterLocked,也就是說Java端的IMS通過nativeSetInputFilterEnabled設置了InputFilter, 即在Java層做Input filter動作,所以如果Java層filterInputEvent即消費了Input事件,此時Input分發事件就結束掉.

在這里不深究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, 然后將攔截結果傳遞給 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攔截結果為1的話,在JAVA層對應的是ACTION_PASS_TO_USER, 意思是攔截的結果是沒有設置該bit, 即表明JAVA層IMS消費了該事件。但是特別注意,這里并沒有結束Input事件傳遞。 而是將policy保存到input event里,繼續分發流程。
那何時處理呢?

InputDispatcher在有event事件發生后,會觸發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消費了,此時在這里會設置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;
        }
        //即使攔截了,也要調用dispatchKeyLocked. 多此一舉么???奇怪
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }
   }
    if (done) { 
        if (dropReason != DROP_REASON_NOT_DROPPED) {
           //進入清理工作,最終調用synthesizeCancelationEventsForAllConnectionsLocked向所有的
           //input client端發送cancel事件,即一個ACTION_UP事件, keycode還是被攔截的keycode
            dropInboundEventLocked(mPendingEvent, dropReason); 
        }
        mLastDropReason = dropReason;
      ...
    }
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ...
    //默認是UNKNOWN
    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { //由于被攔截了,這里不會再調用了
            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);
       //原來在這里結果input事件分發啊
        return true;
    }
    ...
}

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

好了,interceptKeyBeforeQueueing 在這里就結束了

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第一次進來interceptKeyResult默認為INTERCEPT_KEY_RESULT_UNKNOWN, 而且interceptKeyBeforeDispatching并沒有攔截,所以entry->policyFlags&POLICY_FLAG_PASS_TO_USER=true 這沒什么好說的, 如上代碼所示,dispatchKeyLocked函數在post一個command后直接返回了,并沒有繼續往下發送輸入事件了。

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

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

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

InputDispatcher::dispatchOnce函數會先檢查 mCommandQueue中隊列是否為空,如果不為空會優先執行mCommandQueue里的函數,所以此時就開始執行
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調用Java層的interceptKeyBeforeDispatching做攔截操作,然后根據返回結果設置 key event的interceptKeyResult, 如果沒有攔截,設置interceptKeyResult為INTERCEPT_KEY_RESULT_CONTINUE, 否則設置為INTERCEPT_KEY_RESULT_SKIP或TRY_AGAIN.

doInterceptKeyBeforeDispatchingLockedInterruptible只是設置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);
}

當runCommandsLockedInterruptible返回為true時, 會設置nextWakeupTime,進而設置timeoutMillis, 然后looper的pollOnce會立即timeout, 然后會再執行一次 dispatchOnce,
此時進入dispatchOnceInnerLocked

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    ...
    if (! mPendingEvent) { //此時mPendingEvent就是上面傳遞給JAVA層執行攔截操作的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層進行攔截操作的Event.,然后將mPendingEvent傳遞給dispatchKeyLocked,

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

3. Fallback Key事件

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

Fallback key

如圖所示, 當App端在處理KEYCODE_ESCAPE事件后(不管有沒有consume),都會往SystemServer端發送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

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

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

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