前言

本文我们现在主要分析一下android系统对HOME按键的响应过程,HOME按键事件是属于系统级别的按键事件监听,而在Android系统中,系统级别的按键处理逻辑都在PhoneWindowManager这个类中。

一、interceptKeyBeforeDispatching方法分发按键事件

1、PhoneWindowManager的dispatchUnhandledKey方法是最早收到系统级别的按键事件的。

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方法先是判断当前是否处于锁屏状态,如果处于锁屏状体则必须先解锁然后才能打开首页。然后会判断最近任务是否可见:

  • 如果可见则会调用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.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
   public ActivityTaskManagerService mActivityTaskManager;
    @Override
    public void stopAppSwitches() {
        mActivityTaskManager.stopAppSwitches();
    }
}

ActivityManagerService的stopAppSwitches方法什么都没做,只是进一步调用ActivityTaskManagerService的stopAppSwitches方法。
3.2、先来看下ActivityManagerService的stopAppSwitches方法。

08-19 17:02