当前位置: 首页 > news >正文

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更新时,会根据具体的属性显示对应的样式。

相关文章:

Android SystemUI组件(11)SystemUIVisibility解读

该系列文章总纲链接&#xff1a;专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节持续迭代之前章节思维导图&#xff0c;主要关注左侧最上方SystemUiVisibility解读部分即可。 本章节主要讲解SystemUiVisibility的概念及其相…...

JSON 全知全解:深入探索 JSON 的奥秘

目录 一、JSON 基础认知&#xff08;一&#xff09;JSON 的定义与历史&#xff08;二&#xff09;JSON 的语法规则&#xff08;三&#xff09;JSON 与 JS 对象的关系 二、JSON 在不同语言中的用法&#xff08;一&#xff09;JavaScript 中的 JSON 操作&#xff08;二&#xff0…...

CSS | 响应式布局之媒体查询(media-query)详解

media type(媒体类型)是CSS 2中的一个非常有用的属性&#xff0c;通过media type我们可以对不同的设备指定特定的样式&#xff0c;从而实现更丰富的界面。media query(媒体查询)是对media type的一种增强&#xff0c;是CSS 3的重要内容之一。随着移动互联网的发展&#xff0c;m…...

并查集的模拟实现

简化版本 class UnionFindSet { public:UnionFindSet(size_t n):_ufs(n, -1){}void Union(int x1, int x2){int root1 FindRoot(x1);int root2 FindRoot(x2);// 如果本身就在一个集合就没必要合并了if (root1 root2)return;// 控制数据量小的往大的集合合并if (abs(_ufs[roo…...

如何高效删除 MySQL 日志表中的历史数据?实战指南

在处理高并发的物联网平台或者其他日志密集型应用时&#xff0c;数据库中的日志表往往会迅速增长&#xff0c;数据量庞大到数百GB甚至更高&#xff0c;严重影响数据库性能。如何有效管理这些庞大的日志数据&#xff0c;特别是在不影响在线业务的情况下&#xff0c;成为了一项技…...

请散户股民看过来,密切关注两件大事

明天股市要开市&#xff0c;不仅散户股民期盼节后股市大涨&#xff0c;上面也同样想在节后来上一个“开门红”。 为此&#xff0c;上面没休假&#xff0c;关起门来办了两件大事&#xff0c;这两天发布消息已提前预热了。 两件大事如下&#xff1a; 一是&#xff0c;上交所10…...

设计模式之外观模式(Facade)

一、外观模式介绍 外观模式( Facade Pattern)&#xff0c;也叫门面模式&#xff0c;是一个 “结构型” 设计模式。 外观模式的原始定义是&#xff1a;为子系统中的一组接口提供统一的接口。它定义了一个更高级别 的接口&#xff0c;使子系统更易于使用。 外观模式&#xff0c;是…...

解锁 Python 嵌套字典的奥秘:高效操作与实战应用指南

文章目录 前言&#x1f340;一、 什么是 Python 字典&#xff1f;1.1 字典的语法 &#x1f340;二、 字典的基本操作2.1 字典的创建2.2 访问字典中的值2.3 添加或修改键值对2.4 删除字典中的键值对 &#x1f340;三、 字典的遍历操作3.1 遍历字典的键3.2 遍历字典的值3.3 同时遍…...

联想服务器配置阵列、安装操作系统

文章目录 [toc]1.配置阵列2.制作启动盘3.安装系统 1.配置阵列 1.根据提示进入BIOS设置&#xff08;F1&#xff09; 2.系统设置 3.存储 4.第四步可以看到raid卡信息 5.Main Menu 6.Configuration Management 7.Create Virtual Drive 8.Select RAID Level raid5 9.Select Drives…...

【深度强化学习】DDPG实现的4个细节(OUNoise等)

文章目录 前言一、论文内容简述创新点&#xff08;特点&#xff0c;与DQN的区别&#xff09;&#xff1a;可借鉴参数&#xff1a;细节补充&#xff1a; 二、细节1&#xff1a;weight_decay原理代码 三、细节2&#xff1a;OUNoise原理代码 四、细节3&#xff1a;ObsNorm原理代码…...

算法工程师重生之第二十二天(递增子序列 全排列 全排列 II 重新安排行程 N皇后 解数独 总结 )

参考文献 代码随想录 一、非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作…...

css的选择器及优先级

一、css选择器 CSS选择器是用来选择HTML文档中的元素&#xff0c;并为它们应用样式规则的工具。CSS选择器有很多种&#xff0c;可以根据元素的类名、ID、属性、伪类、伪元素、标签等来选择元素。以下是一些常见的CSS选择器及其用法&#xff1a; 1. ID选择器&#xff1a; 根据…...

JavaScript中的数组不改变原数组的方法

数组 var a [1, 2, 3, 5, 8, 13, 21] 不改变原数组的方法 length 数组元素的长度 继承自原型 concat(arrayX,arrayY) 合并两个或多个数组&#xff0c;返回新数组 合并&#xff0c;a.concat(b) var a[1,2,3],b[4,5,6],c[7,8,9]; a.concat(b,c); //[1, 2, 3, 4, 5, 6, 7…...

Go语言实现长连接并发框架 - 路由分组

文章目录 前言接口结构体接口实现项目地址最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;我们上篇博客实现了任务执行流的路由模块&#xff0c;接下来我们实现一下对任务执行流进行任务 接口 trait/router_group.go type RouterGroup interface {RouterGroup(flow ..…...

跨 VLAN 通信

跨 VLAN 通信指的是不同 VLAN 之间的网络设备进行数据交换的能力。由于 VLAN 将网络分割成多个逻辑隔离的广播域&#xff0c;默认情况下&#xff0c;不同 VLAN 之间的设备无法直接通信。为了实现跨 VLAN 通信&#xff0c;需要借助一些网络设备和技术。以下详细讲解跨 VLAN 通信…...

11.4 Linux_线程_条件变量

概述 条件变量的作用&#xff1a; 条件变量和互斥量配合使用&#xff0c;主要应用于生产者和消费者问题。 这种问题也是一种临界资源的问题&#xff0c;但与互斥量一文中 "写文件" 这种资源不同。文件是一直存在的临界资源&#xff0c;而生产者的资源不是一直存在…...

通信工程学习:什么是IP网际协议

IP&#xff1a;网际协议 IP网际协议&#xff08;Internet Protocol&#xff0c;简称IP&#xff09;是整个TCP/IP协议栈中的核心协议之一&#xff0c;它负责在网络中传送数据包&#xff0c;并提供寻址和路由功能。以下是对IP网际协议的详细解释&#xff1a; 一、对IP网际协议的…...

github 国内文件加速下载

参看;https://www.cnblogs.com/ting1/p/18356265 在源网址前加上 https://hub.gitmirror.com/ 或https://mirror.ghproxy.com/&#xff0c;例如&#xff1a; https://hub.gitmirror.com/https://github.com/t1m0thyj/WinDynamicDesktop/releases/download/v5.4.1/WinDynamicD…...

算法6:模拟运算

文章目录 z字形变幻外观数列数青蛙 题目均来自于力扣 z字形变幻 class Solution { public:string convert(string s, int numRows) {int n s.size();if(n < numRows || numRows 1) return s;int d 2 * numRows - 2;string res;for(int j 0; j < n; j d){res s[j]; …...

【网络协议大花园】应用层 http协议的使用小技巧,用好了都不用加班,效率翻两倍(上篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

Win系统权限提升篇UAC绕过DLL劫持未引号路径可控服务全检项目

应用场景&#xff1a; 1、常规某个机器被钓鱼后门攻击后&#xff0c;我们需要做更高权限操作或权限维持等。 2、内网域中某个机器被钓鱼后门攻击后&#xff0c;我们需要对后续内网域做安全测试。 #Win10&11-BypassUAC自动提权-MSF&UACME 为了远程执行目标的exe或者b…...