上一篇文章中我們介紹了android系統(tǒng)的截屏事件,由于截屏事件是一種系統(tǒng)全局處理事件,所以事件的處理邏輯不是在App中執(zhí)行,而是在PhoneWindowManager中執(zhí)行。而本文我們現(xiàn)在主要講解android系統(tǒng)中HOME按鍵的事件處理,和截屏事件類似,這里的HOME按鍵也是系統(tǒng)級別的按鍵事件監(jiān)聽,所以其處理事件的邏輯也應(yīng)該和截屏事件處理流程類似,從上一篇文章的分析過沖中我們不難發(fā)現(xiàn),系統(tǒng)級別的按鍵處理邏輯其實都是在PhoneWindowManager中,所以HOME按鍵的處理邏輯也是在PhoneWindowManager的dispatchUnhandledKey方法中執(zhí)行,那么我們就從dispatchUnhandleKey方法開始分析HOME按鍵的處理流程。
好吧我們看一下PhoneWindowManager的dispatchUnhandleKey方法的實現(xiàn):
@Override
public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
...
KeyEvent fallbackEvent = null;
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
final KeyCharacterMap kcm = event.getKeyCharacterMap();
final int keyCode = event.getKeyCode();
final int metaState = event.getMetaState();
final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0;
// Check for fallback actions specified by the key character map.
final FallbackAction fallbackAction;
if (initialDown) {
fallbackAction = kcm.getFallbackAction(keyCode, metaState);
} else {
fallbackAction = mFallbackActions.get(keyCode);
}
if (fallbackAction != null) {
if (DEBUG_INPUT) {
Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
+ " metaState=" + Integer.toHexString(fallbackAction.metaState));
}
final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
fallbackEvent = KeyEvent.obtain(
event.getDownTime(), event.getEventTime(),
event.getAction(), fallbackAction.keyCode,
event.getRepeatCount(), fallbackAction.metaState,
event.getDeviceId(), event.getScanCode(),
flags, event.getSource(), null);
if (!interceptFallback(win, fallbackEvent, policyFlags)) {
fallbackEvent.recycle();
fallbackEvent = null;
}
if (initialDown) {
mFallbackActions.put(keyCode, fallbackAction);
} else if (event.getAction() == KeyEvent.ACTION_UP) {
mFallbackActions.remove(keyCode);
fallbackAction.recycle();
}
}
}
if (DEBUG_INPUT) {
if (fallbackEvent == null) {
Slog.d(TAG, "No fallback.");
} else {
Slog.d(TAG, "Performing fallback: " + fallbackEvent);
}
}
return fallbackEvent;
}
通過查看源碼,我們重點看一下dispatchUnhandledKey方法中調(diào)用的interceptFallback方法,關(guān)于HOME按鍵的處理邏輯也是在這個方法體中的,所以繼續(xù)看一下interceptFallback方法的實現(xiàn):
private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
if ((actions & ACTION_PASS_TO_USER) != 0) {
long delayMillis = interceptKeyBeforeDispatching(
win, fallbackEvent, policyFlags);
if (delayMillis == 0) {
return true;
}
}
return false;
}
通過分析源碼我們知道關(guān)于HOME按鍵的處理邏輯主要是在interceptKeyBeforeDispatching方法的實現(xiàn)的,既然這樣,我們看一下interceptKeyBeforeDispatching方法的實現(xiàn):
@Override
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
...
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
// it handle it, because that gives us the correct 5 second
// timeout.
if (keyCode == KeyEvent.KEYCODE_HOME) {
// If we have released the home key, and didn't do anything else
// while it was pressed, then it is time to go home!
if (!down) {
cancelPreloadRecentApps();
mHomePressed = false;
if (mHomeConsumed) {
mHomeConsumed = false;
return -1;
}
if (canceled) {
Log.i(TAG, "Ignoring HOME; event canceled.");
return -1;
}
// If an incoming call is ringing, HOME is totally disabled.
// (The user is already on the InCallUI at this point,
// and his ONLY options are to answer or reject the call.)
TelecomManager telecomManager = getTelecommService();
if (telecomManager != null && telecomManager.isRinging()) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
return -1;
}
// Delay handling home if a double-tap is possible.
if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {
mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
mHomeDoubleTapPending = true;
mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
ViewConfiguration.getDoubleTapTimeout());
return -1;
}
handleShortPressOnHome();
return -1;
}
// If a system window has focus, then it doesn't make sense
// right now to interact with applications.
WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
if (attrs != null) {
final int type = attrs.type;
if (type == WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM
|| type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
|| (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
// the "app" is keyguard, so give it the key
return 0;
}
final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
for (int i=0; i<typeCount; i++) {
if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
// don't do anything, but also don't pass it to the app
return -1;
}
}
}
// Remember that home is pressed and handle special actions.
if (repeatCount == 0) {
mHomePressed = true;
if (mHomeDoubleTapPending) {
mHomeDoubleTapPending = false;
mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
handleDoubleTapOnHome();
} else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
|| mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
preloadRecentApps();
}
} else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
if (!keyguardOn) {
handleLongPressOnHome(event.getDeviceId());
}
}
return -1;
}
// Let the application handle the key.
return 0;
}
這里我們主要看一下對android系統(tǒng)HOME按鍵的處理邏輯,通過分析源碼我們知道HOME按鍵進入launcher界面的主要邏輯是在handleShortPressOnHome();方法中執(zhí)行的,所以我們繼續(xù)看一下handleShortPressOnHome方法的實現(xiàn)。
private void handleShortPressOnHome() {
// Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
getHdmiControl().turnOnTv();
// If there's a dream running then use home to escape the dream
// but don't actually go home.
if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
mDreamManagerInternal.stopDream(false /*immediate*/);
return;
}
// Go home!
launchHomeFromHotKey();
}
可以看到在handleShortPressOnHome方法中調(diào)用了launchHomeFromHotKey方法,該方法的注釋用于go home,所以繼續(xù)看一下該方法的實現(xiàn):
void launchHomeFromHotKey() {
launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/);
}
可以看到在launchHomeFromHotKey方法中我們又調(diào)用了launchHomeFromHotkey的重構(gòu)方法,這樣我們看一下這個重構(gòu)方法的實現(xiàn)。
void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
if (respectKeyguard) {
if (isKeyguardShowingAndNotOccluded()) {
// don't launch home if keyguard showing
return;
}
if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
// when in keyguard restricted mode, must first verify unlock
// before launching home
mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
@Override
public void onKeyguardExitResult(boolean success) {
if (success) {
try {
ActivityManagerNative.getDefault().stopAppSwitches();
} catch (RemoteException e) {
}
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
}
});
return;
}
}
// no keyguard stuff to worry about, just launch home!
try {
ActivityManagerNative.getDefault().stopAppSwitches();
} catch (RemoteException e) {
}
if (mRecentsVisible) {
// Hide Recents and notify it to launch Home
if (awakenFromDreams) {
awakenDreams();
}
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
hideRecentApps(false, true);
} else {
// Otherwise, just launch Home
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
}
可以發(fā)現(xiàn)在方法中我們首先調(diào)用了ActivityManagerNative.getDefault().stopAppSwitches();該方法主要用于暫停后臺的打開Activity的操作,避免打擾用戶的操作。比如這時候我們在后臺打開一個新的App,那么這時候由于要回到home頁面,所以需要先延時打開。方法執(zhí)行這個方法之后然后執(zhí)行了sendCloseSystemWindows方法,該方法主要實現(xiàn)了對當(dāng)前系統(tǒng)App頁面的關(guān)閉操作,下面我們先看一下ActivityManagerNative.getDefault().stopAppSwitches();方法的實現(xiàn),這里的ActivityManagerNative.getDefault我們在前面已經(jīng)多次說過了其是一個Binder對象,是應(yīng)用進程Binder客戶端用于與ActivityManagerService之間通訊,所以這里最終調(diào)用的是ActivityManagerService的stopAppsSwitches方法,這樣我們就繼續(xù)看一下ActivityManagerService的stopAppsSwitches方法的實現(xiàn)。
@Override
public void stopAppSwitches() {
if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.STOP_APP_SWITCHES);
}
synchronized(this) {
mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
+ APP_SWITCH_DELAY_TIME;
mDidAppSwitch = false;
mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
}
}
可以發(fā)現(xiàn)這里主要是發(fā)送了一個異步消息,并且msg.what為DO_PENDING_ACTIVITY_LAUNCHES_MSG,即跳轉(zhuǎn)Activity,然后我們繼續(xù)我們看一下mHandler的handleMessage方法當(dāng)msg.what為DO_PENDING_ACTIVITY_LAUNCHES_MSG時的操作。而且我們可以發(fā)現(xiàn)這里的異步消息是一個延時的異步消息,延時的時間為APP_SWITCH_DELAY_TIME,我們可以看一下該變量的定義:
// Amount of time after a call to stopAppSwitches() during which we will
// prevent further untrusted switches from happening.
static final long APP_SWITCH_DELAY_TIME = 5*1000;
然后我們可以看一下mHander的handleMessage方法的具體實現(xiàn):
case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
synchronized (ActivityManagerService.this) {
mStackSupervisor.doPendingActivityLaunchesLocked(true);
}
} break;
可以發(fā)現(xiàn)這里直接調(diào)用了mStackSupervisor.doPendingActivityLaunchesLocked方法,好吧,繼續(xù)看一下doPendingActivityLaunchesLocked方法的實現(xiàn)。
final void doPendingActivityLaunchesLocked(boolean doResume) {
while (!mPendingActivityLaunches.isEmpty()) {
PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
doResume && mPendingActivityLaunches.isEmpty(), null, null);
}
}
可以發(fā)現(xiàn)這里就是調(diào)用了startActivity的操作了,這里就是開始啟動Activity了,所以當(dāng)我們按下HOME按鍵的時候,后臺的startActivity都會延時5秒鐘執(zhí)行...
然后回到我們的launchHomeFromHotKey方法,看一下launchHomeFromHotKey方法的實現(xiàn)。
void sendCloseSystemWindows(String reason) {
PhoneWindow.sendCloseSystemWindows(mContext, reason);
}
可以發(fā)現(xiàn)這里調(diào)用了PhoneWindow的靜態(tài)方法sendCloseSystemWindow,繼續(xù)看一下該方法的實現(xiàn)邏輯。
public static void sendCloseSystemWindows(Context context, String reason) {
if (ActivityManagerNative.isSystemReady()) {
try {
ActivityManagerNative.getDefault().closeSystemDialogs(reason);
} catch (RemoteException e) {
}
}
}
看到這里,很明顯了又是調(diào)用了Binder的進程間通訊,最終ActivityManagerService的closeSystemDialogs方法會被執(zhí)行,所以我們繼續(xù)看一下ActivityManagerService的closeSystemDialogs方法的實現(xiàn)。
@Override
public void closeSystemDialogs(String reason) {
enforceNotIsolatedCaller("closeSystemDialogs");
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
// Only allow this from foreground processes, so that background
// applications can't abuse it to prevent system UI from being shown.
if (uid >= Process.FIRST_APPLICATION_UID) {
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(pid);
}
if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
Slog.w(TAG, "Ignoring closeSystemDialogs " + reason
+ " from background process " + proc);
return;
}
}
closeSystemDialogsLocked(reason);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
可以發(fā)現(xiàn)其實在方法體中將關(guān)閉窗口的邏輯下發(fā)到了closeSystemDialogsLocked中,所以我們繼續(xù)看一下closeSystemDialogsLocked方法的實現(xiàn)。
void closeSystemDialogsLocked(String reason) {
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
if (reason != null) {
intent.putExtra("reason", reason);
}
mWindowManager.closeSystemDialogs(reason);
mStackSupervisor.closeSystemDialogsLocked();
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
AppOpsManager.OP_NONE, null, false, false,
-1, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
可以發(fā)現(xiàn)在方法體中首先調(diào)用了mWindowManager.closeSystemDialogs方法,該方法就是關(guān)閉當(dāng)前頁面中存在的系統(tǒng)窗口,比如輸入法,壁紙等:
@Override
public void closeSystemDialogs(String reason) {
synchronized(mWindowMap) {
final int numDisplays = mDisplayContents.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
final int numWindows = windows.size();
for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
final WindowState w = windows.get(winNdx);
if (w.mHasSurface) {
try {
w.mClient.closeSystemDialogs(reason);
} catch (RemoteException e) {
}
}
}
}
}
}
講過這樣一層操作之后,我們就關(guān)閉了當(dāng)前中存在的系統(tǒng)窗口。然后還是回到我們的launchHomeFromHotKey方法,我們發(fā)現(xiàn)在方法體的最后我們調(diào)用了startDockOrHome方法,這個方法就是實際的跳轉(zhuǎn)HOME頁面的方法了,我們可以具體看一下該方法的實現(xiàn)。
void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {
if (awakenFromDreams) {
awakenDreams();
}
Intent dock = createHomeDockIntent();
if (dock != null) {
try {
if (fromHomeKey) {
dock.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
}
startActivityAsUser(dock, UserHandle.CURRENT);
return;
} catch (ActivityNotFoundException e) {
}
}
Intent intent;
if (fromHomeKey) {
intent = new Intent(mHomeIntent);
intent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
} else {
intent = mHomeIntent;
}
startActivityAsUser(intent, UserHandle.CURRENT);
}
可以發(fā)現(xiàn)我們在方法體中調(diào)用了createHomeDockIntent,這個方法的作用就是創(chuàng)建到達(dá)HOME頁面的Intent對象,然后我們調(diào)用了startActivityAsUser方法,這樣經(jīng)過一系列的調(diào)用之后就調(diào)起了home頁面的Activity,所以這時候系統(tǒng)就返回到了HOME頁面。
總結(jié):
home鍵的處理事件是在PhoneWindowManager類中處理,監(jiān)聽到操作后首先關(guān)閉系統(tǒng)window窗體,然后調(diào)用系統(tǒng)launcher桌面程序,這樣就會到了手機桌面了。