该系列文章总纲链接:专题分纲目录 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个关键的方法调用:
- collectViewAttributes方法:重新计算最新的SystemUIVisibility属性。
- 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更新时,会根据具体的属性显示对应的样式。