本文我们现在主要分析一下android系统对HOME按键的响应过程,HOME按键事件是属于系统级别的按键事件监听,而在Android系统中,系统级别的按键处理逻辑都在PhoneWindowManager这个类中。
1、PhoneWindowManager的dispatchUnhandledKey方法是最早收到系统级别的按键事件的。
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public KeyEvent dispatchUnhandledKey(IBinder focusedToken, 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(), event.getDisplayId(), null);
//在这里进一步触发interceptFallback方法
if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) {
fallbackEvent.recycle();
fallbackEvent = null;
}
if (initialDown) {
mFallbackActions.put(keyCode, fallbackAction);
} else if (event.getAction() == KeyEvent.ACTION_UP) {
mFallbackActions.remove(keyCode);
fallbackAction.recycle();
}
}
}
return fallbackEvent;
}
private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
int policyFlags) {
int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
if ((actions & ACTION_PASS_TO_USER) != 0) {
//进一步调用interceptKeyBeforeDispatching
long delayMillis = interceptKeyBeforeDispatching(
focusedToken, fallbackEvent, policyFlags);
if (delayMillis == 0) {
return true;
}
}
return false;
}
}
PhoneWindowManager的dispatchUnhandledKey方法会继续触发另一个关键方法interceptFallback,而interceptFallback方法又会进一步调用interceptKeyBeforeDispatching方法。
2、PhoneWindowManager的interceptKeyBeforeDispatching方法如下所示。
public class PhoneWindowManager implements WindowManagerPolicy {
private final SparseArray<DisplayHomeButtonHandler> mDisplayHomeButtonHandlers = new SparseArray<>();
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();//按键编码
final int repeatCount = event.getRepeatCount();
final int metaState = event.getMetaState();
final int flags = event.getFlags();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int displayId = event.getDisplayId();//屏幕设备id
final long key_consumed = -1;
...代码省略...
switch(keyCode) {
case KeyEvent.KEYCODE_HOME://系统主要是在这里对HOME按键事件进行响应的
//从缓存集合中获取Handler
DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId);
if (handler == null) {
//如果没有缓存,则创建Handler并添加到缓存集合中
handler = new DisplayHomeButtonHandler(displayId);
mDisplayHomeButtonHandlers.put(displayId, handler);
}
return handler.handleHomeButton(focusedToken, event);
case KeyEvent.KEYCODE_MENU://菜单按键事件
...代码省略...
break;
case KeyEvent.KEYCODE_APP_SWITCH://应用切换按键事件
...代码省略...
return key_consumed;
...代码省略...
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
return key_consumed;
}
if ((metaState & KeyEvent.META_META_ON) != 0) {
return key_consumed;
}
return 0;
}
interceptKeyBeforeDispatching方法会通过switch的各个case分支,分别来响应各种按键事件,我们这里主要关注HOME按键事件的响应过程,系统首先从mDisplayHomeButtonHandlers集合中获取DisplayHomeButtonHandler类型的缓存对象,如果不存在缓存对象则创建DisplayHomeButtonHandler对象并添加到集合中,反正最终会调用DisplayHomeButtonHandler的handleHomeButton方法。
3、DisplayHomeButtonHandler是PhoneWindowManager的内部类,handleHomeButton方法如下所示。
public class PhoneWindowManager implements WindowManagerPolicy {
private class DisplayHomeButtonHandler {
private final int mDisplayId;
private boolean mHomeDoubleTapPending;
private boolean mHomePressed;
private boolean mHomeConsumed;
private final Runnable mHomeDoubleTapTimeoutRunnable = new Runnable() {
@Override
public void run() {
if (mHomeDoubleTapPending) {
mHomeDoubleTapPending = false;
handleShortPressOnHome(mDisplayId);
}
}
};
DisplayHomeButtonHandler(int displayId) {
mDisplayId = displayId;
}
int handleHomeButton(IBinder focusedToken, KeyEvent event) {
final boolean keyguardOn = keyguardOn();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;//是否是按下事件
final boolean canceled = event.isCanceled();
//一个按键事件大多都有两种Action,按下(ACTION_DOWN)和抬起(ACTION_UP)
//!down意味着只有用户触发按键抬起的时候这里才会做响应回到首页
if (!down) {
if (mDisplayId == DEFAULT_DISPLAY) {
cancelPreloadRecentApps();
}
mHomePressed = false;
if (mHomeConsumed) {
mHomeConsumed = false;
return -1;
}
if (canceled) {
Log.i(TAG, "Ignoring HOME; event canceled.");
return -1;
}
if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {
mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
mHomeDoubleTapPending = true;
mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
ViewConfiguration.getDoubleTapTimeout());
return -1;
}
//为了避免阻塞输入管道,这里通过Handler的post方法切换到了主线程
mHandler.post(() -> handleShortPressOnHome(mDisplayId));
return -1;
}
...代码省略...
return -1;
}
}
private void handleShortPressOnHome(int displayId) {
// Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
final HdmiControl hdmiControl = getHdmiControl();
if (hdmiControl != null) {
hdmiControl.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(displayId);
}
}
handleHomeButton方法首先获取按键的Action类型是否为按下,并且进行了条件判断,只有当按键抬起的时候才会触发返回首页的相关操作,为了避免阻塞输入管道,这里通过Handler的post方法将当前线程切换到了主线程,并进一步调用handleShortPressOnHome方法,该方法又进一步调用launchHomeFromHotKey方法。
4、PhoneWindowManager的launchHomeFromHotKey方法如下所示。
public class PhoneWindowManager implements WindowManagerPolicy {
void launchHomeFromHotKey(int displayId) {
launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
}
/**
* A home key -> launch home action was detected. Take the appropriate action
* given the situation with the keyguard.
*/
void launchHomeFromHotKey(int displayId, final boolean awakenFromDreams,
final boolean respectKeyguard) {
if (respectKeyguard) {
if (isKeyguardShowingAndNotOccluded()) {
return;
}
if (!isKeyguardOccluded() && mKeyguardDelegate.isInputRestricted()) {
//当处于锁屏模式的时候,首先应该解锁然后才能打开首页
mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
@Override
public void onKeyguardExitResult(boolean success) {
if (success) {
startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
}
}
});
return;
}
}
//判断最近任务是否可见
if (mRecentsVisible) {
try {
//如果最近任务视图可见,则会先停止应用切换功能
ActivityManager.getService().stopAppSwitches();
} catch (RemoteException e) {}
if (awakenFromDreams) {
awakenDreams();
}
//隐藏最近任务
hideRecentApps(false, true);
} else {
//否则,打开首页
startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
}
}
}
launchHomeFromHotKey方法先是判断当前是否处于锁屏状态,如果处于锁屏状体则必须先解锁然后才能打开首页。然后会判断最近任务是否可见:
1、PhoneWindowManager的startDockOrHome方法如下所示。
public class PhoneWindowManager implements WindowManagerPolicy {
void startDockOrHome(int displayId, boolean fromHomeKey, boolean awakenFromDreams,
String startReason) {
try {
//先停止应用切换功能
ActivityManager.getService().stopAppSwitches();
} catch (RemoteException e) {}
//关闭系统当前存在的各种弹窗
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
if (awakenFromDreams) {
awakenDreams();
}
if (!mHasFeatureAuto && !isUserSetupComplete()) {
Slog.i(TAG, "Not going home because user setup is in progress.");
return;
}
//创建桌面意图对象
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) {
}
}
if (DEBUG_WAKEUP) {
Log.d(TAG, "startDockOrHome: startReason= " + startReason);
}
//调用ATMS的startHomeOnDisplay方法打开首页
mActivityTaskManagerInternal.startHomeOnDisplay(mCurrentUserId, startReason,
displayId, true /* allowInstrumenting */, fromHomeKey);
}
}
startDockOrHome方法先是调用ActivityManager.getService().stopAppSwitches()暂停掉应用切换,然后调用sendCloseSystemWindows方法关闭系统当前存在的各种弹窗,然后调用createHomeDockIntent方法创建桌面意图对象,如果创建成功则直接打开该意图对象,否则会调用ATMS的startHomeOnDisplay方法打开首页。
2.1、先来看下ActivityManagerService的stopAppSwitches方法。
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
public ActivityTaskManagerService mActivityTaskManager;
@Override
public void stopAppSwitches() {
mActivityTaskManager.stopAppSwitches();
}
}
ActivityManagerService的stopAppSwitches方法什么都没做,只是进一步调用ActivityTaskManagerService的stopAppSwitches方法。
3.2、先来看下ActivityManagerService的stopAppSwitches方法。