前言
在Android系统中,很多应用都需要根据具体情况来控制状态栏和导航栏的显示和隐藏,又或者将状态栏透明,实现诸如沉浸式、全面屏灯效果,而要实现这些效果,都离不开SystemUIVisibility属性。由于SystemUIVisibilityy属性主要用来控制系统状态栏和导航栏的行为,而状态栏和导航栏都属于SystemUI模块的StatusBar,所以SystemUIVisibility属性的消费者肯定包含StatusBar。另外当状态栏和导航栏发生变化的时候,窗口的布局一般也会跟着发生变化,这就意味着窗口管理者PhoneWindowManager肯定也要消费SystemUIVisibility属性。本篇文章我们就来具体分析一下和这个属性有关的代码。
一、SystemUIVisibility属性常见常见取值
1、为窗口设置SystemUIVisibility属性的方式有两种,一种是直接在窗口的WindowManager.LayoutParams对象的systemUiVisibility属性上进行设置,并通过WindowManager.updateViewLayout()方法使其生效。
frameworks/base/core/java/android/view/WindowManager.java
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//隐藏窗口的所有装饰,比如状态栏和导航栏
public static final int FLAG_FULLSCREEN = 0x00000400;
//控制窗口状态栏、导航栏的显示和隐藏
public int systemUiVisibility;
}
}
2、另一种是在一个已经显示在窗口上的控件中调用setSystemUiVisibility方法,传入如下属性。
frameworks/base/core/java/android/view/View.java
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
//隐藏导航栏
public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
//隐藏状态栏
public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
}
这种方式最终影响的其实是窗口的WindowManager.LayoutParams对象的subtreeSystemUiVisibility属性。
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//控制窗口状态栏、导航栏的显示和隐藏
public int subtreeSystemUiVisibility;
}
}
窗口的状态栏导航栏显示与否,最终其实是受以上两个属性共同影响的。接下来我们具体来分析一下View的setSystemUiVisibility方法是如何生效的。
二、View的setSystemUiVisibility方法调用流程
1、View的setSystemUiVisibility方法如下所示。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
int mSystemUiVisibility;
protected ViewParent mParent;
public void setSystemUiVisibility(int visibility) {
if (visibility != mSystemUiVisibility) {
mSystemUiVisibility = visibility;//保存SystemUIVisibility属性
if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
mParent.recomputeViewAttributes(this);//通知父控件子控件属性发生了变化
}
}
}
}
setSystemUiVisibility方法首先将属性赋值给mSystemUiVisibility,然后会调用父控件的recomputeViewAttributes方法,通知父控件子控件属性发生了变化。ViewParent是一个接口,在Android中有两个类实现了这个接口,它们分别是ViewGroup和ViewRootImpl。
2、ViewGroup和ViewRootImpl和recomputeViewAttributes方法相关的代码如下所示。
frameworks/base/core/java/android/view/View.java
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public void recomputeViewAttributes(View child) {
if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
ViewParent parent = mParent;
if (parent != null) parent.recomputeViewAttributes(this);
}
}
}
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
final View.AttachInfo mAttachInfo;
@Override
public void recomputeViewAttributes(View child) {
checkThread();//检测线程是不是UI线程
if (mView == child) {
mAttachInfo.mRecomputeGlobalAttributes = true;//标记需要重新计算本地属性
if (!mWillDrawSoon) {
scheduleTraversals();//进一步调用scheduleTraversals方法。
}
}
}
}
结合Android 9.0系统源码_窗口管理(二)WindowManager对窗口的管理过程,我们知道包括Activity的跟布局DecorView在内的任何View,WindowManager在将它添加到窗口上的过程中,最终都会创建一个ViewRootImpl,并将View设置给ViewRootImpl,这样根View的父类就变成了ViewRootImpl。这就意味着不管任何子View调用recomputeViewAttributes方法,最终所触发的都是ViewRootImpl的recomputeViewAttributes,而ViewRootImpl会进一步调用scheduleTraversals方法。
3、ViewRootImpl和scheduleTraversals方法相关的代码如下所示。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
final Choreographer mChoreographer;//编舞者
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();//回调对象
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();//继续执行doTraversal
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//执行performTraversals
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
}
scheduleTraversals方法会为编舞者对象设置回调,最终会等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法,该方法会调用doTraversal方法,然后进一步调用performTraversals方法。
4、ViewRootImpl的performTraversals方法代码逻辑非常多,这里只列出了我们需要关注的代码。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
final IWindowSession mWindowSession;//和WMS通信的Binder对象,具体为Session对象
public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
final View.AttachInfo mAttachInfo;//控件信息
private void performTraversals() {
final View host = mView;
if (host == null || !mAdded) {
return;
}
if (mWaitForBlastSyncComplete) {
mRequestedTraverseWhilePaused = true;
return;
}
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
WindowManager.LayoutParams lp = mWindowAttributes;//将当前窗口的最新属性赋值给lp
int desiredWindowWidth;
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
|| mAppVisibilityChanged);
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
WindowManager.LayoutParams params = null;
...代码省略...
//收集mView的属性,判断是否需要更新params
if (collectViewAttributes()) {
params = lp;
}
...代码省略...
//此方法最终会触发WindowManagerService的relayoutWindow方法
relayoutWindow(params, viewVisibility, insetsPending);
...代码省略...
//测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...代码省略...
//布局
performLayout(lp, mWidth, mHeight);
...代码省略...
//绘制
performDraw();
...代码省略...
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
//调用IWindowSession的relayout方法,该方法最终会触发WindowManagerService的relayoutWindow方法
int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
}
}
performTraversals首先调用collectViewAttributes方法收集所有子View的属性,然后调用relayoutWindow方法,该方法最终会触发WindowManagerService的relayoutWindow方法,然后回继续调用触发View测量的performMeasure方法,触发View布局的performLayout方法和触发View绘制的performDraw方法。
三、获取最新的SystemUIVisibility属性
1、ViewRootImpl的collectViewAttributes方法是一个很关键的方法,此方法会重新计算最新的SystemUIVisibility属性。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
final View.AttachInfo mAttachInfo;//控件信息
private boolean collectViewAttributes() {
//判断是否需要重新计算本地属性
if (mAttachInfo.mRecomputeGlobalAttributes) {
//Log.i(mTag, "Computing view hierarchy attributes!");
mAttachInfo.mRecomputeGlobalAttributes = false;
boolean oldScreenOn = mAttachInfo.mKeepScreenOn;
mAttachInfo.mKeepScreenOn = false;
//清空已经存在的SystemUiVisibility属性
mAttachInfo.mSystemUiVisibility = 0;
mAttachInfo.mHasSystemUiListeners = false;
//重新获取窗口视图mView最新的的SystemUI属性,赋值给mAttachInfo
mView.dispatchCollectViewAttributes(mAttachInfo, 0);
...代码省略...
}
return false;
}
}
collectViewAttributes首先会清空当前窗口视图mView已经存在的SystemUiVisibility属性,然后调用View的dispatchCollectViewAttributes方法重新获取最新的的SystemUiVisibility属性。
2、View的dispatchCollectViewAttributes方法如下所示。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
int mSystemUiVisibility;
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;
//设置最新的SystemUiVisibility监听对象,如果不为空,则将AttachInfo的mHasSystemUiListeners属性设置为true。
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
attachInfo.mHasSystemUiListeners = true;
}
}
}
}
3、接着看ViewRootImpl的collectViewAttributes方法。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
final View.AttachInfo mAttachInfo;//控件信息
private boolean collectViewAttributes() {
//判断是否需要重新计算本地属性
if (mAttachInfo.mRecomputeGlobalAttributes) {
...代码省略...
//移除被禁用的SystemUiVisibility属性
mAttachInfo.mSystemUiVisibility &= ~mAttachInfo.mDisabledSystemUiVisibility;
//让params引用指向mWindowAttributes对象
WindowManager.LayoutParams params = mWindowAttributes;
mAttachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params);
mCompatibleVisibilityInfo.globalVisibility =
(mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
| (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
if (mAttachInfo.mKeepScreenOn != oldScreenOn
|| mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
|| mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
applyKeepScreenOnFlag(params);
//将重新获取的窗口视图mView的SystemUiVisibility保存到窗口的LayoutParams属性中
params.subtreeSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
params.hasSystemUiListeners = mAttachInfo.mHasSystemUiListeners;
//调用View的dispatchWindowSystemUiVisiblityChanged方法
mView.dispatchWindowSystemUiVisiblityChanged(mAttachInfo.mSystemUiVisibility);
return true;
}
}
return false;
}
}
在重新获得mView的SystemUiVisibility属性之后,首先会从该属性中移除被禁用的SystemUiVisibility属性,然后让params引用指向mWindowAttributes对象,并将重新获取的保存在mAttachInfo对象中的SystemUiVisibility属性保存到当前窗口的LayoutParams属性中,最后会调用当前View的dispatchWindowSystemUiVisiblityChanged方法。
4、View的dispatchWindowSystemUiVisiblityChanged方法如下所示。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
@Deprecated
public void dispatchWindowSystemUiVisiblityChanged(int visible) {
onWindowSystemUiVisibilityChanged(visible);//调用onWindowSystemUiVisibilityChanged方法
}
public void onWindowSystemUiVisibilityChanged(int visible) {
//默认为空实现
}
}
该方法会进一步调用onWindowSystemUiVisibilityChanged方法,onWindowSystemUiVisibilityChanged方法默认为空实现,但是如果当前mView为DecorView时则不同,DecorView实现了此方法。
四、DecorView更新状态栏和导航栏背景颜色
1、DecorView的onWindowSystemUiVisibilityChanged方法如下所示。
frameworks/base/core/java/com/android/internal/policy/DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@Override
public void onWindowSystemUiVisibilityChanged(int visible) {
//调用updateColorViews方法
updateColorViews(null /* insets */, true /* animate */);
updateDecorCaptionStatus(getResources().getConfiguration());
if (mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) {
updateStatusGuardColor();
}
}
}
onWindowSystemUiVisibilityChanged方法会调用一个updateColorViews这个关键方法。
2、updateColorViews会调用updateColorViewInt方法更新导航栏和状态栏的背景颜色。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
...代码省略...
//更新导航栏颜色,mNavigationColorViewState为导航栏的相关筛选条件
Log.d("SystemBar", "updateColorViews mNavigationColorViewState="+mNavigationColorViewState);
updateColorViewInt(mNavigationColorViewState, calculateNavigationBarColor(appearance),
mWindow.mNavigationBarDividerColor, navBarSize,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, controller);
boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
&& (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
if (viewRoot != null) {
viewRoot.requestInvalidateRootRenderNode();
}
}
...代码省略...
boolean statusBarNeedsRightInset = navBarToRightEdge
&& mNavigationColorViewState.present;
boolean statusBarNeedsLeftInset = navBarToLeftEdge
&& mNavigationColorViewState.present;
int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
: statusBarNeedsLeftInset ? mLastLeftInset : 0;
int statusBarColor = calculateStatusBarColor(appearance);
//更新状态栏颜色,mStatusColorViewState为状态栏的相关筛选条件
Log.d("SystemBar", "updateColorViews mStatusColorViewState="+mStatusColorViewState);
updateColorViewInt(mStatusColorViewState, statusBarColor, 0,
mLastTopInset, false /* matchVertical */, statusBarNeedsLeftInset,
statusBarSideInset, animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, controller);
...代码省略...
}
}
关于DecorView更新状态栏、导航栏背景颜色的过程,后续篇章会具体分析。
五、WindowManagerService的relayoutWindow方法
1、重新回到第二节第4步ViewRootImpl的performTraversals方法中。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
final IWindowSession mWindowSession;//和WMS通信的Binder对象,具体为Session对象
public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
final View.AttachInfo mAttachInfo;//控件信息
private void performTraversals() {
...代码省略...
//收集mView的属性,判断是否需要更新params
if (collectViewAttributes()) {
params = lp;
}
...代码省略...
//此方法最终会触发WindowManagerService的relayoutWindow方法
relayoutWindow(params, viewVisibility, insetsPending);
...代码省略...
//测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...代码省略...
//布局
performLayout(lp, mWidth, mHeight);
...代码省略...
//绘制
performDraw();
...代码省略...
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
//调用IWindowSession的relayout方法,该方法最终会触发WindowManagerService的relayoutWindow方法
int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
}
}
在调用collectViewAttributes获取最新的systemUIVisibiliy属性之后,会调用relayoutWindow方法,该方法进一步调用IWindowSession的relayout方法,IWindowSession的具体实现类为Session。
2、Session的relayout方法如下所示。
frameworks/base/services/core/java/com/android/server/wm/Session.java
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final WindowManagerService mService;
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
int res = mService.relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSurfaceSize);
return res;
}
}
relayout方法会进一步调用WindowManagerService的relayoutWindow方法。
3、WindowManagerService的relayoutWindow方法如下所示。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs//窗口属性
, int requestedWidth, int requestedHeight, int viewVisibility//根View控件是否可见
, int flags, long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
...代码省略...
final WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return 0;
}
final DisplayContent displayContent = win.getDisplayContent();
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
...代码省略...
//是否应该重新布局
final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
(win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
|| win.mActivityRecord.isClientVisible());
...代码省略...
if (shouldRelayout) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
//标记
result = win.relayoutVisibleWindow(result);
if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
//焦点可能变化了
focusMayChange = true;
}
if (win.mAttrs.type == TYPE_INPUT_METHOD
&& displayContent.mInputMethodWindow == null) {
displayContent.setInputMethodWindowLocked(win);
imMayMove = true;
}
win.adjustStartingWindowFlags();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
...代码省略...
if (focusMayChange) {
//relayoutWindow add by syl
Log.d(TAG, "SystemBar: WindowManagerService_relayoutWindow");
if (updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/)) {
imMayMove = false;
}
}
...代码省略...
}
}
relayoutWindow方法会调用一个关键方法updateFocusedWindowLocked。
4、WindowManagerService的updateFocusedWindowLocked方法如下所示。
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
RootWindowContainer mRoot;
//更新窗口焦点的变化
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
Log.d("SystemBar", "WindowManagerService: updateFocusedWindowLocked");
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changed;
}
}
此方法会进一步调用RootWindowContainer的updateFocusedWindowLocked方法。
5、RootWindowContainer的updateFocusedWindowLocked方法如下所示。
/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
class RootWindowContainer extends WindowContainer<DisplayContent>
implements DisplayManager.DisplayListener {
// 当窗口焦点发生变化的时候,更新窗口焦点的变化
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
mTopFocusedAppByProcess.clear();
boolean changed = false;
int topFocusedDisplayId = INVALID_DISPLAY;
for (int i = mChildren.size() - 1; i >= 0; --i) {
//循环遍历DisplayContent的updateFocusedWindowLocked方法
final DisplayContent dc = mChildren.get(i);
changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
final WindowState newFocus = dc.mCurrentFocus;
if (newFocus != null) {
final int pidOfNewFocus = newFocus.mSession.mPid;
if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {
mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mActivityRecord);
}
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = dc.getDisplayId();
}
} else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {
// The top-most display that has a focused app should still be the top focused
// display even when the app window is not ready yet (process not attached or
// window not added yet).
topFocusedDisplayId = dc.getDisplayId();
}
}
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = DEFAULT_DISPLAY;
}
if (mTopFocusedDisplayId != topFocusedDisplayId) {
mTopFocusedDisplayId = topFocusedDisplayId;
mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);
ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
}
return changed;
}
6、DisplayContent 的updateFocusedWindowLocked方法如下所示。
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
//当窗口焦点发生变化的时候,更新窗口焦点的变化
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
int topFocusedDisplayId) {
WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
if (mCurrentFocus == newFocus) {
return false;
}
boolean imWindowChanged = false;
final WindowState imWindow = mInputMethodWindow;
if (imWindow != null) {
final WindowState prevTarget = mImeLayeringTarget;
final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
imWindowChanged = prevTarget != newTarget;
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
assignWindowLayers(false /* setLayoutNeeded */);
}
if (imWindowChanged) {
mWmService.mWindowsChanged = true;
setLayoutNeeded();
newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
}
}
ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",
mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
final WindowState oldFocus = mCurrentFocus;
mCurrentFocus = newFocus;
if (newFocus != null) {
mWinAddedSinceNullFocus.clear();
mWinRemovedSinceNullFocus.clear();
if (newFocus.canReceiveKeys()) {
// Displaying a window implicitly causes dispatching to be unpaused.
// This is to protect against bugs if someone pauses dispatching but
// forgets to resume.
newFocus.mToken.paused = false;
}
}
getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
if (imWindowChanged && oldFocus != mInputMethodWindow) {
// Focus of the input method window changed. Perform layout if needed.
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
//准备重新布局
performLayout(true /*initial*/, updateInputWindows);
} else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
// Client will do the layout, but we need to assign layers
// for handleNewWindowLocked() below.
assignWindowLayers(false /* setLayoutNeeded */);
}
}
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
// If we defer assigning layers, then the caller is responsible for doing this part.
getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);
}
adjustForImeIfNeeded();
// We may need to schedule some toast windows to be removed. The toasts for an app that
// does not have input focus are removed within a timeout to prevent apps to redress
// other apps' UI.
scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
}
// Notify the accessibility manager for the change so it has the windows before the newly
// focused one starts firing events.
// TODO(b/151179149) investigate what info accessibility service needs before input can
// dispatch focus to clients.
if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mH.sendMessage(PooledLambda.obtainMessage(
this::updateAccessibilityOnWindowFocusChanged,
mWmService.mAccessibilityController));
}
return true;
}
}
7、DisplayContent的performLayout方法如下所示。
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
//对视图进行布局,计算窗口布局的宽和高 add by syl
void performLayout(boolean initial, boolean updateInputWindows) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout");
try {
Log.d("SystemBar", "DisplayContent_performLayout");
performLayoutNoTrace(initial, updateInputWindows);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
}
8、DisplayContent的performLayoutNoTrace方法如下所示。
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
private final Consumer<WindowState> mPerformLayout = w -> {
// Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
// wasting time and funky changes while a window is animating away.
final boolean gone = w.isGoneForLayout();
if (DEBUG_LAYOUT && !w.mLayoutAttached) {
Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
+ " mLayoutAttached=" + w.mLayoutAttached
+ " config reported=" + w.isLastConfigReportedToClient());
final ActivityRecord activity = w.mActivityRecord;
if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
+ " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+ " parentHidden=" + w.isParentWindowHidden());
else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
+ " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+ " parentHidden=" + w.isParentWindowHidden());
}
// If this view is GONE, then skip it -- keep the current frame, and let the caller know
// so they can ignore it if they want. (We do the normal layout for INVISIBLE windows,
// since that means "perform layout as normal, just don't display").
if ((!gone || !w.mHaveFrame || w.mLayoutNeeded) && !w.mLayoutAttached) {
if (mTmpInitial) {
w.resetContentChanged();
}
w.mSurfacePlacementNeeded = true;
w.mLayoutNeeded = false;
w.prelayout();
final boolean firstLayout = !w.isLaidOut();
Log.d("SystemBar", "DisplayContent_mPerformLayout");
getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);
w.mLayoutSeq = mLayoutSeq;
// If this is the first layout, we need to initialize the last frames and inset values,
// as otherwise we'd immediately cause an unnecessary resize.
if (firstLayout) {
// The client may compute its actual requested size according to the first layout,
// so we still request the window to resize if the current frame is empty.
if (!w.getFrame().isEmpty()) {
w.updateLastFrames();
}
w.onResizeHandled();
w.updateLocationInParentDisplayIfNeeded();
}
if (w.mActivityRecord != null) {
w.mActivityRecord.layoutLetterbox(w);
}
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
+ " mContainingFrame=" + w.getContainingFrame()
+ " mDisplayFrame=" + w.getDisplayFrame());
}
};
private final Consumer<WindowState> mPerformLayoutAttached = w -> {
if (w.mLayoutAttached) {
if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
+ " mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled);
// If this view is GONE, then skip it -- keep the current frame, and let the caller
// know so they can ignore it if they want. (We do the normal layout for INVISIBLE
// windows, since that means "perform layout as normal, just don't display").
if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
|| w.mLayoutNeeded) {
if (mTmpInitial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
w.resetContentChanged();
}
w.mSurfacePlacementNeeded = true;
w.mLayoutNeeded = false;
w.prelayout();
Log.d("SystemBar", "DisplayContent_mPerformLayoutAttached");
getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
w.mLayoutSeq = mLayoutSeq;
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
+ " mContainingFrame=" + w.getContainingFrame()
+ " mDisplayFrame=" + w.getDisplayFrame());
}
}
};
//计算窗口布局的宽和高 add by syl
private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
if (!isLayoutNeeded()) {
return;
}
clearLayoutNeeded();
if (DEBUG_LAYOUT) {
Slog.v(TAG, "-------------------------------------");
Slog.v(TAG, "performLayout: dw=" + mDisplayInfo.logicalWidth
+ " dh=" + mDisplayInfo.logicalHeight);
}
int seq = mLayoutSeq + 1;
if (seq < 0) seq = 0;
mLayoutSeq = seq;
mTmpInitial = initial;
// First perform layout of any root windows (not attached to another window).
//界定窗口的宽和高,设置监听对象 add by syl
forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
// Now perform layout of attached windows, which usually depend on the position of the
// window they are attached to. XXX does not deal with windows that are attached to windows
// that are themselves attached.
//界定窗口的宽和高,设置监听对象 add by syl
forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);
// Window frames may have changed. Tell the input dispatcher about it.
mInputMonitor.setUpdateInputWindowsNeededLw();
if (updateInputWindows) {
mInputMonitor.updateInputWindowsLw(false /*force*/);
}
}
}
9、DisplayPolicy的layoutWindowLw方法如下所示。
public class DisplayPolicy {
/**
* 界定窗口边框的宽和高
* Called for each window attached to the window manager as layout is proceeding. The
* implementation of this function must take care of setting the window's frame, either here or
* in finishLayout().
*
* @param win The window being positioned.
* @param attached For sub-windows, the window it is attached to; this
* window will already have had layoutWindow() called on it
* so you can use its Rect. Otherwise null.
* @param displayFrames The display frames.
*/
public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
Log.d("SystemBar", "DisplayPolicy_layoutWindowLw");
if (win == mNavigationBar && !INSETS_LAYOUT_GENERALIZATION) {
Log.d("SystemBar", "DisplayPolicy_layoutNavigationBar");
mNavigationBarPosition = layoutNavigationBar(displayFrames,
mBarContentFrames.get(TYPE_NAVIGATION_BAR));
return;
}
if ((win == mStatusBar && !canReceiveInput(win)) && !INSETS_LAYOUT_GENERALIZATION) {
Log.d("SystemBar", "DisplayPolicy_layoutStatusBar");
layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
return;
}
if (win.mActivityRecord != null && win.mActivityRecord.mWaitForEnteringPinnedMode) {
// Skip layout of the window when in transition to pip mode.
return;
}
final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);
final int type = attrs.type;
final int fl = attrs.flags;
final int pfl = attrs.privateFlags;
final int sim = attrs.softInputMode;
displayFrames = win.getDisplayFrames(displayFrames);
final WindowFrames windowFrames = win.getLayoutingWindowFrames();
sTmpLastParentFrame.set(windowFrames.mParentFrame);
final Rect pf = windowFrames.mParentFrame;
final Rect df = windowFrames.mDisplayFrame;
windowFrames.setParentFrameWasClippedByDisplayCutout(false);
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
final InsetsState state = win.getInsetsState();
if (windowFrames.mIsSimulatingDecorWindow && INSETS_LAYOUT_GENERALIZATION) {
// Override the bounds in window token has many side effects. Directly use the display
// frame set for the simulated layout for this case.
computeWindowBounds(attrs, state, df, df);
} else {
computeWindowBounds(attrs, state, win.getBounds(), df);
}
if (attached == null) {
pf.set(df);
if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
final InsetsSource source = state.peekSource(ITYPE_IME);
if (source != null) {
pf.inset(source.calculateInsets(pf, false /* ignoreVisibility */));
}
}
} else {
pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrame() : df);
}
final int cutoutMode = attrs.layoutInDisplayCutoutMode;
// Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
// the cutout safe zone.
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
final boolean attachedInParent = attached != null && !layoutInScreen;
final boolean requestedFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR);
final boolean requestedHideNavigation =
!win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
// TYPE_BASE_APPLICATION windows are never considered floating here because they don't
// get cropped / shifted to the displayFrame in WindowState.
final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
&& type != TYPE_BASE_APPLICATION;
final Rect displayCutoutSafeExceptMaybeBars = sTmpDisplayCutoutSafeExceptMaybeBarsRect;
displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
if (displayFrames.mDisplayWidth < displayFrames.mDisplayHeight) {
displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
} else {
displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
}
}
if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
// At the top we have the status bar, so apps that are
// LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
// already expect that there's an inset there and we don't need to exclude
// the window from that area.
displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
}
if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
// Same for the navigation bar.
switch (mNavigationBarPosition) {
case NAV_BAR_BOTTOM:
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
break;
case NAV_BAR_RIGHT:
displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
break;
case NAV_BAR_LEFT:
displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
break;
}
}
if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
// The IME can always extend under the bottom cutout if the navbar is there.
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
}
// Windows that are attached to a parent and laid out in said parent already avoid
// the cutout according to that parent and don't need to be further constrained.
// Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
// They will later be cropped or shifted using the displayFrame in WindowState,
// which prevents overlap with the DisplayCutout.
if (!attachedInParent && !floatingInScreenWindow) {
sTmpRect.set(pf);
pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
windowFrames.setParentFrameWasClippedByDisplayCutout(!sTmpRect.equals(pf));
}
// Make sure that NO_LIMITS windows clipped to the display don't extend under the
// cutout.
df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}
// TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
// Also, we don't allow windows in multi-window mode to extend out of the screen.
if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
&& !win.inMultiWindowMode()) {
df.left = df.top = -10000;
df.right = df.bottom = 10000;
}
if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
+ ": sim=#" + Integer.toHexString(sim)
+ " attach=" + attached + " type=" + type
+ String.format(" flags=0x%08x", fl)
+ " pf=" + pf.toShortString() + " df=" + df.toShortString());
if (!sTmpLastParentFrame.equals(pf)) {
windowFrames.setContentChanged(true);
}
win.computeFrameAndUpdateSourceFrame(displayFrames);
if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_STATUS_BAR) {
if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
// Make sure that the zone we're avoiding for the cutout is at least as tall as the
// status bar; otherwise fullscreen apps will end up cutting halfway into the status
// bar.
displayFrames.mDisplayCutoutSafe.top = Math.max(
displayFrames.mDisplayCutoutSafe.top,
windowFrames.mFrame.bottom);
}
}
}
}