Android SystemUI组件(11)SystemUIVisibility解读

该系列文章总纲链接:专题分纲目录 Android SystemUI组件


本章关键点总结 & 说明:

说明:本章节持续迭代之前章节思维导图,主要关注左侧最上方SystemUiVisibility解读部分即可。

本章节主要讲解SystemUiVisibility的概念及其相关常用的属性,以及在应用中如何使用,最后研究下在framework层setSystemUiVisibility的具体实现逻辑及涉及到的一些相关内容。

1 理解SystemUIVisibility

1.1 SysIVisibility 简介

在Android系统中,SystemUIVisibility 是普通应用用于控制系统UI元素(如状态栏和导航栏)可见性的机制。通过设置不同的标志,开发者可以控制这些UI元素的显示和隐藏,以及它们对应用布局的影响。以下是一些与SystemUIVisibility相关的常用属性:

  • SYSTEM_UI_FLAG_LAYOUT_STABLE:当系统栏的可见性改变时,保持应用的布局稳定,避免内容布局随着系统栏的显示和隐藏而跳动。
  • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:允许应用的布局扩展到导航栏区域,即使导航栏可见。
  • SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:允许应用的布局扩展到状态栏区域,即使状态栏可见。
  • SYSTEM_UI_FLAG_HIDE_NAVIGATION:隐藏导航栏,但用户可以通过滑动屏幕边缘来重新显示导航栏。
  • SYSTEM_UI_FLAG_FULLSCREEN:隐藏状态栏,但用户可以通过下拉屏幕顶部来重新显示状态栏。
  • SYSTEM_UI_FLAG_IMMERSIVE:提供一种沉浸式体验,系统栏不会自动显示,直到用户执行特定的滑动操作。
  • SYSTEM_UI_FLAG_IMMERSIVE_STICKY:类似于SYSTEM_UI_FLAG_IMMERSIVE,但系统栏会在一定时间后自动隐藏。
  • SYSTEM_UI_FLAG_LIGHT_STATUS_BAR:将状态栏的文字和图标颜色设置为深色,以便在浅色背景上清晰可见。

使用setSystemUiVisibility方法时,可以通过按位或操作(|)组合多个标志来实现复杂的系统UI控制。

1.2 解读应用中setSystemUiVisibility方法的使用

在Android中,setSystemUiVisibility(int visibility)方法是View类的一部分,通常在Activity的某个视图上调用,以控制系统UI元素(如状态栏和导航栏)的可见性。以下是一个普通应用中如何使用setSystemUiVisibility方法的步骤:

  • 获取布局中的视图: 首先,你需要获取到Activity主布局或者特定的视图,这取决于你想要影响的UI部分。
  • 调用setSystemUiVisibility方法: 在视图上调用setSystemUiVisibility方法,并传入一个或多个标志的组合,这些标志定义了系统UI的可见性。
  • 处理onWindowFocusChanged回调: 在你的Activity中重写onWindowFocusChanged方法,并在其中调用setSystemUiVisibility方法。当窗口焦点发生变化时,这个方法会被调用。

下面是一个示例代码,展示了如何在Activity中隐藏状态栏:

@Override
public void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);if (hasFocus) {// 隐藏状态栏getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // 隐藏导航栏| View.SYSTEM_UI_FLAG_FULLSCREEN // 隐藏状态栏| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);}
}

在这个例子中,当Activity获得窗口焦点时,状态栏和导航栏会被隐藏,并且布局会扩展到这些UI元素的区域。

注意setSystemUiVisibility方法在API级别11中引入,在31中废除,所以如果你的应用支持的最低API级别低于11,你需要做兼容性处理。

使用setSystemUiVisibility是控制应用内系统UI元素可见性的简单有效方式。

2 setSystemUiVisibility流程解读(framework层分析)

2.1 从View的setSystemUiVisibility方法开始解读

接下来开始分析setSystemUiVisibility的实现,对应的代码实现如下所示:

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {private static final boolean DBG = false;//...public void setSystemUiVisibility(int visibility) {if (visibility != mSystemUiVisibility) {mSystemUiVisibility = visibility;if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {mParent.recomputeViewAttributes(this);}}}//...
}

过程中除了调用View的可能会调用ViewGroup中的recomputeViewAttributes方法,对应的代码实现如下所示:

//ViewGrouppublic void recomputeViewAttributes(View child) {if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {ViewParent parent = mParent;if (parent != null) parent.recomputeViewAttributes(this);}}

但是不管怎样,最后一定会调用到ViewRootImpl的recomputeViewAttributes方法,对应的代码实现如下:

//ViewRootImpl//...//关键流程 step1@Overridepublic void recomputeViewAttributes(View child) {checkThread(); // 确保该方法在UI线程中调用if (mView == child) {mAttachInfo.mRecomputeGlobalAttributes = true; // 设置标志,表示需要重新计算全局属性if (!mWillDrawSoon) { // 如果当前没有即将进行的绘制操作scheduleTraversals(); // 调度绘制流程,以便更新视图}}}//...//关键流程 step2void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().postSyncBarrier();//等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();}}//...//关键流程 step3final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}//...//关键流程 step4void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");try {//执行performTraversalsperformTraversals();} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}//...//关键流程 step5private void performTraversals() {// cache mView since it is used so much below...final View host = mView;if (host == null || !mAdded)return;mIsInTraversal = true;mWillDrawSoon = true;boolean windowSizeMayChange = false;boolean newSurface = false;boolean surfaceChanged = false;WindowManager.LayoutParams lp = mWindowAttributes;int desiredWindowWidth;int desiredWindowHeight;final int viewVisibility = getHostVisibility();boolean viewVisibilityChanged = mViewVisibility != viewVisibility|| mNewSurfaceNeeded;WindowManager.LayoutParams params = null;if (mWindowAttributesChanged) {mWindowAttributesChanged = false;surfaceChanged = true;params = lp;}CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();//...//收集mView的属性,判断是否需要更新paramsif (collectViewAttributes()) {params = lp;}//...//此方法最终会触发WindowManagerService的relayoutWindow方法relayoutWindow(params, viewVisibility, insetsPending);//... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//测量逻辑//...     	performLayout(lp, mWidth, mHeight);//布局逻辑//...     	performDraw();//绘制逻辑//...}

接下来我们要关注2个关键的方法调用:

  1. collectViewAttributes方法:重新计算最新的SystemUIVisibility属性。
  2. relayoutWindow方法:重新布局,流程较长,最终影响系统状态栏StatusBar对SystemUIVisibility属性的处理。

接下来的2.2 和 2.3 分别以这2个方法为入口进行代码的分析和解读。

2.2 解读collectViewAttributes方法

ViewRootImpl的collectViewAttributes方法代码实现如下:

//ViewRootImplprivate boolean collectViewAttributes() {// 检查是否需要重新计算全局属性if (mAttachInfo.mRecomputeGlobalAttributes) {// 重置全局属性重新计算标志mAttachInfo.mRecomputeGlobalAttributes = false;// 保存旧的屏幕保持状态boolean oldScreenOn = mAttachInfo.mKeepScreenOn;// 初始化屏幕保持标志为falsemAttachInfo.mKeepScreenOn = false;// 重置系统UI可见性标志mAttachInfo.mSystemUiVisibility = 0;// 重置系统UI监听器标志mAttachInfo.mHasSystemUiListeners = false;// 通知视图分发收集视图属性mView.dispatchCollectViewAttributes(mAttachInfo, 0);// 应用视图分发时设置的系统UI可见性标志,并考虑被禁用的标志mAttachInfo.mSystemUiVisibility &= ~mAttachInfo.mDisabledSystemUiVisibility;WindowManager.LayoutParams params = mWindowAttributes;// 获取布局参数中隐含的系统UI可见性标志mAttachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params);// 检查屏幕保持标志、系统UI可见性标志和系统UI监听器是否有变化if (mAttachInfo.mKeepScreenOn != oldScreenOn|| mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility|| mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {// 应用保持屏幕打开的标志applyKeepScreenOnFlag(params);// 更新布局参数中的系统UI可见性标志params.subtreeSystemUiVisibility = mAttachInfo.mSystemUiVisibility;// 更新布局参数中的系统UI监听器标志params.hasSystemUiListeners = mAttachInfo.mHasSystemUiListeners;// 分发系统UI可见性变化事件mView.dispatchWindowSystemUiVisiblityChanged(mAttachInfo.mSystemUiVisibility);return true;// 返回true表示属性有变化}}return false;// 返回false表示属性没有变化}

该方法会清空当前窗口视图的SystemUiVisibility属性(mAttachInfo.mSystemUiVisibility = 0),然后调用View的dispatchCollectViewAttributes方法重新获取最新的的SystemUiVisibility属性。接下来我们分析View的dispatchCollectViewAttributes和dispatchWindowSystemUiVisiblityChanged方法。

2.2.1 dispatchCollectViewAttributes方法分析

dispatchCollectViewAttributes的目的是遍历视图树,并收集所有视图的属性,特别是与系统 UI 相关的属性,如系统栏的可见性(SystemUIVisibility)。代码实现如下:

//View//...//关键流程 step1void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) {performCollectViewAttributes(attachInfo, visibility);}//...//关键流程 step2void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {if ((visibility & VISIBILITY_MASK) == VISIBLE) {if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) {attachInfo.mKeepScreenOn = true;}//将新的systemuivisiblity赋予attachInfo.mSystemUiVisibilityattachInfo.mSystemUiVisibility |= mSystemUiVisibility;ListenerInfo li = mListenerInfo;// 如果监听器信息不为空,并且设置了系统UI可见性变化的监听器if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {// 设置AttachInfo的mHasSystemUiListeners为trueattachInfo.mHasSystemUiListeners = true;}}}

这里performCollectViewAttributes方法的主要作用是收集视图的属性,并将这些属性更新到AttachInfo对象中。这些属性包括屏幕保持标志和系统UI可见性标志,以及是否有监听器需要响应系统UI可见性的变化。这些信息对于视图的正确显示和系统UI的控制非常重要。

2.2.2 dispatchWindowSystemUiVisiblityChanged方法分析

View的dispatchWindowSystemUiVisiblityChanged方法是在系统 UI 可见性发生变化时,分发这些变化通知给视图树中的所有相关视图。这些变化可能包括状态栏和导航栏的显示或隐藏,以及它们的外观(如颜色、图标颜色等)。代码实现如下:

//View//...//关键流程 step1public void dispatchWindowSystemUiVisiblityChanged(int visible) {onWindowSystemUiVisibilityChanged(visible);}//关键流程 step2public void onWindowSystemUiVisibilityChanged(int visible) {}

默认的View对onWindowSystemUiVisibilityChanged的实现为空,但如果是DecorView,则代码实现为:

//PhoneWindow//DecorView@Overridepublic void onWindowSystemUiVisibilityChanged(int visible) {updateColorViews(null /* insets */);}private WindowInsets updateColorViews(WindowInsets insets) {WindowManager.LayoutParams attrs = getAttributes();int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();if (!mIsFloating && ActivityManager.isHighEndGfx()) {  //如果不是悬浮窗且设备支持高端图形if (insets != null) {  // 如果有系统窗口插入// 更新状态栏和导航栏底部的插入距离mLastTopInset = Math.min(insets.getStableInsetTop(), insets.getSystemWindowInsetTop());mLastBottomInset = Math.min(insets.getStableInsetBottom(), insets.getSystemWindowInsetBottom());mLastRightInset = Math.min(insets.getStableInsetRight(), insets.getSystemWindowInsetRight());}// 更新状态栏颜色视图mStatusColorView = updateColorViewInt(mStatusColorView, sysUiVisibility,SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,mStatusBarColor, mLastTopInset, Gravity.TOP,STATUS_BAR_BACKGROUND_TRANSITION_NAME,com.android.internal.R.id.statusBarBackground,(getAttributes().flags & FLAG_FULLSCREEN) != 0);// 更新导航栏颜色视图mNavigationColorView = updateColorViewInt(mNavigationColorView, sysUiVisibility,SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,mNavigationBarColor, mLastBottomInset, Gravity.BOTTOM,NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,com.android.internal.R.id.navigationBarBackground,false /* hiddenByWindowFlag */);}// 处理窗口布局参数和系统UI可见性标志以确定是否消费了导航栏空间boolean consumingNavBar =(attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0&& (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;int consumedRight = consumingNavBar ? mLastRightInset : 0;int consumedBottom = consumingNavBar ? mLastBottomInset : 0;// 如果内容根视图存在并且其布局参数是MarginLayoutParams类型if (mContentRoot != null&& mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();// 如果消费的右边或底部距离发生变化,则更新布局参数if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {lp.rightMargin = consumedRight;lp.bottomMargin = consumedBottom;mContentRoot.setLayoutParams(lp);if (insets == null) {  // 如果当前没有分发系统窗口插入// 请求应用系统窗口插入requestApplyInsets();}}// 如果有系统窗口插入,更新插入信息if (insets != null) {insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(),insets.getSystemWindowInsetTop(),insets.getSystemWindowInsetRight() - consumedRight,insets.getSystemWindowInsetBottom() - consumedBottom);}}// 如果有系统窗口插入,消费稳定的插入部分if (insets != null) {insets = insets.consumeStableInsets();}return insets;  // 返回更新后的系统窗口插入信息}

updateColorViews 方法的主要目的是更新窗口中状态栏和导航栏的背景色,以及处理系统窗口插入的逻辑。这确保了窗口的 UI 与系统UI可见性标志和窗口布局参数保持同步,从而提供一致的用户体验。

2.3 解读relayoutWindow方法

2.3.1 主流程ViewRootImpl的relayoutWindow分析

ViewRootImpl的relayoutWindow代码实现如下所示:

//ViewRootImplprivate int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,boolean insetsPending) throws RemoteException {//...int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,(int) (mView.getMeasuredWidth() * appScale + 0.5f),(int) (mView.getMeasuredHeight() * appScale + 0.5f),viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,mPendingStableInsets, mPendingConfiguration, mSurface);//...return relayoutResult;}

调用relayoutWindow方法,该方法主要是调用IWindowSession的relayout方法,Session的relayout方法代码如下所示:

//Sessionpublic int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,int requestedWidth, int requestedHeight, int viewFlags,int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,Rect outVisibleInsets, Rect outStableInsets, Configuration outConfig,Surface outSurface) {//...int res = mService.relayoutWindow(this, window, seq, attrs,requestedWidth, requestedHeight, viewFlags, flags,outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,outStableInsets, outConfig, outSurface);//...return res;}

该方法主要是调用WindowManagerService的relayout方法,WindowManagerService的relayout方法代码如下所示:

//WindowManagerService//...//关键流程step1public int relayoutWindow(Session session, IWindow client, int seq,WindowManager.LayoutParams attrs, int requestedWidth,int requestedHeight, int viewVisibility, int flags,Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,Rect outVisibleInsets, Rect outStableInsets, Configuration outConfig,Surface outSurface) {boolean toBeDisplayed = false;  // 标记窗口是否将要被显示boolean inTouchMode;  // 当前是否处于触摸模式boolean configChanged;  // 标记配置是否改变boolean surfaceChanged = false;  // 标记表面是否改变boolean animating;  // 窗口是否正在动画中//是否有状态栏的使用权限boolean hasStatusBarPermission =mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)== PackageManager.PERMISSION_GRANTED;// 清除调用者身份,防止身份伪造long origId = Binder.clearCallingIdentity();synchronized(mWindowMap) {//...//如果焦点可能改变,更新焦点窗口if (focusMayChange) {if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,false /*updateInputWindows*/)) {imMayMove = false;}}//...}//构造返回值,表示窗口重新布局的结果return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0)| (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0)| (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0)| (animating ? WindowManagerGlobal.RELAYOUT_RES_ANIMATING : 0);}//...//关键流程step2private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {// 计算当前应该获得焦点的窗口WindowState newFocus = computeFocusedWindowLocked();// 如果新的焦点窗口与当前的不一样if (mCurrentFocus != newFocus) {// 移除之前的焦点改变消息,发送新的焦点改变消息mH.removeMessages(H.REPORT_FOCUS_CHANGE);mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);// 获取默认显示内容final DisplayContent displayContent = getDefaultDisplayContentLocked();// 如果需要移动输入法窗口final boolean imWindowChanged = moveInputMethodWindowsIfNeededLocked(mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES);// 如果输入法窗口发生了变化,重新计算焦点if (imWindowChanged) {displayContent.layoutNeeded = true;newFocus = computeFocusedWindowLocked();}// 更新当前焦点窗口final WindowState oldFocus = mCurrentFocus;mCurrentFocus = newFocus;mLosingFocus.remove(newFocus);// 如果启用了辅助功能并且焦点窗口在默认显示上,通知辅助功能控制器if (mAccessibilityController != null&& displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {mAccessibilityController.onWindowFocusChangedLocked();}// 通知PhoneWindowManager焦点变化int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);//...// 返回true表示焦点确实发生了变化return true;}// 如果焦点没有变化,返回falsereturn false;}

relayout方法的主要负责处理窗口的重新布局和显示。它涉及权限检查、同步操作、窗口参数调整、系统 UI 可见性处理、焦点更新等多个关键步骤,以确保窗口的正确显示和交互。

这里我们主要关注了updateFocusedWindowLocked方法,该方法用于更新获得焦点的窗口,并处理与焦点变化相关的一系列操作,包括输入法窗口的移动、辅助功能的更新、窗口政策的焦点变化通知、布局执行等。如果焦点发生变化,该方法返回true,否则返回false。

在updateFocusedWindowLocked方法中,我们关注通知PhoneWindowManager焦点变化的方法mPolicy.focusChangedLw。PhoneWindowManager中的focusChangedLw方法代码实现如下:

//PhoneWindowManager//...//关键流程 step1@Overridepublic int focusChangedLw(WindowState lastFocus, WindowState newFocus) {mFocusedWindow = newFocus;if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) {return FINISH_LAYOUT_REDO_LAYOUT;}return 0;}//...//关键流程 step2private int updateSystemUiVisibilityLw() {WindowState win = mFocusedWindow != null ? mFocusedWindow : mTopFullscreenOpaqueWindowState;int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)& ~mResettingSystemUiFlags& ~mForceClearedSystemUiFlags;// 如果正在强制显示导航栏,并且当前窗口的层次低于强制显示的层次,则清除可清除的标记if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) {tmpVisibility &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS);}// 更新系统栏的可见性final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);// 计算新旧可见性标记的差异final int diff = visibility ^ mLastSystemUiFlags;// 检查当前窗口是否需要菜单final boolean needsMenu = win.getNeedsMenuLw(mTopFullscreenOpaqueWindowState);// 如果没有差异,并且菜单需求没有变化,并且焦点应用没有变化,则直接返回if (diff == 0 && mLastFocusNeedsMenu == needsMenu&& mFocusedApp == win.getAppToken()) {return 0;}// 更新最后的系统UI可见性标记mLastSystemUiFlags = visibility;mLastFocusNeedsMenu = needsMenu;mFocusedApp = win.getAppToken();// 在主线程中异步更新状态栏服务mHandler.post(new Runnable() {@Overridepublic void run() {try {//获取StatusBarManagerService服务IStatusBarService statusbar = getStatusBarService();if (statusbar != null) {// 调用状态栏服务的setSystemUiVisibility方法,更新状态栏和导航栏的可见性statusbar.setSystemUiVisibility(visibility, 0xffffffff);statusbar.topAppWindowChanged(needsMenu);}} catch (RemoteException e) {mStatusBarService = null;}}});// 返回差异标记return diff;}

updateSystemUiVisibilityLw方法的主要作用是更新系统UI的可见性,包括状态栏和导航栏的可见性。它通过计算当前窗口的系统UI可见性标记,处理导航栏强制显示的情况,然后异步更新状态栏服务来实现。这个方法确保了系统UI的可见性与窗口的状态保持同步。同时,这里调用状态栏管理服务StatusBarManagerService的setSystemUiVisibility方法,通知状态栏和底部栏进行样式调整。
StatusBarManagerService的setSystemUiVisibility方法代码实现如下:

//StatusBarManagerServiceprivate volatile IStatusBar mBar;//...//关键流程 step1public void setSystemUiVisibility(int vis, int mask) {// also allows calls from window manager which is in this process.enforceStatusBarService();synchronized (mLock) {updateUiVisibilityLocked(vis, mask);disableLocked(mCurrentUserId,vis & StatusBarManager.DISABLE_MASK,mSysUiVisToken,"WindowManager.LayoutParams");}}//...//关键流程 step2private void updateUiVisibilityLocked(final int vis, final int mask) {if (mSystemUiVisibility != vis) {mSystemUiVisibility = vis;mHandler.post(new Runnable() {public void run() {if (mBar != null) {try {mBar.setSystemUiVisibility(vis, mask);} catch (RemoteException ex) {}}}});}}

这里继续分析mBar.setSystemUiVisibility的方法实现,mBar是IStatusBar 类型的参照文章:

Android SystemUI组件(05)状态栏-系统状态图标显示&管理中2.2 部分可知。这里涉及到的mBar实际上是 CommandQueue(它继承了IStatusBar.Stub)类型。因此继续分析mBar对应类型CommandQueue的setSystemUiVisibility方法。具体实现如下:

//CommandQueue//...//关键流程 step1public void setSystemUiVisibility(int vis, int mask) {synchronized (mList) {mHandler.removeMessages(MSG_SET_SYSTEMUI_VISIBILITY);mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget();}}//...//关键流程 step2,handler处理消息private final class H extends Handler {public void handleMessage(Message msg) {final int what = msg.what & MSG_MASK;switch (what) {//...case MSG_SET_SYSTEMUI_VISIBILITY:mCallbacks.setSystemUiVisibility(msg.arg1, msg.arg2);break;//...}}}

这个主逻辑线最终执行到了mCallbacks的setSystemUiVisibility。至此这条线就结束了,接下来主要看mCallbacks是如何赋值的即可。

2.3.2 基于mCallbacks赋值的分析和深入解读

mCallbacks是在CommandQueue初始化时进行赋值的,代码如下所示:

//CommandQueue//callback在CommandQueue构造时的初始化public CommandQueue(Callbacks callbacks, StatusBarIconList list) {mCallbacks = callbacks;mList = list;}

使用CommandQueue初始化的位置只有BaseStatusBar中有一个new的操作,代码如下所示:

//BaseStatusBarpublic void start() {mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);mWindowManagerService = WindowManagerGlobal.getWindowManagerService();mDisplay = mWindowManager.getDefaultDisplay();//...mCommandQueue = new CommandQueue(this, iconList);//...}

这里传递的this实际上是PhoneStatusBar,即BaseStatusBar的子类(该部分如不理解可参考文章:Android SystemUI组件(05)状态栏-系统状态图标显示&管理)。基于此,接下来分析PhoneStatusBar的setSystemUiVisibility方法实现,代码如下:

//PhoneStatusBar//...//关键流程 step1public void setSystemUiVisibility(int vis, int mask) {// 获取旧的系统UI可见性值final int oldVal = mSystemUiVisibility;// 计算新的系统UI可见性值final int newVal = (oldVal & ~mask) | (vis & mask);final int diff = newVal ^ oldVal;// 如果有差异,执行更新操作if (diff != 0) {// 保存最近应用可见性的状态final boolean wasRecentsVisible = (mSystemUiVisibility & View.RECENT_APPS_VISIBLE) > 0;// 更新系统UI可见性值mSystemUiVisibility = newVal;//...// 计算状态栏模式final int sbMode = computeBarMode(oldVal, newVal, mStatusBarView.getBarTransitions(),View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_TRANSLUCENT);// 计算导航栏模式final int nbMode = mNavigationBarView == null ? -1 : computeBarMode(oldVal, newVal, mNavigationBarView.getBarTransitions(),View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT);final boolean sbModeChanged = sbMode != -1;final boolean nbModeChanged = nbMode != -1;boolean checkBarModes = false;// 如果状态栏模式发生变化,更新状态栏模式if (sbModeChanged && sbMode != mStatusBarMode) {mStatusBarMode = sbMode;checkBarModes = true;}// 如果导航栏模式发生变化,更新导航栏模式if (nbModeChanged && nbMode != mNavigationBarMode) {mNavigationBarMode = nbMode;checkBarModes = true;}// 如果状态栏或导航栏模式发生变化,检查模式if (checkBarModes) {checkBarModes();}// 如果状态栏或导航栏模式发生变化,更新显示if (sbModeChanged || nbModeChanged) {// 更新临时栏自动隐藏if (mStatusBarMode == MODE_SEMI_TRANSPARENT || mNavigationBarMode == MODE_SEMI_TRANSPARENT) {scheduleAutohide();} else {cancelAutohide();}}// 准备取消隐藏if ((vis & View.STATUS_BAR_UNHIDE) != 0) {mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE;}if ((vis & View.NAVIGATION_BAR_UNHIDE) != 0) {mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE;}// 恢复最近应用可见性的状态if (wasRecentsVisible) {mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;}// 通知窗口管理器系统UI可见性发生变化notifyUiVisibilityChanged(mSystemUiVisibility);}}//...//关键流程 step2private void notifyUiVisibilityChanged(int vis) {try {mWindowManagerService.statusBarVisibilityChanged(vis);} catch (RemoteException ex) {}}

setSystemUiVisibility方法用于控制系统UI元素的可见性,包括状态栏、导航栏和最近应用栏。它通过计算新的可见性值,更新低功耗模式,计算和更新状态栏和导航栏的模式,以及通知窗口管理器可见性变化来实现。

接下来继续分析WindowManagerService.statusBarVisibilityChanged方法的实现,代码如下:

//WindowManagerService//...//关键流程 step1public void statusBarVisibilityChanged(int visibility) {//...synchronized (mWindowMap) {mLastStatusBarVisibility = visibility;//调整可见性visibility = mPolicy.adjustSystemUiVisibilityLw(visibility);//更新窗口可见性updateStatusBarVisibilityLocked(visibility);}}//...//关键流程 step2void updateStatusBarVisibilityLocked(int visibility) {// 通知输入管理器系统UI可见性的变化mInputManager.setSystemUiVisibility(visibility);// 获取默认窗口列表final WindowList windows = getDefaultWindowListLocked();// 遍历所有窗口final int N = windows.size();for (int i = 0; i < N; i++) {WindowState ws = windows.get(i);try {// 获取当前窗口的系统UI可见性值int curValue = ws.mSystemUiVisibility;// 计算当前值与新值的差异int diff = curValue ^ visibility;// 只关注可清除标志位的差异diff &= View.SYSTEM_UI_CLEARABLE_FLAGS;// 如果标志位实际上已经被清除了diff &= ~visibility;// 计算新的系统UI可见性值int newValue = (curValue & ~diff) | (visibility & diff);// 如果新值与当前值不同,则更新窗口的系统UI可见性值和序列号if (newValue != curValue) {ws.mSeq++;ws.mSystemUiVisibility = newValue;}// 如果值有变化,或者窗口有系统UI监听器,则分发系统UI可见性变化事件if (newValue != curValue || ws.mAttrs.hasSystemUiListeners) {ws.mClient.dispatchSystemUiVisibilityChanged(ws.mSeq,visibility, newValue, diff);}} catch (RemoteException e) {// 如果发生远程异常,忽略该窗口// so sorry}}}

updateStatusBarVisibilityLocked方法的主要作用是更新状态栏的可见性。它通过遍历所有窗口,计算系统UI可见性的变化,然后更新每个窗口的状态,并分发系统UI可见性变化事件。这个方法确保了所有窗口的系统UI可见性与当前状态保持同步。至此,setSystemUiVisibility的流程基本上就分析结束了。

总之,通过上面的一系列流程,从View的setSystemUiVisibility方法一直到PhoneStatusBar自己的setSystemUiVisibility方法执行,也会通过WMS将对应的SystemUiVisibility属性更新每个窗口的状态。这样,下一次UI更新时,会根据具体的属性显示对应的样式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/55552.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(一).创建基础表

一. 使用工具和命令 1.1 使用的工具 Navicat Premium 17 &#xff1a;“Navicat”是一套可创建多个连接的数据库管理工具。 MySQL版本8.0.39 。 1.2 使用的命令 Navicat中使用的命令 命令命令解释SHOW DATABASES&#xff1b;展示所有的数据库CREATE DATABASE 数据库名称; 创…

thinkphp 学习记录

1、PHP配置 &#xff08;点开链接后&#xff0c;往下拉&#xff0c;找到PHP8.2.2版本&#xff0c;下载的是ZIP格式&#xff0c;解压即用&#xff09; PHP For Windows: Binaries and sources Releases &#xff08;这里是下载地址&#xff09; 我解压的地址是&#xff1a;D:\…

1、如何查看电脑已经连接上的wifi的密码?

在电脑桌面右下角的如下位置&#xff1a;双击打开查看当前连接上的wifi的名字&#xff1a;ZTE-kfdGYX-5G 按一下键盘上的win R 键, 输入【cmd】 然后&#xff0c;按一下【回车】。 输入netsh wlan show profile ”wifi名称” keyclear : 输入完成后&#xff0c;按一下回车&…

中断系统的原理

一、介绍 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的。中断是指‌CPU在正常运行程序时&#xff0c;由于内部或外部事件的发生&#xff0c;导致CPU中断当前运行的程序&#xff0c;转而去执行其他程序的过程。‌ 中断可以是硬件产生的&#xff0c;也可以是…

安全运营中心 (SOC) 团队对其安全工具感到失望

Vectra AI 表示&#xff0c;安全运营中心 (SOC) 从业人员认为&#xff0c;由于太多孤立的工具和缺乏准确的攻击信号&#xff0c;他们在检测和确定真实威胁的优先级方面正在失败。 人们对供应商的不信任感日益加深&#xff0c;认为供应商的工具在发现真正的攻击方面起的阻碍作用…

金纳米星“融入”水凝胶,原位生长的奥秘,应用前景的探索

大家好&#xff01;今天来了解一项在三维水凝胶表面生长金纳米星的研究——《Growing Gold Nanostars on 3D Hydrogel Surfaces》发表于《Chemistry of Materials》。水凝胶在生物医学等诸多领域有着重要应用&#xff0c;而金纳米星具有独特的光学性质。这项研究通过原位合成的…

【Linux】线程与线程安全知识总结

向外张望的人在做梦&#xff0c; 向内审视的人才是清醒的。 --- 荣格 --- 我最近复习了线程安全这部分知识&#xff0c;将不明白的问题总结出来&#xff0c;并通过AI进行问答帮助我进行学习巩固。本人能力有限 &#xff0c;可能有些内容不准确&#xff0c;望各位大佬海涵&am…

【前端开发入门】css快速入门

目录 引言一、css盒模型1. 盒模型概念2. 盒模型案例 二、css编写1. html文件内部编写1.1 标签style属性编写1.2 css选择器关联1.2.1 id选择器1.2.2 class选择器1.2.3 标签选择器1.2.4 css选择器作用域1.2.5 其他选择器1.2.6 各css选择器优先级 2. 单独维护css文件2.1 创建css文…

HDLBits中文版,标准参考答案 | 3.1.4 Karnaugh Map to Circuit | 卡诺图到电路

关注 望森FPGA 查看更多FPGA资讯 这是望森的第 11 期分享 作者 | 望森 来源 | 望森FPGA 目录 1 3 变量 2 4 变量 3 4 变量 4 4 变量 5 最小 SOP 和 POS 6 卡诺图 7 卡诺图 8 使用多路复用器实现的卡诺图 本文中的代码都能够正常运行&#xff0c;请放心食用&#x1f…

最强AI绘画大模型Flux可以在SDWebUI 上使用了!超便捷的Flux模型使用教程!AI绘画零基础入门到实战教程

大家好&#xff0c;我是画画的小强 目前最强的AI绘画大模型Flux.1 横空出世有段时间了&#xff0c;模型效果也得到了广泛的认可&#xff0c;但是 Stable Diffusion WebUI 官方迟迟没有跟进&#xff0c;据说是因为要修改很多底层的处理机制&#xff0c;加之ComfyUI如火如荼&…

Nginx的基础讲解之重写conf文件

一、Nginx 1、什么是nginx&#xff1f; Nginx&#xff08;engine x&#xff09;是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。 2、用于什么场景 Nginx适用于各种规模的网站和应用程序&#xff0c;特别是需要高并发处理和负载均衡的场…

【React】事件机制

事件机制 react 基于浏览器的事件机制自身实现了一套事件机制&#xff0c;称为合成事件。比如&#xff1a;onclick -> onClick 获取原生事件&#xff1a;e.nativeEvent onClick 并不会将事件代理函数绑定到真实的 DOM节点上&#xff0c;而是将所有的事件绑定到结构的最外层…

Pikachu-目录遍历

目录遍历&#xff0c;跟不安全文件上传下载有差不多&#xff1b; 访问 jarheads.php 、truman.php 都是通过 get 请求&#xff0c;往title 参数传参&#xff1b; 在后台&#xff0c;可以看到 jarheads.php 、truman.php所在目录&#xff1a; /var/www/html/vul/dir/soup 图片…

master节点k8s部署]33.ceph分布式存储(四)

总结ceph分布式存储&#xff08;三&#xff09;中提到的三种方法&#xff1a; 1.创建rbda&#xff0c;并且在创建pv的时候配置该rbda,以下代码仅展示关键信息。 [rootxianchaomaster1 ~]# cat pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: ceph-pv ...…

【每日一题 | 24.10.7】Fizz Buzz 经典问题

1. 题目2. 解题思路3. 代码实现&#xff08;AC_Code&#xff09; 个人主页&#xff1a;C_GUIQU 归属专栏&#xff1a;每日一题 1. 题目 Fizz Buzz 经典问题 2. 解题思路 【法1】逻辑硬解&#xff1a;按照题目逻辑分四种情况&#xff0c;用if else 判断即可。 【法2】switc…

Win10鼠标总是频繁自动失去焦点-非常有效-重启之后立竿见影

针对Win10鼠标频繁自动失去焦点的问题&#xff0c;可以尝试以下解决方案&#xff1a; 一、修改注册表&#xff08;最有效的方法-重启之后立竿见影&#xff09; 打开注册表编辑器&#xff1a; 按下WindowsR组合键&#xff0c;打开运行窗口。在运行窗口中输入“regedit”&#x…

VMware ESXi 7.0U3q macOS Unlocker OEM BIOS 2.7 Dell HPE 联想定制版 9 月更新发布

VMware ESXi 7.0U3q macOS Unlocker & OEM BIOS 2.7 Dell HPE 联想定制版 9 月更新发布 VMware ESXi 7.0U3q macOS Unlocker & OEM BIOS 2.7 标准版和厂商定制版 ESXi 7.0U3 标准版&#xff0c;Dell (戴尔)、HPE (慧与)、Lenovo (联想)、Inspur (浪潮)、Cisco (思科)…

探索二叉树的奇幻世界:解密二叉树的结构与遍历

文章目录 目录 一、二叉树的基本操作 1.1 获取树中节点的个数 1.2 获取叶子节点的个数 1.3 获取第K层节点的个数 1.4 获取二叉树的高度 二、二叉树相关习题 2.1 检查两颗树是否相同 2.2 另一颗树的子树 2.3 翻转二叉树 2.4 判断一颗二叉树是否是平衡二叉树 一、二…

封装el-upload组件,用于上传图片和视频

使用环境 vue3element-ui plus 需要根据后端返回结构修改的函数&#xff1a;onPreview onRemove onSuccess 组件使用 基本使用 源代码&#xff1a; <script setup> import AutoUploadFile from /components/auto-upload-file/index.vue function change(urls){console.…

Html批量转word工具2.1

2024年10月7日记录&#xff1a; 有客户反馈&#xff0c;2.0刚运行就提示转换完成 有问题就解决。正好国庆假期这几天有空&#xff0c;2.1版就出炉了。 2.1 更新记录&#xff1a; 修复了1个bug&#xff1a;刚运行就提示转换完成 下载地址&#xff1a;Html 转 word 批量处理工具…