我們使用的大多數android手機上的Home鍵,返回鍵以及menu鍵都是實體觸控感應按鍵。如果你用Google的Nexus4或Nexus5話,你會發現它們並沒有實體按鍵或觸控感應按鍵,取而代之的是在螢幕的下方加了一個小黑條,在這個黑條上有3個按鈕控制元件,這種設定無疑使得手機的外觀的設計更加簡約。但我遇到身邊用Nexus 4手機的人都吐槽這種設計,原因很簡單:好端端的螢幕,被劃出一塊區域用來顯示3個按鈕(如下圖所示):Back, Home, Recent。並且它一直用在那裡佔用著。


在android原始碼中,那一塊區域被叫做NavigationBar。同時,google在程式碼中也預留了標誌,用來控制它的顯示與隱藏。NavigationBar的顯示與隱藏的控制是放在SystemU中的,具體的路徑是:\frameworks\base\packages\SystemUI。對android4.0以上的手機而言,SystemUi包含兩部分:StatusBar和NavigationBar。在SystemUI的工程下有一個類PhoneStatusBar.java,在該類中可以發現關於控制NavigationBar的相關程式碼:

在start()方法裡可以看到NavigationBar是在那時候被新增進來,但只是新增,決定它顯示還是隱藏是在後面控制的。

<span style="font-size:18px;">@Override
    public void start() {
        mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay();
        updateDisplaySize();

        /// M: Support Smartbook Feature.
        if (SIMHelper.isMediatekSmartBookSupport()) {
            /// M: [ALPS01097705] Query the plug-in state as soon as possible.
            mIsDisplayDevice = SIMHelper.isSmartBookPluggedIn(mContext);
            Log.v(TAG, "start, mIsDisplayDevice=" + mIsDisplayDevice);
        }

        super.start(); // calls createAndAddWindows()

        addNavigationBar();

        // Lastly, call to the icon policy to install/update all the icons.
        mIconPolicy = new PhoneStatusBarPolicy(mContext);

        mHeadsUpObserver.onChange(true); // set up
        if (ENABLE_HEADS_UP) {
            mContext.getContentResolver().registerContentObserver(
                    Settings.Global.getUriFor(SETTING_HEADS_UP), true,
                    mHeadsUpObserver);
        }
    }</span>

其中的addNavigationBar()具體的實現方法如下:

<span style="font-size:18px;"> // For small-screen devices (read: phones) that lack hardware navigation buttons
    private void addNavigationBar() {
        if (DEBUG) Slog.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);
        if (mNavigationBarView == null) return;

        prepareNavigationBarView();

        mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
    }</span>
可以看到Navigationbar實際上windowmanager向window窗口裡新增一個view。在呼叫addNavigationBar()方法之前會回撥start()的父方法super.start()來判斷是否要新增NavigationBar。在super.start()的呼叫父類方法裡會呼叫createAndAddWindows(),該方法內會判斷是否需要新增顯示NavigationBar,然後決定是否要例項化NavigationBarView.
<span style="font-size:18px;">try {
            boolean showNav = mWindowManagerService.hasNavigationBar();
            if (DEBUG) Slog.v(TAG, "hasNavigationBar=" + showNav);
            if (showNav) {
                mNavigationBarView =
                    (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);

                mNavigationBarView.setDisabledFlags(mDisabled);
                mNavigationBarView.setBar(this);
            }
        } catch (RemoteException ex) {
            // no window manager? good luck with that
        }</span>

WindowManagerService類實現了WindowManagerPolicy的介面,所以WindowManagerService會回撥WindowManagerPolicy 的hasNavigationBar()介面,

<span style="font-size:18px;"> @Override
    public boolean hasNavigationBar() {
        return mPolicy.hasNavigationBar();
    }</span>

Policy向下呼叫實際上呼叫的是PhoneWindowManager實現的hasNavigationBar方法,下面程式碼是PhoneWindowManager中的hasNavigationBar()方法。
<span style="font-size:18px;">// Use this instead of checking config_showNavigationBar so that it can be consistently
    // overridden by qemu.hw.mainkeys in the emulator.
    public boolean hasNavigationBar() {
        return mHasNavigationBar;
    }</span>

而mHasNavigationBar的賦值可以在PhoneWindowManager中的setInitialDisplaySize(Display display, int width, int height, int density)方法中找到,

<span style="font-size:18px;">        if (!mHasSystemNavBar) {
            mHasNavigationBar = mContext.getResources().getBoolean(
                    com.android.internal.R.bool.config_showNavigationBar);
            // Allow a system property to override this. Used by the emulator.
            // See also hasNavigationBar().
            String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
            if (! "".equals(navBarOverride)) {
                if      (navBarOverride.equals("1")) mHasNavigationBar = false;
                else if (navBarOverride.equals("0")) mHasNavigationBar = true;
            }
        } else {
            mHasNavigationBar = false;
        }</span>
從上面程式碼可以看到mHasNavigationBar的值的設定是由兩處決定的:
1.首先從系統的資原始檔中取設定值config_showNavigationBar, 這個值的設定的檔案路徑是frameworks/base/core/res/res/values/config.xml
<!-- Whether a software navigation bar should be shown. NOTE: in the future this may be
         autodetected from the Configuration. -->
    <bool name="config_showNavigationBar">false</bool>
2.然後系統要獲取“qemu.hw.mainkeys”的值,這個值可能會覆蓋上面獲取到的mHasNavigationBar的值。如果“qemu.hw.mainkeys”獲取的值不為空的話,不管值是true還是false,都要依據後面的情況來設定。

所以上面的兩處設定共同決定了NavigationBar的顯示與隱藏。