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

相关文章
|
15天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
66 6
|
14天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
16天前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
22 3
|
17天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
19 2
|
30天前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
33 3
|
16天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
13 0
|
2月前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
讲解Activity的启动流程了,Activity的启动流程相对复杂一下,涉及到了Activity中的生命周期方法,涉及到了Android体系的CS模式,涉及到了Android中进程通讯Binder机制等等, 首先介绍一下Activity,这里引用一下Android guide中对Activity的介绍:
49 4
|
2月前
|
Android开发 开发者
Android面试之Activity启动流程简述
每个Android开发者都熟悉的Activity,但你是否了解它的启动流程呢?本文将带你深入了解。启动流程涉及四个关键角色:Launcher进程、SystemServer的AMS、应用程序的ActivityThread及Zygote进程。核心在于AMS与ActivityThread间的通信。文章详细解析了从Launcher启动Activity的过程,包括通过AIDL获取AMS、Zygote进程启动以及ActivityThread与AMS的通信机制。接着介绍了如何创建Application及Activity的具体步骤。整体流程清晰明了,帮助你更深入理解Activity的工作原理。
46 0
|
3月前
|
Android开发
我的Android进阶修炼:安卓启动流程之init(1)
本文深入分析了Android系统中的init进程,包括其源码结构、主要功能以及启动流程的详细注解,旨在帮助读者理解init作为用户空间的1号进程在Android启动过程中的关键作用。
56 1
|
2月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
147 0