Android技术知识Android进阶之路Android开发

Android 13 多用户切换 NavigationBar 返

2023-02-27  本文已影响0人  孤街酒客0911

学习笔记:

我们根据实际问题进行分析:设置多用户后,点击切换到新用户,在准备阶段返回主用户,移除新用户,概率出现返回键消失。

大家可以根据自己的经验判断下问题出在哪?

BackButton 是否显示在 NavigationBarView#updateNavButtonIcons() 中进行更新:

// NavigationBarView.java
    public void updateNavButtonIcons() {
        // 我们必须分别在退出或进入汽车模式时替换或恢复后退和主页按钮图标。
        // 最近在导航栏的 CarMode 中不可用,因此不需要更改为最近图标
        final boolean useAltBack =
                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
        KeyButtonDrawable backIcon = mBackIcon;
        orientBackButton(backIcon);
        KeyButtonDrawable homeIcon = mHomeDefaultIcon;
        if (!mUseCarModeUi) {
            orientHomeButton(homeIcon);
        }
        getHomeButton().setImageDrawable(homeIcon);
        getBackButton().setImageDrawable(backIcon);

        updateRecentsIcon();

        // Update IME button visibility, a11y and rotate button always overrides the appearance
        boolean disableImeSwitcher =
                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) == 0
                || isImeRenderingNavButtons();
        mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher);

        mBarTransitions.reapplyDarkIntensity();

        boolean disableHome = isGesturalMode(mNavBarMode)
                || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);

        // 当备用汽车模式 UI 处于活动状态和辅助显示时,始终禁用最近使用。
        boolean disableRecent = isRecentsButtonDisabled();

        // 如果 hone 和 recents 都被禁用,则禁用 home handle
        boolean disableHomeHandle = disableRecent
                && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);

        // ***********重点关注*******mDisabledFlags**********
        boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
                || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0))
                || isImeRenderingNavButtons();


        final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
        if (mOverviewProxyEnabled) {
            disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
            if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
                disableBack = disableHome = false;
            }
        } else if (pinningActive) {
            disableBack = disableRecent = false;
        }

        ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
        if (navButtons != null) {
            LayoutTransition lt = navButtons.getLayoutTransition();
            if (lt != null) {
                if (!lt.getTransitionListeners().contains(mTransitionListener)) {
                    lt.addTransitionListener(mTransitionListener);
                }
            }
        }

        getBackButton().setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
        getHomeButton().setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
        getRecentsButton().setVisibility(disableRecent  ? View.INVISIBLE : View.VISIBLE);
        getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
        notifyActiveTouchRegions();
    }

根据上述代码可知 BackButton 的显示取决于 disableBack 变量,而这个变量,我通过日志打印,发现取决于 mDisabledFlags 的值,这里 mDisabledFlags 我打印出来了3个值(当 mDisabledFlags = 4194304 时,BackButton 将不会显示。)

mDisabledFlags 值得更新在 NavigationBarView#setDisabledFlags() 方法中:

// NavigationBarView.java
    void setDisabledFlags(int disabledFlags, SysUiState sysUiState) {
        if (mDisabledFlags == disabledFlags) return;

        final boolean overviewEnabledBefore = isOverviewEnabled();
        mDisabledFlags = disabledFlags;

        // 如果刚刚启用概览,则更新图标以确保显示正确的图标
        if (!overviewEnabledBefore && isOverviewEnabled()) {
            reloadNavIcons();
        }

        updateNavButtonIcons();
        updateSlippery();
        updateDisabledSystemUiStateFlags(sysUiState);
    }

上述的 setDisabledFlags() 方法在 NavigationBar#disable() 中被调用:

// NavigationBar.java
    @Override
    public void disable(int displayId, int state1, int state2, boolean animate) {
        if (displayId != mDisplayId) {
            return;
        }
        // Navigation bar flags are in both state1 and state2.
        final int masked = state1 & (StatusBarManager.DISABLE_HOME
                | StatusBarManager.DISABLE_RECENT
                | StatusBarManager.DISABLE_BACK
                | StatusBarManager.DISABLE_SEARCH);
        if (masked != mDisabledFlags1) {
            mDisabledFlags1 = masked;
            mView.setDisabledFlags(state1, mSysUiFlagsContainer);
            updateScreenPinningGestures();
        }

        // 省略部分代码......
    }

disable() 方法中,有两个 state 值,这里只关注 state1。

通过打印堆栈,发现 disable()CommandQueue.java 中的 handleMessage() 里被调;
CommandQueue#disable()

// CommandQueue.java
    public void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
            boolean animate) {
        synchronized (mLock) {
            setDisabled(displayId, state1, state2);
            mHandler.removeMessages(MSG_DISABLE);
            final SomeArgs args = SomeArgs.obtain();
            args.argi1 = displayId;
            args.argi2 = state1;
            args.argi3 = state2;
            args.argi4 = animate ? 1 : 0;
            // 重点关注,Handler 消息,将会在handleMessage()里面处理
            Message msg = mHandler.obtainMessage(MSG_DISABLE, args);
            if (Looper.myLooper() == mHandler.getLooper()) {
                // If its the right looper execute immediately so hides can be handled quickly.
                mHandler.handleMessage(msg);
                msg.recycle();
            } else {
                msg.sendToTarget();
            }
        }
    }

这里还是要关注 state1,因为我们就是在朔源,找源头;通过分析可以看:StatusBarManagerService#disableLocked()

// StatusBarManagerService.java
    private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
            int whichFlag) {
        // 该方法里面会进行 setFlags(),set 的其实就是 what。后面讲到 
 setFlags()
        manageDisableListLocked(userId, what, token, pkg, whichFlag);
        // 重点关注
        final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
        final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
        final UiState state = getUiState(displayId);
        if (!state.disableEquals(net1, net2)) {
            state.setDisabled(net1, net2);
            mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
            if (mBar != null) {
                try {
                    // 这里我们只需关注  net1 的来源;这里会回调到:CommandQueue#disable()
                    mBar.disable(displayId, net1, net2);
                } catch (RemoteException ex) {
                }
            }
        }
    }

通过上述代码,我们留意到 StatusBarManagerService#gatherDisableActionsLocked()

    int gatherDisableActionsLocked(int userId, int which) {
        final int N = mDisableRecords.size();
        // gather the new net flags
        int net = 0;
        for (int i=0; i<N; i++) {
            final DisableRecord rec = mDisableRecords.get(i);
            if (rec.userId == userId) {
                net |= rec.getFlags(which);
            }
        }
        return net; 
    }

在这里,我们发现 net 是通过 rec.getFlags(which) 取的;那么就需要找对应的 setFlags() 方法。

这里面如何 setFlags() 的,不做过多分析,其实就是在StatusBarManagerService#manageDisableListLocked()方法里。

根据上述分析,接下来就需要跟踪 what 值得来源。
下面看个堆栈:

02-16 13:17:20.210965  1268  1316 D yexiao  : java.lang.Throwable
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.statusbar.StatusBarManagerService.disableLocked(StatusBarManagerService.java:1021)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.statusbar.StatusBarManagerService.setDisableFlags(StatusBarManagerService.java:1175)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.statusbar.StatusBarManagerService.-$$Nest$msetDisableFlags(Unknown Source:0)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.statusbar.StatusBarManagerService$1.setDisableFlags(StatusBarManagerService.java:383)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.wm.DisplayPolicy.lambda$updateSystemBarAttributes$14(DisplayPolicy.java:2376)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.wm.DisplayPolicy$$ExternalSyntheticLambda0.accept(Unknown Source:8)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.wm.DisplayPolicy.lambda$callStatusBarSafely$16$com-android-server-wm-DisplayPolicy(DisplayPolicy.java:2409)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.wm.DisplayPolicy$$ExternalSyntheticLambda16.run(Unknown Source:4)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at android.os.Handler.handleCallback(Handler.java:942)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at android.os.Handler.dispatchMessage(Handler.java:99)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at android.os.Looper.loopOnce(Looper.java:209)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at android.os.Looper.loop(Looper.java:296)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at android.os.HandlerThread.run(HandlerThread.java:67)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.ServiceThread.run(ServiceThread.java:44)
02-16 13:17:20.210965  1268  1316 D yexiao  :   at com.android.server.UiThread.run(UiThread.java:45)

上面堆栈,就是 what 值得来源,一旦有值改变,一定会通过 StatusBarManagerService#setDisableFlags()方法回调到 StatusBarManagerService#disableLocked()

根据堆栈看 DisplayPolicy#updateSystemBarAttributes()

// DisplayPolicy.java
    void updateSystemBarAttributes() {
        WindowState winCandidate = mFocusedWindow;

         // 省略部分代码......

        final WindowState win = winCandidate;
        mSystemUiControllingWindow = win;

        final int displayId = getDisplayId();
        // ******重点关注******  1
        final int disableFlags = win.getDisableFlags();
        final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
        final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
                mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
        final boolean isNavbarColorManagedByIme =
                navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow;
        final int appearance = updateLightNavigationBarLw(win.mAttrs.insetsFlags.appearance,
                navColorWin) | opaqueAppearance;
        final int behavior = win.mAttrs.insetsFlags.behavior;
        final String focusedApp = win.mAttrs.packageName;
        final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
                || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
        final AppearanceRegion[] statusBarAppearanceRegions =
                new AppearanceRegion[mStatusBarAppearanceRegionList.size()];
        mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions);
        // ******重点关注******  2
        Log.d("yexiao","mLastDisableFlags = "+ mLastDisableFlags  +"----------- disableFlags = "+disableFlags );
        if (mLastDisableFlags != disableFlags) {
            mLastDisableFlags = disableFlags;
            final String cause = win.toString();
            callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
                    cause));
        }

         // 省略部分代码......

    }

上述代码注释1处,disableFlags的值来源其实是在:WindowManagerService#relayoutWindow(),还可以往上朔源,这里就不在深入了。

wm这边的值,是在注释2处,设置到StatusBarManagerService那边去的,在按前面的流程,一步一步设置到SystemUI

回到开头那实际问题,当用户切换时有一个广播发出,最终在 DisplayPolicy.java 这边执行 DisplayPolicy#resetSystemBarAttributes()

// DisplayPolicy.java
    void resetSystemBarAttributes() {
        mLastDisableFlags = 0;
        updateSystemBarAttributes();
    }

这里将 mLastDisableFlags0,而 DisplayPolicy#updateSystemBarAttributes() 会一直被某个方法不停回调,这里没用去查看是哪个方法。当置 0 时,会出现时序问题;类似这样的变化:

mLastDisableFlags = 0 ----------- disableFlags = 4194304
mLastDisableFlags = 4194304 ----------- disableFlags = 4194304
mLastDisableFlags = 4194304 ----------- disableFlags = 4194304
// mLastDisableFlags = 4194304 ----------- disableFlags = 0    正常情况;
mLastDisableFlags = 0 ----------- disableFlags = 0       // 出现时序问题的情况

导致 disableFlags = 0 这种情况,无法设置到 StatusBarManagerService那边去,SystemUI那边也就无法更改。

该问题:需要修改注释2处的判断条件,并只在执行 DisplayPolicy#resetSystemBarAttributes() 时触发。

上一篇下一篇

猜你喜欢

热点阅读