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

相关文章
|
1月前
|
Android开发
Android Mediatek bootloader oem锁定和解锁流程
Android Mediatek bootloader oem锁定和解锁流程
39 0
|
8月前
|
存储 SQL 人工智能
Android Activity启动流程一:从Intent到Activity创建
Android Activity启动流程一:从Intent到Activity创建
|
1天前
|
Android开发
Android面试题之activity启动流程
该文探讨了Android应用启动和Activity管理服务(AMS)的工作原理。从Launcher启动应用开始,涉及Binder机制、AMS回调、进程创建、Application和Activity的生命周期。文中详细阐述了AMS处理流程,包括创建ClassLoader、加载APK、启动Activity的步骤,以及权限校验和启动模式判断。此外,还补充了activity启动流程中AMS的部分细节。欲了解更多内容,可关注公众号“AntDream”。
6 1
|
1天前
|
vr&ar 数据库 Android开发
Android面试题之ActivityManagerService的启动流程
本文探讨了Android系统的SystemServer启动过程,包括创建SystemContext、引导服务、启动各类核心服务以及AMS的启动和初始化。AMS负责管理activity、广播队列、provider等,并设置SystemProcess,安装系统Provider。当AMS调用SystemReady时,系统UI准备启动,启动Launcher。文中还对比了init、zygote和system_server进程的角色。最后推荐了两本关于Android内核剖析的书籍:柯元旦教授的《Android内核剖析》和罗升阳的《Android系统源代码情景分析》。关注公众号AntDream获取更多内容。
4 0
|
3天前
|
Java Linux Android开发
Android面试题之说说系统的启动流程(总结)
这篇文章概述了Android系统的启动流程,从Boot Rom到Zygote进程和SystemServer的启动。init进程作为用户级别的第一个进程,负责创建文件目录、初始化服务并启动Zygote。Zygote通过预加载资源和创建Socket服务,使用fork函数生成SystemServer进程。fork过程中,子进程继承父进程大部分信息但具有独立的进程ID。Zygote预加载资源以减少后续进程的启动时间,而SystemServer启动众多服务并最终开启Launcher应用。文中还讨论了为何从Zygote而非init或SystemServer fork新进程的原因。
9 2
|
30天前
|
设计模式 算法 Android开发
2024年Android网络编程总结篇,androidview绘制流程面试
2024年Android网络编程总结篇,androidview绘制流程面试
2024年Android网络编程总结篇,androidview绘制流程面试
|
1月前
|
Java Android开发
Android 切换壁纸代码流程追踪
Android 切换壁纸代码流程追踪
20 0
|
1月前
|
编解码 调度 Android开发
Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置
Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置
46 0
|
1月前
|
Java Android开发 Kotlin
Android Dialog 弹出时,隐藏 navigation bar
Android Dialog 弹出时,隐藏 navigation bar
22 1
|
1月前
|
Java Android开发
Android startActivity流程
Android startActivity流程
11 0