Android輸入事件從讀取到分發五:事件分發前的攔截過程

來源:互聯網
上載者:User

標籤:har   article   需要   sso   vol   完全   rac   regexp   service   

在前面的文章: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則是不做處理了。所有沒有對應這種狀態的處理代碼。

Android輸入事件從讀取到分發五:事件分發前的攔截過程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.