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阅读 175,490评论 5 419
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 74,060评论 2 335
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 124,407评论 0 291
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 47,741评论 0 248
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 56,543评论 3 329
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 43,040评论 1 246
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 34,107评论 3 358
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 32,646评论 0 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 36,694评论 1 271
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 32,398评论 2 279
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 33,987评论 1 288
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 30,097评论 3 285
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 35,298评论 3 282
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 27,278评论 0 14
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 28,413评论 1 232
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 38,397评论 2 309
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 38,099评论 2 314

推荐阅读更多精彩内容

  • https://www.jianshu.com/p/2bff4ecd86c9本篇博客主要是过一下Android I...
    wbo4958阅读 7,611评论 4 17
  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,440评论 1 11
  • 一.input 系统初始化 安卓系统启动时,会开启SystemServer进程,SystemServer执行mai...
    人海中一只羊阅读 6,350评论 0 10
  • 事件分为按键事件分发,触摸事件分发,还有轨迹球事件,轨迹球已经被淘汰,按键事件分发主要是在TV上,使用遥控器做按键...
    博为峰51Code教研组阅读 957评论 0 0
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,256评论 0 17