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桌面程序,这

样就会到了手机桌面了。