Android 13 多用户切换 NavigationBar 返回键更新流程

学习笔记:

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

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

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()mLastDisableFlags 异常 时触发。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容