Android 12系统源码_SystemUI(十)窗口焦点发生变化导航栏闪烁问题分析

news/2024/11/8 3:01:44/

前言

在使用Android12为车机系统载体进行系统SystemUI开发的过程中发现一个很奇特的问题,当不同页面发生切换的时候,导航栏总是会闪一下,其实就是窗口焦点发生变化的时候,导航栏总是会消失一下再出现,虽然问题不是很严重,但这对于用户体验来说是极差的,本篇文章我们就来梳理一下为什么会出现这种现象。

一、窗口焦点发生变化

1、当窗口焦点发生变化的时候,首先会触发WindowManagerService的updateFocusedWindowLocked方法。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {RootWindowContainer mRoot;boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);return changed;}
}

WindowManagerService的updateFocusedWindowLocked方法会进一步调用RootWindowContainer的updateFocusedWindowLocked方法。

2、RootWindowContainer的updateFocusedWindowLocked方法如下所示。

frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

class RootWindowContainer extends WindowContainer<DisplayContent>implements DisplayManager.DisplayListener {// List of children for this window container. List is in z-order as the children appear on// screen with the top-most window container at the tail of the list.protected final WindowList<E> mChildren = new WindowList<E>();//注意,此属性是父类WindowContainer的属性boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {mTopFocusedAppByProcess.clear();boolean changed = false;int topFocusedDisplayId = INVALID_DISPLAY;for (int i = mChildren.size() - 1; i >= 0; --i) {final DisplayContent dc = mChildren.get(i);//循环调用集合子条目DisplayContent的updateFocusedWindowLocked方法changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);final WindowState newFocus = dc.mCurrentFocus;if (newFocus != null) {final int pidOfNewFocus = newFocus.mSession.mPid;if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mActivityRecord);}if (topFocusedDisplayId == INVALID_DISPLAY) {topFocusedDisplayId = dc.getDisplayId();}} else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {// The top-most display that has a focused app should still be the top focused// display even when the app window is not ready yet (process not attached or// window not added yet).topFocusedDisplayId = dc.getDisplayId();}}if (topFocusedDisplayId == INVALID_DISPLAY) {topFocusedDisplayId = DEFAULT_DISPLAY;}if (mTopFocusedDisplayId != topFocusedDisplayId) {mTopFocusedDisplayId = topFocusedDisplayId;mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);}return changed;}}

RootWindowContainer的updateFocusedWindowLocked方法会循环调用类型为DisplayContent的集合的每个条目的updateFocusedWindowLocked方法。

3、DisplayContent的updateFocusedWindowLocked方法如下所示。

frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,int topFocusedDisplayId) {WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);if (mCurrentFocus == newFocus) {return false;}boolean imWindowChanged = false;final WindowState imWindow = mInputMethodWindow;if (imWindow != null) {final WindowState prevTarget = mImeLayeringTarget;final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);imWindowChanged = prevTarget != newTarget;if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {assignWindowLayers(false /* setLayoutNeeded */);}if (imWindowChanged) {mWmService.mWindowsChanged = true;setLayoutNeeded();newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);}}ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));final WindowState oldFocus = mCurrentFocus;mCurrentFocus = newFocus;if (newFocus != null) {mWinAddedSinceNullFocus.clear();mWinRemovedSinceNullFocus.clear();if (newFocus.canReceiveKeys()) {// Displaying a window implicitly causes dispatching to be unpaused.// This is to protect against bugs if someone pauses dispatching but// forgets to resume.newFocus.mToken.paused = false;}}//调用DisplayPolicy的focusChangedLwgetDisplayPolicy().focusChangedLw(oldFocus, newFocus);if (imWindowChanged && oldFocus != mInputMethodWindow) {// Focus of the input method window changed. Perform layout if needed.if (mode == UPDATE_FOCUS_PLACING_SURFACES) {performLayout(true /*initial*/,  updateInputWindows);} else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {// Client will do the layout, but we need to assign layers// for handleNewWindowLocked() below.assignWindowLayers(false /* setLayoutNeeded */);}}if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {// If we defer assigning layers, then the caller is responsible for doing this part.getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);}adjustForImeIfNeeded();// We may need to schedule some toast windows to be removed. The toasts for an app that// does not have input focus are removed within a timeout to prevent apps to redress// other apps' UI.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);if (mode == UPDATE_FOCUS_PLACING_SURFACES) {pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;}// Notify the accessibility manager for the change so it has the windows before the newly// focused one starts firing events.// TODO(b/151179149) investigate what info accessibility service needs before input can// dispatch focus to clients.if (mWmService.mAccessibilityController.hasCallbacks()) {mWmService.mH.sendMessage(PooledLambda.obtainMessage(this::updateAccessibilityOnWindowFocusChanged,mWmService.mAccessibilityController));}return true;}}

DisplayContent的updateFocusedWindowLocked方法会进一步调用DisplayPolicy的focusChangedLw方法。

4、DisplayPolicy的focusChangedLw方法如下所示。

frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java

public class DisplayPolicy {/*** A new window has been focused.*/public void focusChangedLw(WindowState lastFocus, WindowState newFocus) {mFocusedWindow = newFocus;mLastFocusedWindow = lastFocus;if (mDisplayContent.isDefaultDisplay) {mService.mPolicy.onDefaultDisplayFocusChangedLw(newFocus);}updateSystemBarAttributes();}
}

DisplayPolicy的focusChangedLw方法最终会调用updateSystemBarAttributes方法来刷新系统栏属性。

二、更新系统栏属性

1、DisplayPolicy的updateSystemBarAttributes的方法如下所示。

public class DisplayPolicy {void updateSystemBarAttributes() {WindowState winCandidate = mFocusedWindow;if (winCandidate == null && mTopFullscreenOpaqueWindowState != null&& (mTopFullscreenOpaqueWindowState.mAttrs.flags& WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) {// Only focusable window can take system bar control.winCandidate = mTopFullscreenOpaqueWindowState;}// If there is no window focused, there will be nobody to handle the events// anyway, so just hang on in whatever state we're in until things settle down.if (winCandidate == null) {return;}// The immersive mode confirmation should never affect the system bar visibility, otherwise// it will unhide the navigation bar and hide itself.if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {// The immersive mode confirmation took the focus from mLastFocusedWindow which was// controlling the system ui visibility. So if mLastFocusedWindow can still receive// keys, we let it keep controlling the visibility.final boolean lastFocusCanReceiveKeys =(mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys());winCandidate = isKeyguardShowing() && !isKeyguardOccluded() ? mNotificationShade: lastFocusCanReceiveKeys ? mLastFocusedWindow: mTopFullscreenOpaqueWindowState;if (winCandidate == null) {return;}}final WindowState win = winCandidate;mSystemUiControllingWindow = win;final int displayId = getDisplayId();//final int disableFlags = win.getDisableFlags();//获取final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);//更新系统栏参数...代码省略...}}

updateSystemBarAttributes方法首先获取当前的焦点窗口mFocusedWindow,将该窗口赋值给winCandidate,并判断该窗口是否可以操控系统栏,如果不允许会直接返回;如果允许,则会结合winCandidate的属性调用updateSystemBarsLw方法,来更新系统栏参数。

2、DisplayPolicy的updateSystemBarsLw方法如下所示。

public class DisplayPolicy {private int updateSystemBarsLw(WindowState win, int disableFlags) {final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();final boolean multiWindowTaskVisible =defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)|| defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW);final boolean freeformRootTaskVisible =defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);//判断当前是否存在多任务窗口或者悬浮窗,如果存在则需要强制显示系统栏mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible;//经测试发现,窗口焦点变化的时候,这行代码会触发导航栏闪一下mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);//导航栏或状态栏不透明属性int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;appearance = configureStatusBarOpacity(appearance);appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible,freeformRootTaskVisible);//是否需要隐藏导航栏final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);final long now = SystemClock.uptimeMillis();final boolean pendingPanic = mPendingPanicGestureUptime != 0&& now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;final DisplayPolicy defaultDisplayPolicy =mService.getDefaultDisplayContentLocked().getDisplayPolicy();if (pendingPanic && requestHideNavBar && win != mNotificationShade&& getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)// TODO (b/111955725): Show keyguard presentation on all external displays&& defaultDisplayPolicy.isKeyguardDrawComplete()) {// The user performed the panic gesture recently, we're about to hide the bars,// we're no longer on the Keyguard and the screen is ready. We can now request the bars.mPendingPanicGestureUptime = 0;if (!isNavBarEmpty(disableFlags)) {mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC,true /* isGestureOnSystemBar */);}}// update navigation barboolean oldImmersiveMode = mLastImmersiveMode;boolean newImmersiveMode = isImmersiveMode(win);if (oldImmersiveMode != newImmersiveMode) {mLastImmersiveMode = newImmersiveMode;// The immersive confirmation window should be attached to the immersive window root.final RootDisplayArea root = win.getRootDisplayArea();final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId;mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, newImmersiveMode,mService.mPolicy.isUserSetupComplete(),isNavBarEmpty(disableFlags));}return appearance;}}

以上代码经过测试发现,是mDisplayContent.getInsetsPolicy().updateBarControlTarget(win)这段代码导致窗口焦点发生变化的时候,导航栏会闪一下。

3、InsetsPolicy调用updateBarControlTarget更新当前可以控制系统栏的窗口对象。

frameworks/base/services/core/java/com/android/server/wm/InsetsPolicy.java

class InsetsPolicy {/** Updates the target which can control system bars. */void updateBarControlTarget(@Nullable WindowState focusedWin) {if (mFocusedWin != focusedWin){abortTransient();}mFocusedWin = focusedWin;final InsetsControlTarget statusControlTarget =getStatusControlTarget(focusedWin, false /* fake */);final InsetsControlTarget navControlTarget =getNavControlTarget(focusedWin, false /* fake */);mStateController.onBarControlTargetChanged(statusControlTarget,statusControlTarget == mDummyControlTarget? getStatusControlTarget(focusedWin, true /* fake */): null,navControlTarget,navControlTarget == mDummyControlTarget? getNavControlTarget(focusedWin, true /* fake */): null);mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);}}

InsetsPolicy的updateBarControlTarget方法的主要目的是更新当前可以控制系统栏的窗口对象,此方法会进一步调用InsetsStateController的onBarControlTargetChanged方法。

4、InsetsStateController的onBarControlTargetChanged方法如下所示。

frameworks/base/services/core/java/com/android/server/wm/InsetsStateController.java

class InsetsStateController {/*** Called when the focused window that is able to control the system bars changes.** @param statusControlling The target that is now able to control the status bar appearance*                          and visibility.* @param navControlling The target that is now able to control the nav bar appearance*                       and visibility.*/void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling,@Nullable InsetsControlTarget fakeStatusControlling,@Nullable InsetsControlTarget navControlling,@Nullable InsetsControlTarget fakeNavControlling) {onControlChanged(ITYPE_STATUS_BAR, statusControlling);//状态栏onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);//导航栏onControlChanged(ITYPE_CLIMATE_BAR, statusControlling);onControlChanged(ITYPE_EXTRA_NAVIGATION_BAR, navControlling);onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);onControlFakeTargetChanged(ITYPE_CLIMATE_BAR, fakeStatusControlling);onControlFakeTargetChanged(ITYPE_EXTRA_NAVIGATION_BAR, fakeNavControlling);notifyPendingInsetsControlChanged();}
}

http://www.ppmy.cn/news/74399.html

相关文章

【Linux】信号集及相关函数(sigemptyset、sigfillset、sigprocmask)

目录 1、信号集2、自定义信号集相关函数3、sigprocmask函数函数解析代码举例 橙色 1、信号集 多个信号组成的一个集合称为信号集&#xff0c;其系统数据类型为 sigset_t 。 在 PCB 中有两个非常重要的信号集&#xff0c;一个称为“阻塞信号集”&#xff0c;另一个是“未决信号…

FreeRTOS移植

准备工作 准备基础工程 基础工程越简单越好&#xff0c;这里直接用之前的跑马灯工程作为基础工程。 FreeRTOS源码 FreeRTOS源码就是Source文件 FreeRTOS移植 向工程中添加相关文件 添加FreeRTOS源码 在基础工程下创建一个文件夹放FreeRTOS源码&#xff0c;portable文件…

关于FLAME和SMPL模型

英文参考文献&#xff1a;https://medium.com/offnote-labs/3d-face-and-body-reconstruction-95f59ada1040 一个训练好的FLAME模型的输入是一个参数向量&#xff0c;包括形状参数、姿势参数和表情参数。这些参数分别控制人脸的身份特征、头部的旋转和平移、面部的表情变化。一…

Python可视化工具分享

今天和大家分享几个实用的纯python构建可视化界面服务&#xff0c;比如日常写了脚本但是不希望给别人代码&#xff0c;可以利用这些包快速构建好看的界面作为服务提供他人使用。有关于库的最新更新时间和当前star数量。 1、 streamlit (23.3k Updated 2 hours ago) Streamlit…

数据全生命周期管理

数据存储 时代"海纳百川&#xff0c;有容乃大"意味结构化、半结构和非结构化多样化的海量的 &#xff0c;也意味着批数据和流数据多种数据形式的存储和计算。面对不同数据结构、数据形式、时效性与性能要求和存储与计算成本等因素考虑&#xff0c;应该使用适合的存储…

metaRTC+ZLMediaKit实现webrtc的推拉流

概述 ZLMediaKit是一个基于C11的高性能运营级流媒体服务框架&#xff0c;是一个支持webrtc SFU的优秀的流媒体服务器系统。 metaRTC新版本支持whip/whep协议&#xff0c;支持whip/whep协议的ZLMediaKit推拉流。 信令通信 ZLMediaKit新版本支持whip和whep协议&#xff0c;支…

【网络工程师人手必备的常用网络命令合集,整理收藏!】

在计算机网络中经常要对网络进行管理&#xff0c;测试&#xff0c;这时就要用到网络命令。今天就为大家整理了一些网络工程师必备的一些常用网络命令合集&#xff0c;建议收藏后观看哦&#xff01; ping命令 ping是个使用频率极高的实用程序&#xff0c;主要用于确定网络的连…

一图看懂!RK3568与RK3399怎么选?

▎简介 RK3568和RK3399都是Rockchip公司的处理器&#xff0c;具有不同的特点和适用场景。以下是它们的主要区别和应用场景。 ▎RK3568 RK3568是新一代的高性能处理器&#xff0c;采用了22nm工艺&#xff0c;具有更高的性能和更低的功耗。它支持4K视频解码和编码&#xff0c;支持…