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中隐藏状态栏:

java 复制代码
@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的实现,对应的代码实现如下所示:

java 复制代码
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方法,对应的代码实现如下所示:

java 复制代码
//ViewGroup
    public void recomputeViewAttributes(View child) {
        if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
            ViewParent parent = mParent;
            if (parent != null) parent.recomputeViewAttributes(this);
        }
    }

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

java 复制代码
//ViewRootImpl
	//...
	//关键流程 step1
	@Override
	public void recomputeViewAttributes(View child) {
		checkThread(); // 确保该方法在UI线程中调用
		if (mView == child) {
			mAttachInfo.mRecomputeGlobalAttributes = true; // 设置标志,表示需要重新计算全局属性
			if (!mWillDrawSoon) { // 如果当前没有即将进行的绘制操作
				scheduleTraversals(); // 调度绘制流程,以便更新视图
			}
		}
	}
	//...
	//关键流程 step2
	void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
			//等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
        }
    }
	//...
	//关键流程 step3
	final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
	//...
	//关键流程 step4
	void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
            try {
				//执行performTraversals
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
	//...
	//关键流程 step5
    private 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的属性,判断是否需要更新params
        if (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方法代码实现如下:

java 复制代码
//ViewRootImpl
	private boolean collectViewAttributes() {
		// 检查是否需要重新计算全局属性
		if (mAttachInfo.mRecomputeGlobalAttributes) {
			// 重置全局属性重新计算标志
			mAttachInfo.mRecomputeGlobalAttributes = false;

			// 保存旧的屏幕保持状态
			boolean oldScreenOn = mAttachInfo.mKeepScreenOn;
			
			// 初始化屏幕保持标志为false
			mAttachInfo.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)。代码实现如下:

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

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

2.2.2 dispatchWindowSystemUiVisiblityChanged方法分析

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

java 复制代码
//View
	//...
	//关键流程 step1
	public void dispatchWindowSystemUiVisiblityChanged(int visible) {
        onWindowSystemUiVisibilityChanged(visible);
    }
	//关键流程 step2
    public void onWindowSystemUiVisibilityChanged(int visible) {
    
	}

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

java 复制代码
//PhoneWindow
	//DecorView
	    @Override
        public 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代码实现如下所示:

java 复制代码
//ViewRootImpl
    private 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方法代码如下所示:

java 复制代码
//Session
    public 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方法代码如下所示:

java 复制代码
//WindowManagerService
	//...
	//关键流程step1
    public 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);
    }
	//...
	//关键流程step2
    private 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;
		}
		// 如果焦点没有变化,返回false
		return false;
	}

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

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

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

java 复制代码
//PhoneWindowManager
	//...
	//关键流程 step1
    @Override
    public int focusChangedLw(WindowState lastFocus, WindowState newFocus) {
        mFocusedWindow = newFocus;
        if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) {
            return FINISH_LAYOUT_REDO_LAYOUT;
        }
        return 0;
    }
	//...
	//关键流程 step2
	private 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() {
				@Override
				public 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方法代码实现如下:

java 复制代码
//StatusBarManagerService
    private volatile IStatusBar mBar;
    //...
    //关键流程 step1
	public 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");
        }
    }
	//...
    //关键流程 step2
	private 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方法。具体实现如下:

java 复制代码
//CommandQueue
	//...
	//关键流程 step1
	public 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初始化时进行赋值的,代码如下所示:

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

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

java 复制代码
//BaseStatusBar
    public 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方法实现,代码如下:

java 复制代码
//PhoneStatusBar
	//...
	//关键流程 step1
	public 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);
		}
	}
	//...
	//关键流程 step2
	private void notifyUiVisibilityChanged(int vis) {
        try {
            mWindowManagerService.statusBarVisibilityChanged(vis);
        } catch (RemoteException ex) {
        }
    }

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

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

java 复制代码
//WindowManagerService
	//...
	//关键流程 step1
	public void statusBarVisibilityChanged(int visibility) {
		//...
        synchronized (mWindowMap) {
            mLastStatusBarVisibility = visibility;
			//调整可见性
            visibility = mPolicy.adjustSystemUiVisibilityLw(visibility);
			//更新窗口可见性
            updateStatusBarVisibilityLocked(visibility);
        }
    }
	//...
	//关键流程 step2
	void 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更新时,会根据具体的属性显示对应的样式。

相关推荐
Dnelic-3 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen5 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年12 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿15 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神16 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛16 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法17 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter18 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快19 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl19 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5