首页 > 代码库 > Android输入事件从读取到分发五:事件分发前的拦截过程

Android输入事件从读取到分发五:事件分发前的拦截过程

在前面的文章:Android输入事件从读取到分发三:InputDispatcherThread线程分发事件的过程 一文中已经提过事件在分发前要做拦截的事情,只不过当时没有展开来分析,因此这篇文章的主要目的就是分析事件在分发前的拦截过程。(注:Android源码版本为6.0)
在Android输入事件从读取到分发三:InputDispatcherThread线程分发事件的过程 一文中我们分析到InputDispatcher类的notifyKey方法中,第一次尝试拦截事件,可以在看看这个方法:

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
...
    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);
...
}

这里是事件进入队列前的拦截,这里将其称为第一次拦截吧。
除此之外,在事件分发之前还要做一次拦截,也就是事件进入到InputDispatcherThread线程后,在发送事件之前,做一次拦截,调用流程如下:
dispatchOnce
->dispatchOnceInnerLocked
->dispatchKeyLocked
->doInterceptKeyBeforeDispatchingLockedInterruptible
->mPolicy->interceptKeyBeforeDispatching
这个过程这里将其称为二次拦截吧。

有了上面知识的铺垫,下面,我们逐一分析两次拦截过程。

第一次事件拦截

首先看下时序图:
技术分享
接下来,跟着时序图,我们分析下事件拦截的源码:
当我们在InputDispatcher::notifyKey调用mPolicy->interceptKeyBeforeQueueing方法后,就进入到NativeInputManager::interceptKeyBeforeQueueing方法了:

void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
        uint32_t& policyFlags) {
    // Policy:
    // - Ignore untrusted events and pass them along.
    // - Ask the window manager what to do with normal events and trusted injected events.
    // - For normal events wake and brighten the screen if currently off or dim.
    bool interactive = mInteractive.load();
    if (interactive) {
        policyFlags |= POLICY_FLAG_INTERACTIVE;
    }
    if ((policyFlags & POLICY_FLAG_TRUSTED)) {
        nsecs_t when = keyEvent->getEventTime();
        JNIEnv* env = jniEnv();
        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
        jint wmActions;
        if (keyEventObj) {
            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;
        }

        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
    } else {
        if (interactive) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;
        }
    }
}

这个函数首先根据传下来的KeyEvent类型的参数构造一个keyEventObj,构造的过程是调用android_view_KeyEvent_fromNative方法实现的:

jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {
    jobject eventObj = env->CallStaticObjectMethod(gKeyEventClassInfo.clazz,
            gKeyEventClassInfo.obtain,
            nanoseconds_to_milliseconds(event->getDownTime()),
            nanoseconds_to_milliseconds(event->getEventTime()),
            event->getAction(),
            event->getKeyCode(),
            event->getRepeatCount(),
            event->getMetaState(),
            event->getDeviceId(),
            event->getScanCode(),
            event->getFlags(),
            event->getSource(),
            NULL);
    if (env->ExceptionCheck()) {
        ALOGE("An exception occurred while obtaining a key event.");
        LOGE_EX(env);
        env->ExceptionClear();
        return NULL;
    }
    return eventObj;
}

这个方法使用了jni来调用java层的一个静态方法obtain,使用这个方法构造了一个eventObj 并返回。这里不是我们关注的,暂时这样吧,回NativeInputManager::interceptKeyBeforeQueueing方法中,构造好keyEventObj对象后,又使用jni调用了java层的返回值为int的实例方法,这个实例由mServiceObj决定,它其实就是InputManagerService的实例。大家稍微追踪一下就会明白,这里就不啰嗦了。
然后就进入到一系列的interceptKeyBeforeQueueing方法的调用了:

    // Native callback.
    private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
    }

mWindowManagerCallbacks的实现类是InputMonitor,它的interceptKeyBeforeQueueing方法如下:

    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
    }

这个函数中的mPolicy定义如下:

final WindowManagerPolicy mPolicy = new PhoneWindowManager();

因此,接下来进入到了PhoneWindowManager的interceptKeyBeforeQueueing方法了。


    /** {@inheritDoc} */
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        if (!mSystemBooted) {
            // If we have not yet booted, don‘t let key events do anything.
            return 0;
        }
        ...
        final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
        final boolean canceled = event.isCanceled();
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {

             if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
                    if (down) {
                        if (interactive && !mScreenshotChordVolumeDownKeyTriggered
                                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                            mScreenshotChordVolumeDownKeyTriggered = true;
                            mScreenshotChordVolumeDownKeyTime = event.getDownTime();
                            mScreenshotChordVolumeDownKeyConsumed = false;
                            cancelPendingPowerKeyAction();
                            interceptScreenshotChord();
                        }
                    } else {
                        mScreenshotChordVolumeDownKeyTriggered = false;
                        cancelPendingScreenshotChordAction();
                    }
                } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
                    if (down) {
                        if (interactive && !mScreenshotChordVolumeUpKeyTriggered
                                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                            mScreenshotChordVolumeUpKeyTriggered = true;
                            cancelPendingPowerKeyAction();
                            cancelPendingScreenshotChordAction();
                        }
                    } else {
                        mScreenshotChordVolumeUpKeyTriggered = false;
                        cancelPendingScreenshotChordAction();
                    }
                }       return result;
        ...
    }

这个方法很长,这里只贴出一小部分。这个方法的返回值很关键,返回0则意味着事件被拦截,返回1则意味着事件允许被发送到应用程序中。我们看下最终返回值的处理。再次回到NativeInputManager::interceptKeyBeforeQueueing方法,返回值保存在wmActions变量中,然后调用handleInterceptActions方法处理返回值。其定义如下:

void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
        uint32_t& policyFlags) {
    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
    }
}

WM_ACTION_PASS_TO_USER定义如下:

enum {
    WM_ACTION_PASS_TO_USER = 1,
};

这里位运算,但结果就是如果返回值为1,二者位与后为1,则给policyFlags 添加POLICY_FLAG_PASS_TO_USER标志,意味着可以把该事件发送到应用程序,否则,从注释中可以知道不会发送事件给用户。
第一次事件拦截具体会拦截什么事件,大家可以自己去看,你可以直接去看PhoneWindowManager的interceptKeyBeforeQueueing方法,看看这个方法中,那些事件处理后返回值为0。如果返回值为0则说明这个事件被拦截了。
接下来我们看下第二次拦截

第二次事件拦截

首先看下时序图:
技术分享
从图中可以看到其调用过程和第一阶段完全相同,因此这里就不再追踪源码了。感兴趣可以看看PhoneWindowManager的interceptKeyBeforeDispatching方法,这个方法对事件做了二次拦截,这个方法的返回值为-1则说明事件被拦截,返回值为0则说明事件被放行。
我们看下返回值的处理过程:

            jlong delayMillis = env->CallLongMethod(mServiceObj,
                    gServiceClassInfo.interceptKeyBeforeDispatching,
                    inputWindowHandleObj, keyEventObj, policyFlags);
            bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
            android_view_KeyEvent_recycle(env, keyEventObj);
            env->DeleteLocalRef(keyEventObj);
            if (!error) {
                if (delayMillis < 0) {
                    result = -1;
                } else if (delayMillis > 0) {
                    result = milliseconds_to_nanoseconds(delayMillis);
                }
            }

这里可以看到返回值存放在delayMillis 变量中,接着判断:
如果返回值为负数,那么result=-1,入则,返回值等于0则不处理,因为result默认初始化值为0,如果返回值大于0则把milliseconds_to_nanoseconds的返回值给result。milliseconds_to_nanoseconds方的定义如下:

static CONSTEXPR inline nsecs_t milliseconds_to_nanoseconds(nsecs_t secs)
{
    return secs*1000000;
}

可以看到就是给返回值*1000000.
result最终会返回到InputDispatcher中:

void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
        CommandEntry* commandEntry) {
    KeyEntry* entry = commandEntry->keyEntry;

    KeyEvent event;
    initializeKeyEvent(&event, entry);

    mLock.unlock();

    nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
            &event, entry->policyFlags);

    mLock.lock();

    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();
}

这个方法中,会根据返回值给entry->interceptKeyResult变量赋值。从名字上我们可以猜测,返回值小于0则拦截事件,等于0则放行事件,大于0是待会再检测是否需要拦截?
这三种类型对应的处理方式在InputDispatcher::dispatchKeyLocked方法中:

    // Handle case where the policy asked us to try again later last time.
    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
        if (currentTime < entry->interceptKeyWakeupTime) {
            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
                *nextWakeupTime = entry->interceptKeyWakeupTime;
            }
            return false; // wait until next wakeup
        }
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
        entry->interceptKeyWakeupTime = 0;
    }

这里展示了对INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER的处理,会判断拦截时间和当前时间,如果当前时间小于拦截时间,则下次循环再处理。所以我们理解的是对的。

 if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
        if (*dropReason == DROP_REASON_NOT_DROPPED) {
            *dropReason = DROP_REASON_POLICY;
        }
    }

这里展示了INTERCEPT_KEY_RESULT_SKIP类型的处理,如果dropReason 的状态为不没有丢弃事件的话,那就把它的状态改为因为策略丢弃。也就是事件被拦截了。
INTERCEPT_KEY_RESULT_CONTINUE则是不做处理了。所有没有对应这种状态的处理代码。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android输入事件从读取到分发五:事件分发前的拦截过程