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
这个过程这里将其称为二次拦截吧。
有了上面知识的铺垫, 下面, 我们逐一分析两次拦截过程。
第一次事件拦截 首先看下时序图:
Android输入事件从读取到分发五(事件分发前的拦截过程)

文章图片

接下来, 跟着时序图, 我们分析下事件拦截的源码:
当我们在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 } }

【Android输入事件从读取到分发五(事件分发前的拦截过程)】WM_ACTION_PASS_TO_USER定义如下:
enum { WM_ACTION_PASS_TO_USER = 1, };

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

文章图片

从图中可以看到其调用过程和第一阶段完全相同, 因此这里就不再追踪源码了。感兴趣可以看看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则是不做处理了。所有没有对应这种状态的处理代码。

    推荐阅读