Android SystemUI组件(11)SystemUIVisibility解读

news2024/10/4 6:09:11

该系列文章总纲链接:专题分纲目录 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方法,对应的代码实现如下所示:

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

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

//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方法代码实现如下:

//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)。代码实现如下:

//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 可见性发生变化时,分发这些变化通知给视图树中的所有相关视图。这些变化可能包括状态栏和导航栏的显示或隐藏,以及它们的外观(如颜色、图标颜色等)。代码实现如下:

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

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

//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代码实现如下所示:

//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方法代码如下所示:

//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方法代码如下所示:

//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方法代码实现如下:

//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方法代码实现如下:

//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方法。具体实现如下:

//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初始化时进行赋值的,代码如下所示:

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

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

//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方法实现,代码如下:

//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方法的实现,代码如下:

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

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

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

相关文章

【EXCEL数据处理】000015案例 EXCEL公式与基础函数

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000015案例 EXCEL公式与基础函数。使用的软件&#x…

基础岛第3关:浦语提示词工程实践

模型部署 使用下面脚本测试模型 from huggingface_hub import login, snapshot_download import osos.environ[HF_ENDPOINT] https://hf-mirror.comlogin(token“your_access_token")models ["internlm/internlm2-chat-1_8b"]for model in models:try:snapsh…

Java--IO基本流

IO流 概述 生活中&#xff0c;你肯定经历过这样的场景。当你编辑一个文本文件&#xff0c;忘记了ctrls &#xff0c;可能文件就白白编辑了。当你电脑上插入一个U盘&#xff0c;可以把一个视频&#xff0c;拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢&#xff1f;键盘…

昇思MindSpore进阶教程--二阶优化器THOR

大家好&#xff0c;我是刘明&#xff0c;明志科技创始人&#xff0c;华为昇思MindSpore布道师。 技术上主攻前端开发、鸿蒙开发和AI算法研究。 努力为大家带来持续的技术分享&#xff0c;如果你也喜欢我的文章&#xff0c;就点个关注吧 正文介绍 深度学习训练过程可以看成损失…

Neo4j CQL语句 使用教程

CREATE命令 : CREATE (<node-name>:<label-name>{ <Property1-name>:<Property1-Value>........<Propertyn-name>:<Propertyn-Value>} )字段说明 CREATE (dept:Dept { deptno:10,dname:“Accounting”,location:“Hyderabad” })&#…

ATLAS/ICESat-2 L3B 每 3 个月网格动态海洋地形图 V001

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 ATLAS/ICESat-2 L3B Monthly 3-Month Gridded Dynamic Ocean Topography V001 ATLAS/ICESat-2 L3B 每月 3 个月网格动态海洋地形图 V001 简介 该数据集包含中纬度、北极和南极网格上动态海洋地形&…

[Offsec Lab] ICMP Monitorr-RCE+hping3权限提升

信息收集 IP AddressOpening Ports192.168.52.218TCP:22,80 $ nmap -p- 192.168.52.218 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.9p1 Debian 10deb10u2 (protocol 2.0) | ssh-hostkey: | 2048 de:b5:23:89:bb:9f:d4:1…

Kubernetes-Kind篇-01-kind搭建测试集群

1、Kind 介绍 官方文档地址&#xff1a;https://kind.sigs.k8s.io/ github仓库地址&#xff1a;https://github.com/kubernetes-sigs/kind 国内镜像仓库地址&#xff1a;https://gitcode.com/gh_mirrors/ki/kind/overview kind 是一种使用 Docker 容器 nodes 运行本地 Kubern…

算法日记-链表翻转

文章目录 场景&#xff1a;解法一&#xff1a;迭代步骤完整代码 解法二&#xff1a;递归步骤完整代码 重温力扣常规算法&#xff0c;记录算法的演变&#xff0c;今天介绍的是链表翻转 场景&#xff1a; 现在有一条单项链表&#xff0c;链表节点存在一个数据和指向下一个节点的…

MySQL--三大范式(超详解)

目录 一、前言二、三大范式2.1概念2.2第一范式&#xff08;1NF&#xff09;2.3第二范式&#xff08;2NF&#xff09;2.3第三范式&#xff08;3NF&#xff09; 一、前言 欢迎大家来到权权的博客~欢迎大家对我的博客进行指导&#xff0c;有什么不对的地方&#xff0c;我会及时改进…

AI不可尽信

看到某项目有类似这样的一段代码 leaves : make([]int, 10) leaves leaves[:0]没理解这样的连续两行,有何作用? 初始化一个长度和容量都为10的切片,接着把切片长度设置为0 即如下demo: (在线地址) package mainimport "fmt"func main() {leaves : make([]int, 1…

【2023工业3D异常检测文献】CPMF: 基于手工制作PCD描述符和深度学习IAD结合的AD方法

Complementary Pseudo Multimodal Feature for Point Cloud Anomaly Detection 1、Background 早期的点云异常检测(PCD)表示是手工制作的&#xff0c;依赖于启发式设计。随着深度学习的发展&#xff0c;最近的方法采用了基于学习的PCD特征。尽管与基线相比有相当大的改进&…

欧几里得算法--(密码学基础)

根基&#xff1a;gcd(a,b)gcd(b,a mod b) 先举个例子吧&#xff0c;gcd(16,6)gcd(6,4)gcd(4,2)gcd(2,0)2 学习这个定理的时候我想了几个问题. 第一个问题&#xff1a;为什么求出的就一定是他们两个数的公约数&#xff1f; 这个问题很简单我们只需要通过几何来计较即可&#x…

MyBatis——ORM

MyBatis——ORM 验证映射配置ResultType本质是ResultMap具体的转换逻辑 概括的说&#xff0c;MyBatis中&#xff0c;对于映射关系的声明是由开发者在xml文件手动完成的。比如对查询方法而言&#xff0c;你需要显式声明ResultType或ResultMap&#xff0c;这里其实就是在定义数据…

Java JUC(三) AQS与同步工具详解

Java JUC&#xff08;三&#xff09; AQS与同步工具详解 一. ReentrantLock 概述 ReentrantLock 是 java.util.concurrent.locks 包下的一个同步工具类&#xff0c;它实现了 Lock 接口&#xff0c;提供了一种相比synchronized关键字更灵活的锁机制。ReentrantLock 是一种独占…

【Kubernetes】常见面试题汇总(五十三)

目录 118. pod 状态为 ErrlmagePull &#xff1f; 119.探测存活 pod 状态为 CrashLoopBackOff &#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。…

uniapp使用字体图标 ttf svg作为选项图标,还支持变色变图按

在staic目录下有一些ttf文件&#xff0c;如uni.ttf&#xff0c;iconfont.ttf 这些文件中保存这字体svg的源码们&#xff0c;我们也可以在网上找其他的。这些就是我们要显示的突图标的 显示来源。这样不用使用png图标&#xff0c;选中不选中还得用两个图片 我的具体使用如下 &q…

Python入门--循环语句

目录 1. while循环基础语法 2. while循环的嵌套 3. while实现九九乘法表 4. for循环基础语法 5. for循环的嵌套 6. for循环实现九九乘法表 7. 循环中断&#xff1a;break和continue 循环普遍存在于日常生活中&#xff0c;同样&#xff0c;在程序中&#xff0c;循环功能也…

thinkphp6入门(25)-- 分组查询 GROUP_CONCAT

假设表名为 user_courses&#xff0c;字段为 user_id 和 course_name&#xff0c;存储每个用户选修的课程&#xff0c;想查询每个学生选修的所有课程 SQL 原生查询 SELECT user_id, GROUP_CONCAT(course_name) as courses FROM user_courses GROUP BY user_id; ThinkPHP 代码…

python常用库总结(argparse、re、matlpotlab.plot)

文章目录 1.argparse库字符串&#xff08;str&#xff09;布尔值&#xff08;bool&#xff09;选择&#xff08;choices&#xff09;计数&#xff08;count&#xff09;常量&#xff08;store_const 和 store_true&#xff09;多个值&#xff08;nargs&#xff09;可选参数&…