2023年12月6日发(作者:)
androidhome键流程分析
上一篇文章中我们介绍了android系统的截屏事件,由于截屏事件是一种系统全局处理事件,所以事件的处理逻辑不是在App中执行,而是
在PhoneWindowManager中执行。而本文我们现在主要讲解android系统中HOME按键的事件处理,和截屏事件类似,这里的HOME按
键也是系统级别的按键事件监听,所以其处理事件的逻辑也应该和截屏事件处理流程类似,从上一篇文章的分析过冲中我们不难发现,系统
级别的按键处理逻辑其实都是在PhoneWindowManager中,所以HOME按键的处理逻辑也是在PhoneWindowManager的
dispatchUnhandledKey方法中执行,那么我们就从dispatchUnhandleKey方法开始分析HOME按键的处理流程。
好吧我们看一下PhoneWindowManager的dispatchUnhandleKey方法的实现:@Override
public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
...
KeyEvent fallbackEvent = null;
if ((gs() & _FALLBACK) == 0) {
final KeyCharacterMap kcm = CharacterMap();
final int keyCode = Code();
final int metaState = aState();
final boolean initialDown = ion() == _DOWN
&& eatCount() == 0;
// Check for fallback actions specified by the key character map.
final FallbackAction fallbackAction;
if (initialDown) {
fallbackAction = lbackAction(keyCode, metaState);
} else {
fallbackAction = (keyCode);
}
if (fallbackAction != null) {
if (DEBUG_INPUT) {
Slog.d(TAG, "Fallback: keyCode=" + e
+ " metaState=" + tring(ate));
}
final int flags = gs() | _FALLBACK;
fallbackEvent = (
nTime(), ntTime(),
ion(), e,
eatCount(), ate,
iceId(), nCode(),
flags, rce(), null);
if (!interceptFallback(win, fallbackEvent, policyFlags)) {
e();
fallbackEvent = null;
}
if (initialDown) {
(keyCode, fallbackAction);
} else if (ion() == _UP) {
(keyCode);
e();
}
}
}
if (DEBUG_INPUT) {
if (fallbackEvent == null) {
Slog.d(TAG, "No fallback.");
} else {
Slog.d(TAG, "Performing fallback: " + fallbackEvent);
}
}
return fallbackEvent;
}
通过查看源码,我们重点看一下dispatchUnhandledKey方法中调用的interceptFallback方法,关于HOME按键的处理逻辑也是在这个方
法体中的,所以继续看一下interceptFallback方法的实现: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;
}
通过分析源码我们知道关于HOME按键的处理逻辑主要是在interceptKeyBeforeDispatching方法的实现的,既然这样,我们看一下
interceptKeyBeforeDispatching方法的实现:
@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 == E_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 && ing()) {
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) {
Callbacks(mHomeDoubleTapTimeoutRunnable); // just in case
mHomeDoubleTapPending = true;
layed(mHomeDoubleTapTimeoutRunnable,
bleTapTimeout());
return -1;
}
handleShortPressOnHome();
return -1;
}
// If a system window has focus, then it doesn't make sense
// right now to interact with applications. // right now to interact with applications.
Params attrs = win != null ? rs() : null;
if (attrs != null) {
final int type = ;
if (type == _KEYGUARD_SCRIM
|| type == _KEYGUARD_DIALOG
|| (eFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
// the "app" is keyguard, so give it the key
return 0;
}
final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_;
for (int i=0; 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; Callbacks(mHomeDoubleTapTimeoutRunnable); handleDoubleTapOnHome(); } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { preloadRecentApps(); } } else if ((gs() & _LONG_PRESS) != 0) { if (!keyguardOn) { handleLongPressOnHome(iceId()); } } return -1; } // Let the application handle the key. return 0; } 这里我们主要看一下对android系统HOME按键的处理逻辑,通过分析源码我们知道HOME按键进入launcher界面的主要逻辑是在 handleShortPressOnHome();方法中执行的,所以我们继续看一下handleShortPressOnHome方法的实现。 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 && ming()) { eam(false /*immediate*/); return; } // Go home! launchHomeFromHotKey(); } 可以看到在handleShortPressOnHome方法中调用了launchHomeFromHotKey方法,该方法的注释用于go home,所以继续看一下该 方法的实现:void launchHomeFromHotKey() { launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/); } 可以看到在launchHomeFromHotKey方法中我们又调用了launchHomeFromHotkey的重构方法,这样我们看一下这个重构方法的实 现。 void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) { if (respectKeyguard) { if (isKeyguardShowingAndNotOccluded()) { // don't launch home if keyguard showing return; } if (!mHideLockScreen && tRestricted()) { // when in keyguard restricted mode, must first verify unlock // before launching home Unlock(new OnKeyguardExitResult() { @Override public void onKeyguardExitResult(boolean success) { if (success) { try { ault().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 { ault().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); } } 可以发现在方法中我们首先调用了ault().stopAppSwitches();该方法主要用于暂停后台的打开Activity的 操作,避免打扰用户的操作。比如这时候我们在后台打开一个新的App,那么这时候由于要回到home页面,所以需要先延时打开。方法执 行这个方法之后然后执行了sendCloseSystemWindows方法,该方法主要实现了对当前系统App页面的关闭操作,下面我们先看一下 ault().stopAppSwitches();方法的实现,这里的ault我们在前面已经多 次说过了其是一个Binder对象,是应用进程Binder客户端用于与ActivityManagerService之间通讯,所以这里最终调用的是 ActivityManagerService的stopAppsSwitches方法,这样我们就继续看一下ActivityManagerService的stopAppsSwitches方法的实 现。@Override public void stopAppSwitches() { if (checkCallingPermission(_APP_SWITCHES) != SION_GRANTED) { throw new SecurityException("Requires permission " + _APP_SWITCHES); } synchronized(this) { mAppSwitchesAllowedTime = Millis() + APP_SWITCH_DELAY_TIME; mDidAppSwitch = false; Messages(DO_PENDING_ACTIVITY_LAUNCHES_MSG); Message msg = Message(DO_PENDING_ACTIVITY_LAUNCHES_MSG); ssageDelayed(msg, APP_SWITCH_DELAY_TIME); } } 可以发现这里主要是发送了一个异步消息,并且为DO_PENDING_ACTIVITY_LAUNCHES_MSG,即跳转Activity,然后我们 继续我们看一下mHandler的handleMessage方法当为DO_PENDING_ACTIVITY_LAUNCHES_MSG时的操作。而且我们可以 发现这里的异步消息是一个延时的异步消息,延时的时间为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方法的具体实现: case DO_PENDING_ACTIVITY_LAUNCHES_MSG: { synchronized () { ingActivityLaunchesLocked(true); } } break; 可以发现这里直接调用了ingActivityLaunchesLocked方法,好吧,继续看一下 doPendingActivityLaunchesLocked方法的实现。 final void doPendingActivityLaunchesLocked(boolean doResume) { while (!y()) { PendingActivityLaunch pal = (0); startActivityUncheckedLocked(pal.r, Record, null, null, lags, doResume && y(), null, null); } } 可以发现这里就是调用了startActivity的操作了,这里就是开始启动Activity了,所以当我们按下HOME按键的时候,后台的startActivity 都会延时5秒钟执行… 然后回到我们的launchHomeFromHotKey方法,看一下launchHomeFromHotKey方法的实现。 void sendCloseSystemWindows(String reason) { oseSystemWindows(mContext, reason); } 可以发现这里调用了PhoneWindow的静态方法sendCloseSystemWindow,继续看一下该方法的实现逻辑。public static void sendCloseSystemWindows(Context context, String reason) { if (emReady()) { try { ault().closeSystemDialogs(reason); } catch (RemoteException e) { } } } 看到这里,很明显了又是调用了Binder的进程间通讯,最终ActivityManagerService的closeSystemDialogs方法会被执行,所以我们继 续看一下ActivityManagerService的closeSystemDialogs方法的实现。 @Override public void closeSystemDialogs(String reason) { enforceNotIsolatedCaller("closeSystemDialogs"); final int pid = lingPid(); final int uid = lingUid(); final long origId = allingIdentity(); 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 >= _APPLICATION_UID) { ProcessRecord proc; synchronized (mPidsSelfLocked) { proc = (pid); } if (Adj > TIBLE_APP_ADJ) { Slog.w(TAG, "Ignoring closeSystemDialogs " + reason + " from background process " + proc); return; } } closeSystemDialogsLocked(reason); } } finally { eCallingIdentity(origId); } } 可以发现其实在方法体中将关闭窗口的逻辑下发到了closeSystemDialogsLocked中,所以我们继续看一下closeSystemDialogsLocked 方法的实现。 void closeSystemDialogsLocked(String reason) { Intent intent = new Intent(_CLOSE_SYSTEM_DIALOGS); gs(_RECEIVER_REGISTERED_ONLY | _RECEIVER_FOREGROUND); if (reason != null) { ra("reason", reason); } ystemDialogs(reason); ystemDialogsLocked(); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, _NONE, null, false, false, -1, _UID, _ALL); } 可以发现在方法体中首先调用了ystemDialogs方法,该方法就是关闭当前页面中存在的系统窗口,比如输入 法,壁纸等:@Override public void closeSystemDialogs(String reason) { synchronized(mWindowMap) { final int numDisplays = (); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final WindowList windows = t(displayNdx).getWindowList(); final int numWindows = (); for (int winNdx = 0; winNdx < numWindows; ++winNdx) { final WindowState w = (winNdx); if (rface) { try { ystemDialogs(reason); } catch (RemoteException e) { } } } } } } 讲过这样一层操作之后,我们就关闭了当前中存在的系统窗口。然后还是回到我们的launchHomeFromHotKey方法,我们发现在方法体 的最后我们调用了startDockOrHome方法,这个方法就是实际的跳转HOME页面的方法了,我们可以具体看一下该方法的实现。 void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) { if (awakenFromDreams) { awakenDreams(); } Intent dock = createHomeDockIntent(); if (dock != null) { try { if (fromHomeKey) { ra(_FROM_HOME_KEY, fromHomeKey); } startActivityAsUser(dock, T); return; } catch (ActivityNotFoundException e) { } } Intent intent; if (fromHomeKey) { intent = new Intent(mHomeIntent); ra(_FROM_HOME_KEY, fromHomeKey); } else { intent = mHomeIntent; } startActivityAsUser(intent, T); } 可以发现我们在方法体中调用了createHomeDockIntent,这个方法的作用就是创建到达HOME页面的Intent对象,然后我们调用了 startActivityAsUser方法,这样经过一系列的调用之后就调起了home页面的Activity,所以这时候系统就返回到了HOME页面。 总结: home键的处理事件是在PhoneWindowManager类中处理,监听到操作后首先关闭系统window窗体,然后调用系统launcher桌面程序,这 样就会到了手机桌面了。


发布评论