2.2 窗口位置和大小计算
当WindowState加入到WindowToken并调整z-order之后,客户端会再次调用WindowManagerService.relayoutWindow执行窗口布局。
主要做了这三件事:
1.接收客户端请求
2.创建SurfaceControl
3.窗口位置和大小计算
2.2.1 接收客户端请求
与addWindow流程的调用过程类似,WindowManagerService.relayoutWindow也是由客户端通过Session来调用的。 首先我们来看一下客户端给我们传递了哪些参数吧。
window:是WMS与客户端通信的Binder。
attrs:窗口的布局属性,根据attrs提供的属性来布局窗口。
requestWidth、requestHeight:客户端请求的窗口尺寸。
viewFlags:窗口的可见性。包括VISIBLE(0,view可见),INVISIBLE(4,view不可见,但是仍然占用布局空间)GONE(8,view不可见,不占用布局空间)
flags:定义一些布局行为。
outFrames:返回给客户端的,保存了重新布局之后的位置与大小。
mergedConfiguration:相关配置信息。
outSurfaceControl:返回给客户端的surfaceControl。
outInsetsState:用来保存系统中所有Insets的状态。
outActiveControls:InSetsSourceControl数组。
outSyncSeqIdBundle:与布局同步有关。
Session调用WMS.relayoutWindow将客户端传入的参数传递给WMS。
代码路径:framework/services/core/java/com/android/server/wm/Session.java
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
int res = mService.relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, flags,
outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSyncSeqIdBundle);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
return res;
}
2.2.2 relayoutWindow
在WMS.relayoutWindow中主要做了以下事情:
1.根据客户端传过来的IWindow在mWindowMap获取窗口添加阶段创建的WindowState。
2.设置DisplayContent.mLayoutNeeded以及shouldRelayout标志位
3.Surface的创建流程。
4.窗口尺寸的计算以及Surface的状态变更。
代码路径:framework/services/core/java/com/android/server/wm/WindowManagerService.java
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
......
synchronized (mGlobalLock) {
/*1.根据客户端传过来的Iwindow从mWindowMap中获取对应的WindowState*/
final WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return 0;
}
//获取DisplayContent、DisplayPolicy以及WindowStateAnimator
final DisplayContent displayContent = win.getDisplayContent();
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
WindowStateAnimator winAnimator = win.mWinAnimator;
if (viewVisibility != View.GONE) {
//根据客户端请求的窗口大小设置WindowState的requestedWidth, requestedHeight
//并设置WindowState.mLayoutNeeded为true
win.setRequestedSize(requestedWidth, requestedHeight);
}
......
//根据请求的宽带和高度窗口缩放比例
win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight);
......
//获取原来window的可见性,此时为INVISIBLE
final int oldVisibility = win.mViewVisibility;
......
//代表现在没有surface但应该很快就有标志位
win.mRelayoutCalled = true;
win.mInRelayout = true;
//将当前窗口的可见性有原来的INVISIBLE调整为VISIBLE
win.setViewVisibility(viewVisibility);
ProtoLog.i(WM_DEBUG_SCREEN_ON,
"Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,
viewVisibility, new RuntimeException().fillInStackTrace());
/*2.1.将displayContent中的布局标志为mLayoutNeeded置为true*/
win.setDisplayLayoutNeeded();
win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
// We should only relayout if the view is visible, it is a starting window, or the
// associated appToken is not hidden.
/*2.2.判断是否允许relayout,此时为true*/
//判断条件:view可见且(activityRecord不为空,或者布局类型为TYPE_APPLICATION_STARTING,或者窗口已经告诉客户端可以显示)
final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
(win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
|| win.mActivityRecord.isClientVisible());
......
// Create surfaceControl before surface placement otherwise layout will be skipped
// (because WS.isGoneForLayout() is true when there is no surface.
/*3.surface的创建流程*/
if (shouldRelayout) {
try {
//进入creatSurfaceControl开始创建SurfaceControl
result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
} catch (Exception e) {
......
return 0;
}
}
// We may be deferring layout passes at the moment, but since the client is interested
// in the new out values right now we need to force a layout.
/*4.窗口尺寸的计算以及Surface的状态变更*/
//WindowSurfacePlacer在WMS初始化的时候创建
mWindowPlacerLocked.performSurfacePlacement(true /* force */);
......
//填充计算好的frame返回给客户端,更新mergedConfiguration对象
win.fillClientWindowFramesAndConfiguration(outFrames, mergedConfiguration,
false /* useLatestConfig */, shouldRelayout);
// Set resize-handled here because the values are sent back to the client.
win.onResizeHandled();
......
}
Binder.restoreCallingIdentity(origId);
//返回result
return result;
}
2.2.3 创建SurfaceControl
在relayoutWindow中创建SurfaceControl
result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
关于SurfaceControl的创建在WMS中主要做两件事:
1.调用WindwoStateAnimator执行具体的SurfaceControl的创建。
2.将创建的SurfaceControl赋值给客户端的outSurfaceControl。
代码路径:framework/services/core/java/com/android/server/wm/WindowManagerService.java
private int createSurfaceControl(SurfaceControl outSurfaceControl, int result,
WindowState win, WindowStateAnimator winAnimator) {
......
WindowSurfaceController surfaceController;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
/*
* WindowStateAnimator用来帮助WindowState管理animator和surface基本操作的
* 1.WMS将创建的surfaceContorl的操作交给windowAnimator来处理
*/
surfaceController = winAnimator.createSurfaceLocked();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (surfaceController != null) {
/*2.将WMS的SurfaceControl赋值给客户端的outSurfaceControl*/
surfaceController.getSurfaceControl(outSurfaceControl);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
} else {
// For some reason there isn't a surface. Clear the
// caller's object so they see the same state.
ProtoLog.w(WM_ERROR, "Failed to create surface control for %s", win);
outSurfaceControl.release();
}
return result;
}
在WindowStateAnimator中创建SurfaceControl主要经过以下三个步骤:
1.重置Surface标志位,变更mDrawState状态为DRAW_PENDING。
2.通过实例化WindowSurfaceController来创建SurfaceControl。
3.处理Surface标志位,将其置为true,标志着当前WindowState已经有surface了。
代码路径:framework/services/core/java/com/android/server/wm/WindowStateAnimator.java
WindowSurfaceController createSurfaceLocked() {
final WindowState w = mWin;
//首先判断是否存在mSurfaceController
if (mSurfaceController != null) {
return mSurfaceController;
}
/*1.1.设置WindowState的mHasSurface设置为false*/
w.setHasSurface(false);
ProtoLog.i(WM_DEBUG_ANIM, "createSurface %s: mDrawState=DRAW_PENDING", this);
/*1.2.将WindowStateAnimator中的DrawState设置为DRAW_PENDING*/
resetDrawState();
mService.makeWindowFreezingScreenIfNeededLocked(w);
/*1.3.将surface创建flag设置为hidden*/
int flags = SurfaceControl.HIDDEN;
//获取windowState的布局参数
final WindowManager.LayoutParams attrs = w.mAttrs;
// Set up surface control with initial size.
try {
......
/*2.创建WindowSurfaceController*/
//attrs.getTitle().toString()为当前activity的全路径名
//format为位图格式
//flags为surface创建的标志位(如:HIDDED(0x04,surface创建为隐藏),SKIP_SCREENSHOT(0x040,截屏时跳过此图层将不会包含在非主显示器上),SECURE(0X080,禁止复制表面的内容,屏幕截图和次要的非安全显示将呈现黑色内容而不是surface内容)等)
//attrs.type为窗口类型
mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
flags, this, attrs.type);
mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
/*3.将WindowState的hasSurface标志设置为true,标志着道歉WindowState已经有surface了*/
w.setHasSurface(true);
......
} catch (OutOfResourcesException e) {
......
} catch (Exception e) {
......
}
......
return mSurfaceController;
}
SurfaceControl的创建过程为典型的建造者模式 接下来看看WindowSurfaceController的构造方法 代码路径:framework/services/core/java/com/android/server/wm/WindowSurfaceController.java
WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
int windowType) {
//设置WindowStateAnimator
mAnimator = animator;
//窗口名
title = name;
//WMS对象
mService = animator.mService;
//WindowState对象
final WindowState win = animator.mWin;
//窗口类型
mWindowType = windowType;
//IWindowSession对象
mWindowSession = win.mSession;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
//makeSurface最终会调用到DisplayContent的makeChildSurface方法,返回SurfaceControl.Builder
final SurfaceControl.Builder b = win.makeSurface()
.setParent(win.getSurfaceControl())
.setName(name)
.setFormat(format)
.setFlags(flags)
.setMetadata(METADATA_WINDOW_TYPE, windowType)
.setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
.setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
.setCallsite("WindowSurfaceController");
......
//获取SurfaceControl实例对象
mSurfaceControl = b.build();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
两个关键方法win.makeSurface()和b.build()
1.final SurfaceControl.Builder b = win.makeSurface()
我们先来看看win.makeSurface(),windowState中没有makeSurface()方法,因此调用其父类WindowContainer的makeSurface()方法
代码路径:framework/services/core/java/com/android/server/wm/WindowContainer.java
SurfaceControl.Builder makeSurface() {
final WindowContainer p = getParent();
return p.makeChildSurface(this);
}
/**
* @param child The WindowContainer this child surface is for, or null if the Surface
* is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
*/
SurfaceControl.Builder makeChildSurface(WindowContainer child) {
final WindowContainer p = getParent();
// Give the parent a chance to set properties. In hierarchy v1 we rely
// on this to set full-screen dimensions on all our Surface-less Layers.
return p.makeChildSurface(child)
.setParent(mSurfaceControl);
}
最终会调用到DisplayContent的makeChildSurface 代码路径:framework/services/core/java/com/android/server/wm/DisplayContent.java
@Override
SurfaceControl.Builder makeChildSurface(WindowContainer child) {
//此时child为WindowState
//获取SurfaceSession,SurfaceSession的创建在Session.windowAddedLocked中,其最开始调用在WindowManagerService.addWindow中win.attach()中创建
SurfaceSession s = child != null ? child.getSession() : getSession();
//返回SurfaceControl.Builder
final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(s).setContainerLayer();
if (child == null) {
return b;
}
//设置SurfaceControl.Builder的name以及parent
return b.setName(child.getName())
.setParent(mSurfaceControl);
}
2.mSurfaceControl = b.build();
再来看看b.build(),调用SurfaceControl中的build
代码路径:framework/core/java/android/view/SurfaceControl.java
/**
* Construct a new {@link SurfaceControl} with the set parameters. The builder
* remains valid.
*/
@NonNull
public SurfaceControl build() {
//检查width以及height,初始都应该为0
if (mWidth < 0 || mHeight < 0) {
throw new IllegalStateException(
"width and height must be positive or unset");
}
if ((mWidth > 0 || mHeight > 0) && (isEffectLayer() || isContainerLayer())) {
throw new IllegalStateException(
"Only buffer layers can set a valid buffer size.");
}
if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) {
setBLASTLayer();
}
//创建SurfaceControl的实例
return new SurfaceControl(
mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata,
mLocalOwnerView, mCallsite);
}
/**
* @param session The surface session, must not be null.
* @param name The surface name, must not be null.
* @param w The surface initial width.
* @param h The surface initial height.
* @param flags The surface creation flags.
* @param metadata Initial metadata.
* @param callsite String uniquely identifying callsite that created this object. Used for
* leakage tracking.
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, SparseIntArray metadata, WeakReference<View> localOwnerView,
String callsite)
throws OutOfResourcesException, IllegalArgumentException {
if (name == null) {
throw new IllegalArgumentException("name must not be null");
}
mName = name;
mWidth = w;
mHeight = h;
mLocalOwnerView = localOwnerView;
//创建Parcel用来传递数据
Parcel metaParcel = Parcel.obtain();
try {
......
//调用native层
mNativeObject = nativeCreate(session, name, w, h, format, flags,
parent != null ? parent.mNativeObject : 0, metaParcel);
} finally {
metaParcel.recycle();
}
if (mNativeObject == 0) {
throw new OutOfResourcesException(
"Couldn't allocate SurfaceControl native object");
}
mNativeHandle = nativeGetHandle(mNativeObject);
mCloseGuard.openWithCallSite("release", callsite);
}
SurfaceControl的构造方法调用完成后,返回查看前面
result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
2.2.4 计算窗口大小位置
在relayoutWindow中计算窗口大小位置
mWindowPlacerLocked.performSurfacePlacement(true /* force */);
该流程我们分为三部分介绍:
1.该部分处理有关窗口布局循环的逻辑。
2.该部分处理Surface的状态变更,以及调用layoutWindowLw的流程。
3.计算窗口位置大小。
1.处理窗口布局循环
performSurfacePlacement是一个确定所有窗口的Surface的如何摆放,如何显示、显示在什么位置、显示区域多大的一个入口方法。 该方法主要设置了布局的循环条件,当mTraversalScheduled 标志位为true,且loopCount大于0。将会调用performSurfacePlacementLoop执行布局操作。 代码路径:framework/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
final void performSurfacePlacement(boolean force) {
if (mDeferDepth > 0 && !force) {
mDeferredRequests++;
return;
}
//将循环的最大次数设置为6次
int loopCount = 6;
do {
//将该标志为设置为false
mTraversalScheduled = false;
//执行窗口布局操作
performSurfacePlacementLoop();
mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
loopCount--;
//只有当mTraversalScheduled为true且循环次数大于0时,才会再次循环执行布局
} while (mTraversalScheduled && loopCount > 0);
mService.mRoot.mWallpaperActionPending = false;
}
performSurfacePlacementLoop方法主要做两件事:
1.调用RootWindowContainer对所有窗口执行布局操作,
2.处理是否再次进行布局的逻辑。如果DisplayContent.mLayoutNeeded标志位为true且布局循环次数小于6次,则会将mTraversalScheduled标志位置为true,在performSurfacePlacement中会再次调用performSurfacePlacementLoop。
private void performSurfacePlacementLoop() {
//若当前已经进行布局操作,则无需重复调用直接返回
if (mInLayout) {
......
return;
}
......
//将该标志位置为true,表示正在处于布局过程中
mInLayout = true;
......
try {
/*1.调用RootWindowContainer的performSurfacePlacement()方法对所有窗口执行布局操作*/
mService.mRoot.performSurfacePlacement();
mInLayout = false;
if (mService.mRoot.isLayoutNeeded()) {
/*2.若需要布局,且布局次数小于6次,则需要再次请求布局*/
if (++mLayoutRepeatCount < 6) {
//该方法中会将mTraversalScheduled标志位设置位true
requestTraversal();
} else {
Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
mLayoutRepeatCount = 0;
}
} else {
mLayoutRepeatCount = 0;
}
if (mService.mWindowsChanged && !mService.mWindowChangeListeners.isEmpty()) {
mService.mH.removeMessages(REPORT_WINDOWS_CHANGE);
mService.mH.sendEmptyMessage(REPORT_WINDOWS_CHANGE);
}
} catch (RuntimeException e) {
mInLayout = false;
Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
}
}
2.处理所有Surface的状态变更,以及调用layoutWindowLw的流程
mService.mRoot.performSurfacePlacement();
上面说到在RootWindowContainer.performSurfacePlacement()中调用了performSurfaceNoTrace()方法,该方法为实际的处理布局的方法,主要处理以下流程:
1.如果有焦点变化,更新焦点。
2.执行窗口尺寸计算,surface状态变更等操作。
3.将Surface状态变更为HAS_DRAWN,触发App触发动画。该过程在finishdrawing()中再详细分析。
4.如果壁纸有变化,更新壁纸。
5.再次处理焦点变化。
6.如果过程中由size或者位置变化,则通知客户端重新relayout。
7.销毁不可见的窗口
(其中mWmService.mSyncEngine.onSurfacePlacement();涉及wmshell相关的动画,这里不做介绍)
代码路径:framework/services/core/java/com/android/server/wm/RootWindowContainer.java
void performSurfacePlacement() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
try {
//调用performSurfacePlacementNoTrace()
performSurfacePlacementNoTrace();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
// "Something has changed! Let's make it correct now."
// TODO: Super long method that should be broken down...
void performSurfacePlacementNoTrace() {
......
/*1.如果有焦点变化,更新焦点*/
if (mWmService.mFocusMayChange) {
mWmService.mFocusMayChange = false;
mWmService.updateFocusedWindowLocked(
UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
}
......
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
//开启事务,获取GlobalTransactionWrapper对象
mWmService.openSurfaceTransaction();
try {
/*2.执行窗口尺寸计算,surface状态变更等操作*/
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
//关闭事务
mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
}
......
// Send any pending task-info changes that were queued-up during a layout deferment
mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
// 涉及wmshell的流程的动画
mWmService.mSyncEngine.onSurfacePlacement();
mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
/*3.将Surface状态变更为HAS_DRAWN,触发App触发动画。该过程在“2.3.3mDrawState变更为HAS_DRAW”流程中再详细分析*/
checkAppTransitionReady(surfacePlacer);
......
/*4.遍历所有DisplayContent,如果壁纸有变化,更新壁纸*/
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
//判断DisplayContent的壁纸是否需要改变
if (displayContent.mWallpaperMayChange) {
ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting");
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
surfacePlacer.debugLayoutRepeats("WallpaperMayChange",
displayContent.pendingLayoutChanges);
}
}
}
/*5.在此处理焦点变化*/
if (mWmService.mFocusMayChange) {
mWmService.mFocusMayChange = false;
mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
false /*updateInputWindows*/);
}
......
/*6.如果过程中size或者位置变化,则通知客户端重新relayout*/
handleResizingWindows();
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"With display frozen, orientationChangeComplete=%b",
mOrientationChangeComplete);
}
if (mOrientationChangeComplete) {
if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
}
mWmService.stopFreezingDisplayLocked();
}
// Destroy the surface of any windows that are no longer visible.
/*7.销毁不可见的窗口*/
i = mWmService.mDestroySurface.size();
if (i > 0) {
do {
i--;
WindowState win = mWmService.mDestroySurface.get(i);
win.mDestroying = false;
final DisplayContent displayContent = win.getDisplayContent();
if (displayContent.mInputMethodWindow == win) {
displayContent.setInputMethodWindowLocked(null);
}
if (displayContent.mWallpaperController.isWallpaperTarget(win)) {
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
win.destroySurfaceUnchecked();
} while (i > 0);
mWmService.mDestroySurface.clear();
}
......
}
在applySurfaceChangesTransaction();方法中其主要执行:
1.水印、StrictMode警告框以及模拟器显示的布局。
2.遍历所有DisplayContent执行其applySurfaceChangesTransaction
我们一起看看这个方法
private void applySurfaceChangesTransaction() {
mHoldScreenWindow = null;
mObscuringWindow = null;
// TODO(multi-display): Support these features on secondary screens.
/*1.水印、StrictMode警告框以及模拟器显示的布局*/
//获取手机默认DisplayContent的信息
final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
final DisplayInfo defaultInfo = defaultDc.getDisplayInfo();
final int defaultDw = defaultInfo.logicalWidth;
final int defaultDh = defaultInfo.logicalHeight;
//布局水印
if (mWmService.mWatermark != null) {
mWmService.mWatermark.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
}
//布局StrictMode警告框
if (mWmService.mStrictModeFlash != null) {
mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
}
//布局模拟器显示覆盖
if (mWmService.mEmulatorDisplayOverlay != null) {
mWmService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh,
mWmService.getDefaultDisplayRotation(), mDisplayTransaction);
}
/*2.遍历RootWindowContainer下所有DisplayContent执行其applySurfaceChangesTransaction()*/
final int count = mChildren.size();
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
dc.applySurfaceChangesTransaction();
}
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
mWmService.mDisplayManagerInternal.performTraversal(mDisplayTransaction);
SurfaceControl.mergeToGlobalTransaction(mDisplayTransaction);
}
接下来继续跟踪dc.applySurfaceChangesTransaction();
该方法主要
1.遍历所有窗口,计算窗口的布局大小,具体流程查看performLayoutNoTrace。(主要跟踪点)
2.surface的状态更改。(见Android T WMS窗口添加流程其三——服务端代码详解(窗口状态刷新)中的2.3.3mDrawState变更为HAS_DRAW”流程)
3.处理surface的位置、大小以及显示等。(见Android T WMS窗口添加流程其三——服务端代码详解(窗口状态刷新)中的2.3.4 show Surface”流程)
代码路径:framework/services/core/java/com/android/server/wm/DisplayContent.java
void applySurfaceChangesTransaction() {
//获取WindowSurfacePlacer
final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
......
// Perform a layout, if needed.
/*1.执行布局,该方法最终会调用performLayoutNoTrace,计算窗口的布局参数*/
performLayout(true /* initial */, false /* updateInputWindows */);
......
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
try {
/*2.遍历所有窗口,主要是改变surface的状态。见“2.3.3mDrawState变更为HAS_DRAW”流程*/
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
/*3.处理各个surface的位置、大小以及是否要在屏幕上显示等。后面finishDrawing()流程中再跟踪*/
prepareSurfaces();
......
}
继续跟踪performLayout(true /* initial */, false /* updateInputWindows */);
该方法主要其实就是调用performLayoutNoTrace()方法,然后在这个方法中首先判断布局标志位mLayoutNeeded,该标志位在WMS.relayoutWindow中被置为true。
false则直接返回不会进行布局操作;true则分别遍历父窗口和子窗口进行布局。
void performLayout(boolean initial, boolean updateInputWindows) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout");
try {
//调用performLayoutNoTrace
performLayoutNoTrace(initial, updateInputWindows);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
/*1.判断是否需要布局,不需要则直接返回,即判断布局标志位mLayoutNeeded是否为true*/
if (!isLayoutNeeded()) {
return;
}
//将DisplayContent.mLayoutNeeded属性置为false
clearLayoutNeeded();
......
// First perform layout of any root windows (not attached to another window).
/*2.对所有顶级窗口进行布局*/
//最终会回调mPerformLayout
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.
/*3.处理子窗口的布局*/
//最终会回调mPerformLayoutAttached
forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);
......
}
我们先看看forAllWindows方法的实现 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
ForAllWindowsConsumerWrapper wrapper = obtainConsumerWrapper(callback);
//调用重载的forAllWindows方法
//boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom)
forAllWindows(wrapper, traverseTopToBottom);
wrapper.release();
}
入参:
Consumer<WindowState> callback:这是一个回调函数,对每个找到的窗口调用它的accept方法。
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
比如:forAllWindows(mPerformLayout, true /* traverseTopToBottom */);中的传入的mPerformLayout这个callback,实现的就是这个accept方法。
boolean traverseTopToBottom:一个布尔值,决定遍历窗口层次结构的顺序。如果为true,则从上到下遍历(即z-order从高到低);如果为false,则从下到上遍历。
这个方法首先获取一个ForAllWindowsConsumerWrapper对象来包装传入的callback,然后调用重载的forAllWindows方法并传入这个包装对象以及traverseTopToBottom的值。在调用完之后,它会释放这个包装对象。
我们先看看ForAllWindowsConsumerWrapper。
private final class ForAllWindowsConsumerWrapper implements ToBooleanFunction<WindowState> {
private Consumer<WindowState> mConsumer;
void setConsumer(Consumer<WindowState> consumer) {
mConsumer = consumer;
}
//apply方法返回为true时,停止遍历
@Override
public boolean apply(WindowState w) {
mConsumer.accept(w);
return false;
}
//用于释放包装对象
void release() {
mConsumer = null;
mConsumerWrapperPool.release(this);
}
}
简单来说就是对Consumer进行了一个包装,通过实现ToBooleanFunction接口的apply方法来调用Consumer的accept方法。
再看看调用的forAllWindows(wrapper, traverseTopToBottom);,也就是重载的forAllWindows方法。
/**
* For all windows at or below this container call the callback.
* @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and
* stops the search if {@link ToBooleanFunction#apply} returns true.
* @param traverseTopToBottom If true traverses the hierarchy from top-to-bottom in terms of
* z-order, else from bottom-to-top.
* @return True if the search ended before we reached the end of the hierarchy due to
* {@link ToBooleanFunction#apply} returning true.
*/
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
}
return false;
}
入参:
ToBooleanFunction<WindowState> callback:这是一个函数式接口,用于对每个找到的窗口调用其apply方法。如果apply方法返回true,则搜索将停止。
boolean traverseTopToBottom:这是一个布尔值,如果为true,则从上到下遍历层次结构(即Z顺序),否则从下到上遍历。
这个方法首先检查traverseTopToBottom的值。如果为true,则从子容器的最后一个开始,向前遍历每个子容器的所有窗口。如果在任何时候callback返回true,则立即返回true。如果遍历完所有子容器后仍未返回true,则返回false。 如果traverseTopToBottom为false,则逻辑相反,从子容器的第一个开始,向后遍历每个子容器的所有窗口。
简单来说,forAllWindows方法就是根据traverseTopToBottom决定遍历的顺序,来对所有的容器执行callback回调函数。
回到performLayoutNoTrace方法中
forAllWindows(mPerformLayout, true /* traverseTopToBottom */);对所有顶级窗口进行布局,即对所有父窗口布局。
当遍历到DisplayContent下的每个窗口时都会执行mPerformLayout,该方法会将WindowState.mLayoutNeeded标志位置false,通过getDisplayPolicy().layoutWindowLw方法处理布局,即将具体的布局操作交给DisplayPolicy进行处理,见“3. 计算窗口位置大小“。
先看看mPerformLayout
private final Consumer<WindowState> mPerformLayout = w -> {
//如果当前窗口为子窗口则直接返回
if (w.mLayoutAttached) {
return;
}
// 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 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").
//!gone 表示isGoneForLayout返回true
//或者 w.mHaveFrame窗口没有设置边界
//或者 mLayoutNeeded标志位为true
if (!gone || !w.mHaveFrame || w.mLayoutNeeded) {
if (mTmpInitial) {
//设置窗口布局WindowFrames.mContentChanged为false
w.resetContentChanged();
}
//将mSurfacePlacementNeeded标志为置为true
w.mSurfacePlacementNeeded = true;
//将WindowState.mLayoutNeeded标志位置为false
w.mLayoutNeeded = false;
//判断当前窗口是否是第一次布局
final boolean firstLayout = !w.isLaidOut();
//调用DisplayPolicy.layoutWindowLw进行布局,根据DisplayFrames对象对WindowState.mWindowFrames中的各个Rect对象属性进行确定
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();
}
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
+ " mParentFrame=" + w.getParentFrame()
+ " mDisplayFrame=" + w.getDisplayFrame());
}
};
当mLayoutNeeded标志被设置时,表示布局需要更新;mSurfacePlacementNeeded标志被设置时,表示需要更新surface位置的条件之一(WindowState.updateSurfacePosition方法中有判断)。
forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);处理子窗口的布局。
和前面同理,当遍历到DisplayContent下的每个窗口时都会执行mPerformLayoutAttached,该方法会将WindowState.mLayoutNeeded标志位置false,通过getDisplayPolicy().layoutWindowLw方法处理布局,将具体的布局操作交给DisplayPolicy进行处理,见“3. 计算窗口位置大小“。
再来看看mPerformLayoutAttached
private final Consumer<WindowState> mPerformLayoutAttached = w -> {
//如果当前窗口为子窗口则直接返回
if (!w.mLayoutAttached) {
return;
}
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").
//w.mViewVisibility != GONE && w.mRelayoutCalled 表示窗口View的可见性不为GONE并且窗口调用过relayoutWindow方法
//或者 w.mHaveFrame窗口没有设置边界
//或者 mLayoutNeeded标志位为true
if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
|| w.mLayoutNeeded) {
if (mTmpInitial) {
//设置窗口布局WindowFrames.mContentChanged为false
w.resetContentChanged();
}
//将mSurfacePlacementNeeded标志为置为true
w.mSurfacePlacementNeeded = true;
//将WindowState.mLayoutNeeded标志位置为false
w.mLayoutNeeded = false;
//调用DisplayPolicy.layoutWindowLw进行布局,根据DisplayFrames对象对WindowState.mWindowFrames中的各个Rect对象属性进行确定
getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
w.mLayoutSeq = mLayoutSeq;
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
+ " mParentFrame=" + w.getParentFrame()
+ " mDisplayFrame=" + w.getDisplayFrame());
}
};
3.计算窗口位置大小
前面说的
getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);处理父窗口布局。
getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);处理子窗口布局。
无论哪种处理方式,layoutWindowLw主要做了以下三件事:
1.首先会获DisplayFrames:DisplayContent新建时创建,内部数据由屏幕提供。
2.其次调用WindowLayout.computeFrames计算窗口布局大小。
3.最后调用WindowState.setFrames将计算的布局参数赋值给当前窗口的windowFrames。
代码路径:framework/services/core/java/com/android/server/wm/DisplayPolicy.java
/**
* 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) {
//判断是否需要跳过布局
if (win.skipLayout()) {
return;
}
// This window might be in the simulated environment.
// We invoke this to get the proper DisplayFrames.
/*1.获取DisplayFrames*/
displayFrames = win.getDisplayFrames(displayFrames);
//获取某个方向的窗口布局参数
final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);
//null
final Rect attachedWindowFrame = attached != null ? attached.getFrame() : null;
// If this window has different LayoutParams for rotations, we cannot trust its requested
// size. Because it might have not sent its requested size for the new rotation.
final boolean trustedSize = attrs == win.mAttrs;
final int requestedWidth = trustedSize ? win.mRequestedWidth : UNSPECIFIED_LENGTH;
final int requestedHeight = trustedSize ? win.mRequestedHeight : UNSPECIFIED_LENGTH;
/*2.调用WindowLayout.computeFrames计算窗口布局大小*/
mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
win.getRequestedVisibilities(), attachedWindowFrame, win.mGlobalScale,
sTmpClientFrames);
/*3.将计算的布局参数赋值给windowFrames*/
win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
}
先来看看computeFrames,计算窗口布局大小
mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
win.getRequestedVisibilities(), attachedWindowFrame, win.mGlobalScale,
sTmpClientFrames);
调用的是WindowLayout的computeFrames方法
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames)
入参说明:
attrs:这些是窗口的布局参数。它们定义了窗口的位置、大小、堆叠顺序等属性。
state:这个参数代表了窗口的边距状态。边距是窗口与设备边缘之间的空间,可能会被其他元素(如状态栏或导航栏)占据。
displayCutoutSafe:这是一个矩形,表示在计算窗口大小时可以安全忽略的显示切边区域。这通常是为了防止应用程序内容与设备上的物理切边重叠。
windowBounds:这是窗口的边界矩形,通常表示窗口在屏幕上的位置和大小。
windowingMode:这个参数定义了窗口的窗口模式。例如,它可以是全屏、浮动等模式。
requestedWidth、requestedHeight:这是应用程序请求的窗口宽度和高度。
requestedVisibilities:请求的可见性。这定义了应用程序请求的边距可见性,例如状态栏或导航栏是否可见。
attachedWindowFrame:附加窗口的边界。如果这个窗口是附加到另一个窗口的,这个参数表示它相对于其父窗口的位置和大小。
compatScale:兼容性比例,一个缩放因子,用于调整窗口内容的显示大小以适应不同的屏幕尺寸或分辨率。
outFrames:用于返回计算后的窗口信息。它包含了窗口的实际边界、边距等信息。
代码路径:frameworks/base/core/java/android/view/WindowLayout.java
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames) {
//传入的参数attrs中提取出窗口的类型(type)、标志(fl)、私有标志(pfl)和布局是否在屏幕内(layoutInScreen)
final int type = attrs.type;
final int fl = attrs.flags;
final int pfl = attrs.privateFlags;
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
//定义了用于存储结果的矩形变量,包含:显示边界(outDisplayFrame)、父边界(outParentFrame)和实际边界(outFrame)
final Rect outDisplayFrame = outFrames.displayFrame;
final Rect outParentFrame = outFrames.parentFrame;
final Rect outFrame = outFrames.frame;
// Compute bounds restricted by insets
//计算窗口被Insets限制的边界。Insets是屏幕边缘的空间,用于放置状态栏、导航栏等。
//这一步通过调用state.calculateInsets()方法完成,该方法需要窗口边界和窗口布局参数作为输入。
final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
attrs.isFitInsetsIgnoringVisibility());
//代码根据Insets的边类型(LEFT、TOP、RIGHT、BOTTOM),从计算出的Insets中提取出相应的边距,
//并将它们添加到窗口的原始边界上,得到显示边界。
final @WindowInsets.Side.InsetsSide int sides = attrs.getFitInsetsSides();
final int left = (sides & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
final int top = (sides & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
final int right = (sides & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
final int bottom = (sides & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
//代码将计算出的显示边界赋值给outDisplayFrame
outDisplayFrame.set(windowBounds.left + left, windowBounds.top + top,
windowBounds.right - right, windowBounds.bottom - bottom);
//根据窗口的附加信息和布局属性来确定父边界的位置和大小。
if (attachedWindowFrame == null) {
//将outParentFrame设置为与outDisplayFrame相同,这意味着父边界与显示边界相同
outParentFrame.set(outDisplayFrame);
//检查私有标志PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME是否被设置。
//这个标志可能表示是否需要根据输入法窗口(IME)的位置来调整父边界。
if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
//从状态中获取输入法窗口的源(source)
final InsetsSource source = state.peekSource(ITYPE_IME);
if (source != null) {
//如果输入法窗口的source存在,则使用该source来计算父边界的内边距(Insets)。
outParentFrame.inset(source.calculateInsets(
outParentFrame, false /* ignoreVisibility */));//这里忽略source的可见性。
}
}
} else {
//如果layoutInScreen为true,则将outParentFrame设置为与attachedWindowFrame相同。
//这表示父边界是由附加窗口的边界决定的。
//如果layoutInScreen为false,则将outParentFrame设置为与outDisplayFrame相同。
//这表示父边界与显示边界相同。
outParentFrame.set(!layoutInScreen ? attachedWindowFrame : outDisplayFrame);
}
// Compute bounds restricted by display cutout
//根据屏幕的显示切边和窗口的布局属性来计算窗口在屏幕上受到限制的位置和大小,确保窗口不会覆盖到显示切边区域
final int cutoutMode = attrs.layoutInDisplayCutoutMode;//切边模式
final DisplayCutout cutout = state.getDisplayCutout();//屏幕上的显示切边区域
//将displayCutoutSafeExceptMaybeBars设置为与displayCutoutSafe相同,
//这是一个临时矩形,用于稍后计算不受某些系统界面元素(如状态栏)影响的显示切边安全区域。
final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
//将outFrames.isParentFrameClippedByDisplayCutout设置为false,表示父边界目前没有被显示切边裁剪
outFrames.isParentFrameClippedByDisplayCutout = false;
//如果layoutInDisplayCutoutMode不是ALWAYS并且显示切边不为空
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
// Ensure that windows with a non-ALWAYS display cutout mode are laid out in
// the cutout safe zone.
//获取屏幕的显示边界(displayFrame)
final Rect displayFrame = state.getDisplayFrame();
//获取状态的Source
final InsetsSource statusBarSource = state.peekSource(ITYPE_STATUS_BAR);
//检查状态栏源(statusBarSource)是否存在,并且如果displayCutoutSafe.top大于屏幕的顶部
if (statusBarSource != null && displayCutoutSafe.top > displayFrame.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.
//调整displayCutoutSafeExceptMaybeBars.top以确保切边避开的区域至少与状态栏一样高。
displayCutoutSafeExceptMaybeBars.top =
Math.max(statusBarSource.getFrame().bottom, displayCutoutSafe.top);
}
//如果layoutInDisplayCutoutMode是SHORT_EDGES
if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
if (displayFrame.width() < displayFrame.height()) {
//如果屏幕的宽度小于高度,则将displayCutoutSafeExceptMaybeBars的顶部和底部设置为最大和最小整数值,
//这意味着不考虑这些方向上的显示切边。
displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
} else {
//否则,将左侧和右侧设置为最大和最小整数值
//这意味着不考虑这些方向上的显示切边。
displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
}
}
//通过位运算检查attrs.flags中的FLAG_LAYOUT_INSET_DECOR标志是否被设置。如果被设置,则layoutInsetDecor为true
// FLAG_LAYOUT_INSET_DECOR:使窗口的内容布局在DecorView(装饰视图)之内
final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
//检查布局是否应在屏幕上进行且是否需要考虑显示切边
//布局在屏幕上、DecorView之内 且 显示切边模式为默认或短边缘模式
if (layoutInScreen && layoutInsetDecor
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
//使用给定的displayFrame、系统栏类型和可见性请求来计算系统栏的插入
//系统栏包含: STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR
final Insets systemBarsInsets = state.calculateInsets(
displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities);
//如果系统栏在左侧、顶部、右侧或底部的插入大于0,则调整displayCutoutSafeExceptMaybeBars的相应边界,
//使其尽可能地远离屏幕边缘。这是为了确保窗口不会覆盖到这些系统栏。
if (systemBarsInsets.left > 0) {
displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
}
if (systemBarsInsets.top > 0) {
displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
}
if (systemBarsInsets.right > 0) {
displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
}
if (systemBarsInsets.bottom > 0) {
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
}
}
//如果窗口类型是输入法(IME)
if (type == TYPE_INPUT_METHOD) {
//获取导航栏的Source
final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR);
//如果存在导航栏且其底部插入大于0
if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) {
// The IME can always extend under the bottom cutout if the navbar is there.
//调整displayCutoutSafeExceptMaybeBars.bottom,允许IME窗口扩展到底部显示切边以下。
//这是为了确保IME可以正常显示在有导航栏的设备上。
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
}
}
//如果窗口已附加到其父窗口并且不是全屏布局,则attachedInParent为true
final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
// 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;
// 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) {
mTempRect.set(outParentFrame); //临时存储父窗口的边界
//将父窗口的边界设置为与displayCutoutSafeExceptMaybeBars交集的边界,没有交集则置为空矩阵的边界
outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
//如果父窗口的边界交集后与原始边界不同,则表示父窗口的边界被切边裁剪了
outFrames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
}
//将输出显示边界设置与displayCutoutSafeExceptMaybeBars交集的边界,没有交集则置为空矩阵的边界
outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}
//检查attrs.flags中的FLAG_LAYOUT_NO_LIMITS位是否被设置。
//FLAG_LAYOUT_NO_LIMITS表示允许窗口布局到屏幕外侧。
final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
//检查当前窗口是否处于多窗口模式
final boolean inMultiWindowMode = WindowConfiguration.inMultiWindowMode(windowingMode);
// 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.
//noLimits是否为true即允许窗口布局到屏幕外)
//type是否不等于TYPE_SYSTEM_ERROR(表示窗口类型不是系统错误)
//inMultiWindowMode是否为false(表示窗口不在多窗口模式下)
if (noLimits && type != TYPE_SYSTEM_ERROR && !inMultiWindowMode) {
//设置输出显示的窗口边界
//[left,top]为左上角左边,[right,bottom]为右上角坐标,两个坐标构成一个矩形
//左上角左边设置为屏幕最小点,右下角坐标设置为屏幕最大点,即窗口将占据整个屏幕的边界
outDisplayFrame.left = MIN_X;
outDisplayFrame.top = MIN_Y;
outDisplayFrame.right = MAX_X;
outDisplayFrame.bottom = MAX_Y;
}
//如果compatScale不等于1,则hasCompatScale为true。这意味着存在一个兼容的缩放因子。
final boolean hasCompatScale = compatScale != 1f;
//父窗口的宽度和高度
final int pw = outParentFrame.width();
final int ph = outParentFrame.height();
//判断窗口的布局尺寸是否因为显示切边而扩展
//某些设备可能具有物理上的切边(如刘海屏、水滴屏等),这些切边区域不能用于显示内容。
//PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT作用就是为了确保应用程序的布局在具有切边的设备上仍然正确显示
//设置这个标志时,窗口的实际尺寸将大于其请求的尺寸,以便在切边区域周围填充空间。
final boolean extendedByCutout =
(attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
//请求的窗口宽度和高度
int rw = requestedWidth;
int rh = requestedHeight;
//窗口的位置坐标
float x, y;
//最终确定的窗口宽度和高度
int w, h;
// If the view hierarchy hasn't been measured, the requested width and height would be
// UNSPECIFIED_LENGTH. This can happen in the first layout of a window or in the simulated
// layout. If extendedByCutout is true, we cannot use the requested lengths. Otherwise,
// the window frame might be extended again because the requested lengths may come from the
// window frame.
//如果请求的窗口宽度或高度,是UNSPECIFIED_LENGTH或者窗口因为显示切边而扩展,
//那么其大于或等于0则使用attrs.width或attrs.height的值,否则使用父窗口的宽度和高度
if (rw == UNSPECIFIED_LENGTH || extendedByCutout) {
rw = attrs.width >= 0 ? attrs.width : pw;
}
if (rh == UNSPECIFIED_LENGTH || extendedByCutout) {
rh = attrs.height >= 0 ? attrs.height : ph;
}
//如果设置了FLAG_SCALED标志,代码会根据是否应用兼容性缩放来调整窗口的宽度和高度。
if ((attrs.flags & FLAG_SCALED) != 0) {
if (attrs.width < 0) {
w = pw;
} else if (hasCompatScale) {
w = (int) (attrs.width * compatScale + .5f);
} else {
w = attrs.width;
}
if (attrs.height < 0) {
h = ph;
} else if (hasCompatScale) {
h = (int) (attrs.height * compatScale + .5f);
} else {
h = attrs.height;
}
} else {
if (attrs.width == MATCH_PARENT) {
w = pw;
} else if (hasCompatScale) {
w = (int) (rw * compatScale + .5f);
} else {
w = rw;
}
if (attrs.height == MATCH_PARENT) {
h = ph;
} else if (hasCompatScale) {
h = (int) (rh * compatScale + .5f);
} else {
h = rh;
}
}
//如果存在兼容缩放因子,则调整窗口的x和y位置以考虑缩放。否则,直接使用原始的x和y位置。
if (hasCompatScale) {
x = attrs.x * compatScale;
y = attrs.y * compatScale;
} else {
x = attrs.x;
y = attrs.y;
}
//当前窗口是多窗口且设置PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME
//PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME的作用是窗口应该根据其父窗口的边界来调整自己的大小和位置。
//即控制子窗口在其父窗口内的布局行为,确保子窗口不会超出父窗口的边界。
if (inMultiWindowMode
&& (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) == 0) {
// Make sure window fits in parent frame since it is in a non-fullscreen task as
// required by {@link Gravity#apply} call.
//将窗口的宽度和高度分别设置为它们与父窗口宽度和高度中的较小值。
//这样做的目的是确保子窗口的大小不会超过其父窗口的大小。
w = Math.min(w, pw);
h = Math.min(h, ph);
}
// We need to fit it to the display if either
// a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
// for the taskless windows)
// b) If it's a secondary app window, we also need to fit it to the display unless
// FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
// screen, but SurfaceViews want to be always at a specific location so we don't fit it to
// the display.
//如果窗口不在多窗口模式下,或者窗口类型不是基础应用程序类型并且noLimits标志未设置,那么窗口需要适应显示。
final boolean fitToDisplay = !inMultiWindowMode
|| ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
// Set mFrame
//根据给定的重力属性、宽度、高度、父边界等,计算并设置outFrame。
//这里主要是确定窗口的位置。
Gravity.apply(attrs.gravity, w, h, outParentFrame,
(int) (x + attrs.horizontalMargin * pw),
(int) (y + attrs.verticalMargin * ph), outFrame);
// Now make sure the window fits in the overall display frame.
//如果窗口需要适应显示,那么使用Gravity.applyDisplay方法来调整outFrame(实际边界)的大小和位置在outDisplayFrame(显示边界)之内。
if (fitToDisplay) {
Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
}
//确保应用窗口的位置不会与设备的切边冲突。
//如果窗口的布局因为切边的存在而进行了扩展,并且窗口的边界超出了安全区域,那么它会调整窗口的位置,使其位于安全区域内。
if (extendedByCutout && !displayCutoutSafe.contains(outFrame)) {
mTempRect.set(outFrame);
// Move the frame into displayCutoutSafe.
final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL;
Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe,
mTempRect);
if (mTempRect.intersect(outDisplayFrame)) {
outFrame.union(mTempRect);
}
}
if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle()
+ " outFrames=" + outFrames
+ " windowBounds=" + windowBounds.toShortString()
+ " attachedWindowFrame=" + (attachedWindowFrame != null
? attachedWindowFrame.toShortString()
: "null")
+ " requestedWidth=" + requestedWidth
+ " requestedHeight=" + requestedHeight
+ " compatScale=" + compatScale
+ " windowingMode=" + WindowConfiguration.windowingModeToString(windowingMode)
+ " displayCutoutSafe=" + displayCutoutSafe
+ " attrs=" + attrs
+ " state=" + state
+ " requestedVisibilities=" + requestedVisibilities);
}
computeFrame方法是WindowState类中的一个重要方法,用于计算窗口的位置和大小。具体来说,它负责计算窗口的绘制区域,即窗口的内容在屏幕上实际显示的位置和大小。这个计算涉及到考虑窗口的位置、大小、布局参数以及可能的边界限制,确保窗口内容不会超出屏幕边界或被其他窗口遮挡。
在窗口管理器中,computeFrame方法通常会在以下情况被调用:
- 当窗口第一次被创建时,需要计算其初始位置和大小。
- 当窗口的布局参数或内容发生变化时,需要重新计算窗口的位置和大小。
- 当屏幕旋转或大小变化等系统事件发生时,需要调整所有窗口的位置和大小。
总之,computeFrame方法在Android窗口管理系统中起到了非常重要的作用,确保应用程序窗口能够正确地在屏幕上显示,并且适应不同的设备和系统事件, 为了计算小窗的位置,以及处理小窗内的View 的边界异常情况,
再来看看setFrames,更新窗口大小位置变化
win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
调用的是WindowState的setFrames方法
入参说明:
sTmpClientFrames:在computeFrames方法计算后的值,用于传递的窗口位置大小相关信息
requestedWidth、requestedHeight:这是应用程序请求的窗口宽度和高度。
代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java
void setFrames(ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
final WindowFrames windowFrames = mWindowFrames;
//用mTmpRect存储windowFrames.mParentFrame
mTmpRect.set(windowFrames.mParentFrame);
//LOCAL_LAYOUTd的值取决于配置项persist.debug.local_layout的值
if (LOCAL_LAYOUT) {
//将clientWindowFrames.frame的值设置为windowFrames.mCompatFrame。
windowFrames.mCompatFrame.set(clientWindowFrames.frame);
//将clientWindowFrames.frame的值设置为windowFrames.mFrame。
//将clientWindowFrames.displayFrame的值设置为windowFrames.mDisplayFrame。
//将clientWindowFrames.parentFrame的值设置为windowFrames.mParentFrame。
windowFrames.mFrame.set(clientWindowFrames.frame);
windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
if (hasCompatScale()) {
// The frames sent from the client need to be adjusted to the real coordinate space.
//如果存在兼容比例(通过调用hasCompatScale()方法检查),则对windowFrames中的Frame进行缩放,以适应实际的坐标空间。
//这里使用mGlobalScale作为缩放因子。
windowFrames.mFrame.scale(mGlobalScale);
windowFrames.mDisplayFrame.scale(mGlobalScale);
windowFrames.mParentFrame.scale(mGlobalScale);
}
} else {
//将clientWindowFrames.parentFrame的值设置为windowFrames.mParentFrame。
//将clientWindowFrames.displayFrame的值设置为windowFrames.mDisplayFrame。
//将clientWindowFrames.frame的值设置为windowFrames.mFrame。
windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
windowFrames.mFrame.set(clientWindowFrames.frame);
//将clientWindowFrames.frame的值设置为windowFrames.mCompatFrame。
windowFrames.mCompatFrame.set(windowFrames.mFrame);
if (hasCompatScale()) {
// Also, the scaled frame that we report to the app needs to be adjusted to be in
// its coordinate space.
//仅对windowFrames.mCompatFrame缩放
windowFrames.mCompatFrame.scale(mInvGlobalScale);
}
}
//isParentFrameClippedByDisplayCutout是一个Boolean
//如果为true,表示父窗口的边界被显示切边裁剪了;如果为false,表示父窗口的边界没有被显示切边裁剪。
windowFrames.setParentFrameWasClippedByDisplayCutout(
clientWindowFrames.isParentFrameClippedByDisplayCutout);
// Calculate relative frame
//将mRelFrame设置为与mFrame相同的值。这意味着mRelFrame现在存储了与mFrame相同的位置和尺寸信息。
windowFrames.mRelFrame.set(windowFrames.mFrame);
//获取当前窗口的父容器
WindowContainer<?> parent = getParent();
//初始化parentLeft和parentTop为0,表示父容器的左上角坐标
int parentLeft = 0;
int parentTop = 0;
if (mIsChildWindow) {//如果当前窗口是一个子窗口
//从父窗口的状态中获取其边界的位置信息。
parentLeft = ((WindowState) parent).mWindowFrames.mFrame.left;
parentTop = ((WindowState) parent).mWindowFrames.mFrame.top;
} else if (parent != null) {//如果当前窗口不是子窗口,并且父容器不为空
//获取父容器的边界位置
final Rect parentBounds = parent.getBounds();
parentLeft = parentBounds.left;
parentTop = parentBounds.top;
}
//调整mRelFrame的位置,使其相对于父容器的左上角有一个偏移
windowFrames.mRelFrame.offsetTo(windowFrames.mFrame.left - parentLeft,
windowFrames.mFrame.top - parentTop);
//如果请求的宽度、高度或者父框架与上次不同
if (requestedWidth != mLastRequestedWidth || requestedHeight != mLastRequestedHeight
|| !mTmpRect.equals(windowFrames.mParentFrame)) {
//更新最后请求的宽度和高度,并标记内容已更改
mLastRequestedWidth = requestedWidth;
mLastRequestedHeight = requestedHeight;
windowFrames.setContentChanged(true);
}
//如果窗口的类型是TYPE_DOCK_DIVIDER,并且边界的位置发生了变化,mMovedByResize标记为true。
//在分屏的场合,YPE_DOCK_DIVIDER窗口类型用于绘制这个分隔栏,并处理用户的触摸事件以实现大小调整功能。
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
mMovedByResize = true;
}
}
//如果当前窗口是壁纸,并且框架的宽度或高度发生了变化,更新壁纸的位置。
if (mIsWallpaper) {
final Rect lastFrame = windowFrames.mLastFrame;
final Rect frame = windowFrames.mFrame;
if (lastFrame.width() != frame.width() || lastFrame.height() != frame.height()) {
mDisplayContent.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
}
}
//更新windowFrames.mFrame
updateSourceFrame(windowFrames.mFrame);
//如果定义了LOCAL_LAYOUT,并且还没有边界,更新最后边界的位置。
if (LOCAL_LAYOUT) {
if (!mHaveFrame) {
// The first frame should not be considered as moved.
updateLastFrames();
}
}
//如果存在活动记录,并且当前窗口不是子窗口
if (mActivityRecord != null && !mIsChildWindow) {
//调用mActivityRecord.layoutLetterbox(this);来填充空白区域。也就是Letterbox模式
mActivityRecord.layoutLetterbox(this);
}
//设置mSurfacePlacementNeeded为true。
mSurfacePlacementNeeded = true;
//设置mHaveFrame为true,表示已经设置了边界
mHaveFrame = true;
}
其中WindowFrames 是一个表示窗口边框大小和位置的类。 WindowFrames 中有一些重要成员变量,用于描述不同的窗口区域。
mFrame表示窗口在屏幕上的位置和大小,是窗口管理和界面绘制的基础依据。
mVisibleFrame表示窗口可见区域的位置和大小,即除去状态栏和导航栏等系统 UI 元素后,窗口实际可以显示的区域。
mDecorFrame表示窗口装饰区域的位置和大小,即窗口除去实际内容区域外,包含的标题栏、边框、按钮等 UI 元素所占用的空间。
mDisplayFrame表示整个屏幕的可见区域的位置和大小,也就是说它包含了状态栏和导航栏等系统 UI 元素。
这些成员变量共同描述了窗口在屏幕中的位置和大小,并提供给其他模块使用,比如 WindowManager 和 View 系统。 在 Android Framework 中,WindowManagerService 会在每次窗口大小发生变化时,调用 WindowFrames 的 setFrames() 方法,更新这些成员变量的值。