前言
本文我们现在主要分析一下android系统对HOME按键的响应过程,HOME按键事件是属于系统级别的按键事件监听,而在Android系统中,系统级别的按键处理逻辑都在PhoneWindowManager这个类中。
一、interceptKeyBeforeDispatching方法分发按键事件
1、PhoneWindowManager的dispatchUnhandledKey方法是最早收到系统级别的按键事件的。
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public class PhoneWindowManager implements WindowManagerPolicy {@Overridepublic 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) {//进一步调用interceptKeyBeforeDispatchinglong 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<>();@Overridepublic 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();//屏幕设备idfinal long key_consumed = -1;...代码省略...switch(keyCode) {case KeyEvent.KEYCODE_HOME://系统主要是在这里对HOME按键事件进行响应的//从缓存集合中获取HandlerDisplayHomeButtonHandler 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() {@Overridepublic 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 casemHomeDoubleTapPending = 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() {@Overridepublic 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方法先是判断当前是否处于锁屏状态,如果处于锁屏状体则必须先解锁然后才能打开首页。然后会判断最近任务是否可见:
- 如果可见则会调用AMS的stopAppSwitches方法停止应用切换功能,该方法的目的主要是为了暂停后台打开Activity的操作,避免打扰用户的操作.比如这时候我们在后台打开一个新的App,那么由于要回到home页面,所以需要先延时打开。在停止应用切换功能之后还会调用hideRecentApps隐藏最近任务。
- 如果最近任务不可见,则会直接调用startDockOrHome方法打开首页。
二、startDockOrHome方法打开首页
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.Stubimplements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {public ActivityTaskManagerService mActivityTaskManager;@Overridepublic void stopAppSwitches() {mActivityTaskManager.stopAppSwitches();}
}
ActivityManagerService的stopAppSwitches方法什么都没做,只是进一步调用ActivityTaskManagerService的stopAppSwitches方法。
2.2、ActivityTaskManagerService 的stopAppSwitches方法。
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {//停止后台打开应用的操作@Overridepublic void stopAppSwitches() {mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "stopAppSwitches");synchronized (mGlobalLock) {mAppSwitchesState = APP_SWITCH_DISALLOW;//纪录当前时间mLastStopAppSwitchesTime = SystemClock.uptimeMillis();mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG);mH.sendEmptyMessageDelayed(H.RESUME_FG_APP_SWITCH_MSG, RESUME_FG_APP_SWITCH_MS);}}
}