編輯:關於Android編程
已經提過事件在分發前要做攔截的事情,只不過當時沒有展開來分析,因此這篇文章的主要目的就是分析事件在分發前的攔截過程。(注:Android源碼版本為6.0)
我們分析到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則是不做處理了。所有沒有對應這種狀態的處理代碼。
手機迅雷個人中心關閉朋友圈方法
1、打開手機迅雷點擊切換到我的迅雷個人中心,點擊左上角[登錄],已登錄請跳過2、在手機迅雷個人中心的最下面,倒數第二項[常用設置]點擊進行設置3、繼續在常用
Android基礎
本來不想寫這些基礎中的基礎,但是想想這些內容雖然用不到,但需要做這樣的了解和學習,也是概念性的居多,理解至上。不過還是不多說,就講兩個部分吧。一。系統架構這次的沒有Xm
Android存儲系統如何優化?
Android存儲系統如何優化?答案是我也不知道…那為什麼會想到要寫這篇文章哪?主要是因為有天晚上和以前一個同事討論到Android手機存儲系統的優化問題,
java/android 設計模式學習筆記(3)---工廠方法模式
這篇來介紹一下工廠方法模式(Factory Method Pattern),在實際開發過程中我們都習慣於直接使用 new 關鍵字用來創建一個對象,可是有時候對象的創造需要