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.rhkb.cn/news/437870.html

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

相关文章

LeetCode从入门到超凡(四)深入浅出理解贪心算法

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的LeetCode学习总结文档&#xff1b;本文主要讲解贪心算法。&#x1f495;&#x1f495;&#x1f60a; 介绍 贪心算法是一种经典的算法…

《中国电子报》报道: 安宝特AR为产线作业者的“秘密武器

近日&#xff0c;中国电子报在其文章《下一代工业智能终端重新定义制造业》中对安宝特的增强现实&#xff08;AR&#xff09;解决方案给予了高度评价&#xff0c;称其为产线作业者的“秘密武器”。这一创新技术改变了传统制造业的作业方式&#xff0c;使得操作人员能够在生产过…

Java中Map和Set详细介绍,哈希桶的实现

大家好呀&#xff0c;前一节我们接触了二叉搜索树&#xff0c;那么紧接着&#xff0c;我们要学习一种十分重要而且也是我们在初阶数据结构中接触的最后一种数据结构—Map和Set&#xff0c;本篇博客将会详细介绍两种数据结构&#xff0c;并且针对哈希表底层实现一个哈希桶&#…

从0到1深入浅出构建Nest.Js项目

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力&#xff0c;使用并完全支持 TypeScript &#xff08;仍然允许开发者使用纯 JavaScript 进行开发&#xff09;&#xff0c;并结合了 OOP &#xff08;面向对…

Java的学习(语法相关)

字符串存储的问题 char 和字符串都是字符的集合&#xff0c;它们之间的确有相似性&#xff0c;但在 Java 中它们有着不同的存储机制和处理方式。让我从 char 和 String 的本质区别入手来解释。 1. char 和 String 的区别 char 是基本类型&#xff1a;char 是 Java 中的基本数据…

Java-数据结构-Map和Set(三)-习题 o(´^`)o

目录 ❄️一、习题一(只出现一次的数字)&#xff1a; ❄️二、习题二(随机链表的复制)&#xff1a; ❄️三、习题三(宝石与石头)&#xff1a; ❄️四、习题四(旧键盘)&#xff1a; ❄️五、习题五(前k个高频单词)&#xff1a; ❄️总结&#xff1a; ❄️一、习题一(只出现一…

Python(三)——列表

文章目录 创建列表访问下标遍历列表元素新增元素查找元素删除元素连接列表切片操作 创建列表 创建列表主要有两种方式 [ ]表示一个空的列表 a [] print(type(a)) # <class list> print(a) # []通过list()的方式来创建一个空列表 a list() print(type(a)) # …

Java对象头

一、对象在堆内存中的布局 1.定义 在HotSpot虚拟机中&#xff0c;对象在堆内存的存储布局可以划分为三个部分&#xff1a;对象头&#xff08;Header&#xff09;、实例数据&#xff08;Instance Data&#xff09;、和对齐填充&#xff08;Paddin&#xff09;。 二、对象在堆内…

Rstudio:强大的R语言集成开发环境(IDE)

Rstudio 应该是 R 语言使用的标配&#xff0c;尽管 Rstudio 的母公司 Posit 推出了新一代的集成开发环境 Positron&#xff0c;但其还处于开发阶段。作为用户不妨让其成熟后再使用&#xff0c;现阶段还是 Rstudio 更稳定。 如果你在生物信息学或统计学领域工作&#xff0c;R语言…

【springboot】整合沙箱支付

目录 1. 配置沙箱应用环境2. 配置springboot项目1. 引入依赖2. 配置文件注册下载ngrok 3. 创建支付宝支付服务类4. 支付界面模板5. 控制类实现支付6. 测试 1. 配置沙箱应用环境 使用支付宝账号登录到开放平台控制台。 使用支付宝登录后&#xff0c;看到以下页面&#xff0c;下…

动态内存分配

1. 基本使用 在内存空间中&#xff0c;我们如何做到想用多少内存空间就申请多少内存空间&#xff1f; 使用以下函数可以实现&#xff1a; 如何利用malloc申请一片连续的内存空间&#xff1a; int* p malloc(100 * sizef(int)); 该代码实现了&#xff0c;申请一片空间&#…

VS开发 - 静态编译和动态编译的基础实践与混用

目录 1. 基础概念 2. 直观感受一下静态编译和动态编译的体积与依赖项目 3. VS运行时库包含哪些主要文件&#xff08;从VS2015起&#xff09; 4. 动态库和静态库混用的情况 5. 感谢清单 1. 基础概念 所谓的运行时库&#xff08;Runtime Library&#xff09;就是WINDOWS系统…

828华为云征文|WordPress部署

目录 前言 一、环境准备 二、远程连接 三、WordPress简介 四、WordPress安装 1. 基础环境安装 ​编辑 2. WordPress下载与解压 3. 创建站点 4. 数据库配置 总结 前言 WordPress 是一个非常流行的开源内容管理系统&#xff08;Content Management System, CMS&#xf…

进度条(倒计时)Linux

\r回车(回到当前行开头) \n换行 行缓冲区概念 什么现象&#xff1f; 什么现象&#xff1f;&#xff1f; 什么现象&#xff1f;&#xff1f;&#xff1f; 自己总结&#xff1a; #pragma once 防止头文件被重复包含 倒计时 在main.c中&#xff0c;windows.h是不可以用的&…

CleanMyMac X v4.12.1 中文破解版 Mac优化清理工具

在数字时代&#xff0c;我们的Mac设备承载着越来越多的重要信息和日常任务。然而&#xff0c;随着时间的推移&#xff0c;这些设备可能会变得缓慢、混乱&#xff0c;甚至充满不必要的文件。这就是CleanMyMac X发挥作用的地方。 CleanMyMac X是一款功能强大的Mac优化工具&#…

Python 从入门到实战32(数据库MySQL)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库编程接口操作的相关知识。今天我们将学习…

CSP-J Day 3 模拟赛补题报告

姓名&#xff1a;王胤皓&#xff0c;校区&#xff1a;和谐校区&#xff0c;考试时间&#xff1a; 2024 2024 2024 年 10 10 10 月 3 3 3 日 9 : 00 : 00 9:00:00 9:00:00~ 12 : 30 : 00 12:30:00 12:30:00&#xff0c;学号&#xff1a; S 07738 S07738 S07738 请关注作者的…

[20241003] 狂飙500天,国产大模型如何突破商业化之困?

大模型加速狂飙&#xff0c;AI商业化却面临巨大鸿沟。 一方面&#xff0c;传统企业不知道怎么将AI融入原始业务&#xff0c;另一方面&#xff0c;AI企业难以找到合适的变现方式。AI企业究竟该如何突破商业化之困&#xff1f;B端和C端&#xff0c;呈现出两种不同的路径。 纵…

Pikachu-暴力破解-验证码绕过(on client)

访问页面&#xff0c; 从burpsuite 上看到返回的源代码&#xff1b; 验证码生成时通过 createCode 方法生成&#xff0c;在前端页面生成&#xff1b; 同时也是在前端做的校验&#xff1b; 直接验证&#xff1b;F12 -- 网络&#xff0c;随便输入个账号、密码、验证码&#xff0…

OceanBase—02(入门篇——对于单副本单节点,由1个observer扩容为3个observer集群)——之前的记录,当初有的问题未解决,目前新版未尝试

OceanBase—02&#xff08;入门篇——对于单副本单节点&#xff0c;由1个observer扩容为3个observer集群&#xff09;——之前的记录&#xff0c;有的问题未解决&#xff0c;新版未尝试 1、前言—安装单副本单节点集群1.1 docker安装OB 2、查看现有集群情况2.1 进入容器&#x…