1. 分屏窗口尺寸计算

1.1 窗口添加到WMS

Activity首次启动之后,在其resume阶段会将自己的Window添加到WMS:

    void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();//顶层DecorViewwm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}

ViewRootImpl.setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {//顶层DecorViewmView = view;.......//添加窗口到WMS,mWindow(Binder类型W,传给WMS以便WMS可以调用应用进程方法)res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,mTempInsets);//mTmpFrame为WMS计算得到的窗口尺寸setFrame(mTmpFrame);......}}
}

窗口通过addToDisplay添加到WMS之后,WMS会粗略计算当前窗口的尺寸

WMS.addWindow

public int addWindow(Session session, IWindow client, int seq,LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,Rect outContentInsets, Rect outStableInsets, Rect outOutsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState) {......final Rect taskBounds;final boolean floatingStack;if (atoken != null && atoken.getTask() != null) {taskBounds = mTmpRect;//重点:这里getBounds得到的尺寸是在Activity启动阶段调用setBounds设置的atoken.getTask().getBounds(mTmpRect);floatingStack = atoken.getTask().isFloating();} else {taskBounds = null;floatingStack = false;}//重点方法getLayoutHintLwif (displayPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;}......
}

以上同样只关注和窗口尺寸相关的代码,上述最重要的点是获取taskBounds,这个值会获取Activity启动阶段调用setBounds设置的值,对于分屏窗口来说当分屏窗口启动之后会调用resizeDockedStack设置分屏栈边界,这个方法最终调用的就是setBounds方法设置边界的值,WMS此处就是获取分屏窗口自己设置的值,接着在看getLayoutHintLw方法之前,先来看看DisplayFrames中的几个边界Rect

//DisplayFrames.java
//以模拟器尺寸Rect(0, 0 - 800, 480)为例/*** 真实屏幕的尺寸  Rect(0, 0 - 800, 480)*/public final Rect mOverscan = new Rect();/*** 除开状态栏,导航栏,输入法的内容区域  Rect(0, 57 - 800, 396)*/public final Rect mCurrent = new Rect();/*** 包含状态栏导航栏的屏幕可见区域  Rect(0, 0 - 800, 480)*/public final Rect mUnrestricted = new Rect();/*** 除开导航栏的内容区域  Rect(0, 0 - 800, 396)*/public final Rect mRestricted = new Rect();/** 除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396) */public final Rect mStable = new Rect();/*** 除开导航栏的内容区域  Rect(0, 0 - 800, 396)*/public final Rect mStableFullscreen = new Rect();

上述的几个边界值在窗口计算中非常重要,它们的值是在DisplayPolicy.beginLayoutLw中完成初始化的,Android窗口尺寸计算非常依赖这四个边界值,屏幕区域,状态栏区域,导航栏区域,输入法区域。

有了上述几个尺寸接下来看DisplayPolicy.getLayoutHintLw

public boolean getLayoutHintLw(LayoutParams attrs, Rect taskBounds,DisplayFrames displayFrames, boolean floatingStack, Rect outFrame,Rect outContentInsets, Rect outStableInsets,Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) {//这里获取和窗口尺寸计算相关的flag,//如WindowManager.LayoutParams.FLAG_FULLSCREEN,全屏//WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS等,半透明状态栏final int fl = PolicyControl.getWindowFlags(null, attrs);final int pfl = attrs.privateFlags;//获取和SystemUI相关 flag,//这些flag定义在View中,大多和是否全屏显示,是否隐藏状态栏,是否隐藏导航栏相关final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs);final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs);//屏幕旋转角度final int displayRotation = displayFrames.mRotation;//是否使用超出真实屏幕的底部像素值final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);if (useOutsets) {//这个值定义在configs.xml中(config_windowOutsetBottom),默认为0,int outset = mWindowOutsetBottom;if (outset > 0) {if (displayRotation == Surface.ROTATION_0) {outOutsets.bottom += outset;} else if (displayRotation == Surface.ROTATION_90) {outOutsets.right += outset;} else if (displayRotation == Surface.ROTATION_180) {outOutsets.top += outset;} else if (displayRotation == Surface.ROTATION_270) {outOutsets.left += outset;}}}final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) != 0;final boolean layoutInScreenAndInsetDecor = layoutInScreen&& (fl & FLAG_LAYOUT_INSET_DECOR) != 0;final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;if (layoutInScreenAndInsetDecor && !screenDecor) {if ((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {//包含状态栏导航栏的屏幕可见区域  Rect(0, 0 - 800, 480)outFrame.set(displayFrames.mUnrestricted);} else {//除开导航栏的内容区域  Rect(0, 0 - 800, 396)outFrame.set(displayFrames.mRestricted);}final Rect sf;//悬浮栈,窗口模式为自由窗口或者画中画的栈if (floatingStack) {sf = null;} else {//除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396)sf = displayFrames.mStable;}final Rect cf;if (floatingStack) {cf = null;} else if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {if ((fl & FLAG_FULLSCREEN) != 0) {//除开导航栏的内容区域  Rect(0, 0 - 800, 396)cf = displayFrames.mStableFullscreen;} else {//除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396)cf = displayFrames.mStable;}} else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {//真实屏幕的尺寸  Rect(0, 0 - 800, 480)cf = displayFrames.mOverscan;} else {//除开状态栏,导航栏,输入法的内容区域  Rect(0, 57 - 800, 396)cf = displayFrames.mCurrent;}if (taskBounds != null) {//taskBounds为Activity自己设置的大小,Rect(400, 57 - 800, 396)outFrame.intersect(taskBounds);}InsetUtils.insetsBetweenFrames(outFrame, cf, outContentInsets);InsetUtils.insetsBetweenFrames(outFrame, sf, outStableInsets);outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame).getDisplayCutout());return mForceShowSystemBars;} else {if (layoutInScreen) {//包含状态栏导航栏的屏幕可见区域  Rect(0, 0 - 800, 480)outFrame.set(displayFrames.mUnrestricted);} else {//除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396)outFrame.set(displayFrames.mStable);}if (taskBounds != null) {//taskBounds为Activity自己设置的大小,Rect(400, 57 - 800, 396)outFrame.intersect(taskBounds);}outContentInsets.setEmpty();outStableInsets.setEmpty();outDisplayCutout.set(DisplayCutout.NO_CUTOUT);return mForceShowSystemBars;}}

上述代码的核心其实就是DisplayFrames中的几个边界Rect和Activity类型窗口的栈边界,无非就是根据窗口添加的flag选用不同的Rect,然后做一下Rect的加减,

outFrame就是最终WMS计算得到的Activity的窗口尺寸,上述getLayoutHintLw方法计算的到的尺寸并非最终的尺寸,这里的尺寸更多可以理解为父窗口尺寸,例如如果添加的是一个Activity中的Dialog,这里outFrame得到的就是Dialog所属的Activity所在栈的边界值而并非Dialog自己的尺寸,后续将outFrame返回给应用进程之后Dialog拿到这个尺寸进行measure,此时才能得到Dialog的所要求的尺寸,但这任然可能不是最终尺寸,真正尺寸计算会在后续的WMS.relayout中进行。

这个值粗略计算得到的值会返回给Activity所在应用进程:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {//顶层DecorViewmView = view;.......//添加窗口到WMS,mWindow(Binder类型W,传给WMS以便WMS可以调用应用进程方法)res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,mTempInsets);//mTmpFrame为WMS计算得到的窗口尺寸setFrame(mTmpFrame);......}}
}

setFrame将WMS计算得到的Activity窗口尺寸保存在了ViewRootImpl的成员变量mWinFrame中:

   private void setFrame(Rect frame) {mWinFrame.set(frame);mInsetsController.onFrameChanged(frame);}

有了Activity窗口尺寸,接下来Activity要进行自己的测量工作了:

1.2 performTraversals

在添加窗口到WMS成功之后会执行View的三大流程,onMeasureonLayoutonDraw,这三大流程在ViewRootImplperformTraversals中执行,这个方法非常复杂,这里重点关注和窗口尺寸计算相关的代码:

private void performTraversals() {//DecorViewfinal View host = mView;......WindowManager.LayoutParams lp = mWindowAttributes;//这两个变量用来记录Activity窗口的宽高尺寸int desiredWindowWidth;int desiredWindowHeight;......//mWinFrame用来记录Activity窗口的尺寸,这个值是WMS计算的到的   Rect(400, 57 - 800, 396)Rect frame = mWinFrame;//首次进入if (mFirst) {mFullRedrawNeeded = true;mLayoutRequested = true;final Configuration config = mContext.getResources().getConfiguration();//是否使用屏幕的尺寸,TYPE_STATUS_BAR_PANEL,TYPE_INPUT_METHOD,TYPE_VOLUME_OVERLAY这三种类型窗口返回trueif (shouldUseDisplaySize(lp)) {Point size = new Point();mDisplay.getRealSize(size);desiredWindowWidth = size.x;desiredWindowHeight = size.y;} else {//将Activity宽高保存在这两个变量中,w = 400,h = 339desiredWindowWidth = mWinFrame.width();desiredWindowHeight = mWinFrame.height();}......//此方法用来处理可能出现的系统窗口,状态栏,导航栏,输入法,罗升阳老师文章中叫做边衬区域,暂时略过dispatchApplyInsets(host);} else {desiredWindowWidth = frame.width();desiredWindowHeight = frame.height();//mWidth和mHeight记录上次WMS为Activity计算得到的窗口宽高if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {//不等说明窗口尺寸发生了变化mFullRedrawNeeded = true;mLayoutRequested = true;windowSizeMayChange = true;}}...//对DecorView进行测量,宽高使用WMS.addWindow计算出来的尺寸windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth, desiredWindowHeight);......//这里有六个条件,满足其中之一就会对窗口进行再次计算if (mFirst || windowShouldResize || insetsChanged ||viewVisibilityChanged || params != null || mForceNextWindowRelayout) {......//针对窗口发生变化的情况进行再次计算relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);......}.....}

上面代码关于尺寸计算的代码中最重要的代码就是relayoutWindow,此方法用于计算窗口尺寸,insetsPending代表是否指定额外的边衬区域,默认为false:

relayoutWindow

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,boolean insetsPending) throws RemoteException {//缩放系数 等于1float appScale = mAttachInfo.mApplicationScale;......//调用WMS的relayout方法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, frameNumber,mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,mPendingMergedConfiguration, mSurfaceControl, mTempInsets);if (mSurfaceControl.isValid()) {mSurface.copyFrom(mSurfaceControl);} else {destroySurface();}......//设置WMS再次计算得到窗口尺寸setFrame(mTmpFrame);mInsetsController.onStateChanged(mTempInsets);return relayoutResult;}

1.3 WMS.relayoutWindow

public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,int requestedWidth, int requestedHeight, int viewVisibility, int flags,long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,SurfaceControl outSurfaceControl, InsetsState outInsetsState) {......if (viewVisibility != View.GONE) {//requestedWidth和requestedHeight是Activity窗口经过测量后得到的自己的想要的宽高win.setRequestedSize(requestedWidth, requestedHeight);}......//WMS核心功能,刷新系统UI,这里面会去计算窗口尺寸,//遍历系统所有窗口调用其WindowState的computeFrameLw方法mWindowPlacerLocked.performSurfacePlacement(true /* force */);......win.getCompatFrame(outFrame);......}

WMS.relayoutWindow将Activity窗口经过测量后得到的自己的想要的宽高保存在了其对应的WindowState中, mWindowPlacerLocked.performSurfacePlacement最终回调到WindowStatecomputeFrameLw方法去计算窗口的尺寸:

1.4 WindowState.computeFrameLw

每个WindowState内部都有一个类WindowFrames,这个类中提供了众多Rect来描述不同的区域边界,这些Rect的值有的来自DisplayFrames中,有的来自computeFrameLw的计算,DisplayFrames中的Rect描述的是窗口的基础边界,例如,屏幕宽高,状态栏,导航栏宽高,而一个窗口的真正尺寸还是需要自己经过计算得到,例如有的窗口没有状态栏或者导航栏,有的窗口有输入法等,这些情况都是要实际计算时才能知道。

先看WindowFrames中的窗口基础边界如何赋值的,Android系统中任意一个窗口的添加都会触发系统中所有窗口尺寸重新计算,每个窗口都会调用DisplayPolicylayoutWindowLw方法,

public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {......final WindowFrames windowFrames = win.getWindowFrames();final Rect pf = windowFrames.mParentFrame;final Rect df = windowFrames.mDisplayFrame;final Rect of = windowFrames.mOverscanFrame;final Rect cf = windowFrames.mContentFrame;final Rect vf = windowFrames.mVisibleFrame;final Rect dcf = windowFrames.mDecorFrame;final Rect sf = windowFrames.mStableFrame;......//经过各种条件判断,最后会对上述Rect赋值,赋的值全部来自DisplayFrames中.....//有了上述窗口的基础边界之后便开始窗口自己尺寸的计算了win.computeFrameLw();.....
}

上述赋值过程省略了,给一张最后赋值完成的图:

关注重点是窗口自己尺寸的计算:

@Overridepublic void computeFrameLw() {if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {return;}mHaveFrame = true;//获取窗口对应的task,非Activity窗口为空final Task task = getTask();//是否为全屏,非多窗口模式并且getBounds等于Display的getBounds,我们分析的是分屏窗口,所以这里为falsefinal boolean isFullscreenAndFillsDisplay = !inMultiWindowMode() && matchesDisplayBounds();//分屏不是悬浮窗口,此处为falsefinal boolean windowsAreFloating = task != null && task.isFloating();final DisplayContent dc = getDisplayContent();//这里getBounds返回分屏窗口启动是所设置的边界值,即为Rect(400, 57 - 800, 396)mInsetFrame.set(getBounds());final Rect layoutContainingFrame;final Rect layoutDisplayFrame;final int layoutXDiff;final int layoutYDiff;//是否有输入法窗口final WindowState imeWin = mWmService.mRoot.getCurrentInputMethodWindow();//当前窗口是否为输入法窗口的目标窗口final boolean isImeTarget =imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget();if (isFullscreenAndFillsDisplay || layoutInParentFrame()) {//分屏窗口不走这里,省略......} else {//这里的getDisplayedBounds就等于task.getBounds,即为Rect(400, 57 - 800, 396)mWindowFrames.mContainingFrame.set(getDisplayedBounds());if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {//冻屏窗口的情况,省略......}// 当前窗口是否为输入法窗口的目标窗口if (isImeTarget) {//需要计算输入法的情况,省略......}if (windowsAreFloating) {//悬浮窗口的情况,省略......}//获取窗口所在的栈,final TaskStack stack = getStack();if (inPinnedWindowingMode() && stack != null&& stack.lastAnimatingBoundsWasToFullscreen()) {//画中画模式的窗口,省略......}//mWindowFrames.mDisplayFrame值为 Rect(0, 0 - 800, 480)layoutDisplayFrame = new Rect(mWindowFrames.mDisplayFrame);//mWindowFrames.mContainingFrame值为 Rect(400, 57 - 800, 396)mWindowFrames.mDisplayFrame.set(mWindowFrames.mContainingFrame);//layout得到的尺寸和实际尺寸的偏移量,大多数情况为0layoutXDiff = mInsetFrame.left - mWindowFrames.mContainingFrame.left;layoutYDiff = mInsetFrame.top - mWindowFrames.mContainingFrame.top;layoutContainingFrame = mInsetFrame;//mTmpRect保存屏幕尺寸,为 Rect(0, 0 - 800, 480)mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);subtractInsets(mWindowFrames.mDisplayFrame, layoutContainingFrame, layoutDisplayFrame,mTmpRect);//layoutInParentFrame代表当前计算尺寸的是否为子窗口if (!layoutInParentFrame()) {subtractInsets(mWindowFrames.mContainingFrame, layoutContainingFrame,mWindowFrames.mParentFrame, mTmpRect);subtractInsets(mInsetFrame, layoutContainingFrame, mWindowFrames.mParentFrame,mTmpRect);}layoutDisplayFrame.intersect(layoutContainingFrame);}//对于当前窗口为子窗口或者全屏的情况mWindowFrames.mContainingFrame保存的是父窗口的尺寸,//否则mWindowFrames.mContainingFrame保存的就是自己的尺寸final int pw = mWindowFrames.mContainingFrame.width();final int ph = mWindowFrames.mContainingFrame.height();//mRequestedWidth和mRequestedHeight是Activity自己测量出来的自己的DecorView的宽高//WMS要结合这个应用自己请求的宽高来计算窗口的尺寸if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {mLastRequestedWidth = mRequestedWidth;mLastRequestedHeight = mRequestedHeight;mWindowFrames.setContentChanged(true);}//mWindowFrames.mFrame保存的是最终窗口计算出来的实际尺寸,computeFrameLw方法最终要计算的就是它的值//目前这里还是0final int fw = mWindowFrames.mFrame.width();final int fh = mWindowFrames.mFrame.height();//计算mFrame的核心方法,layoutContainingFrame代表的是父窗口尺寸区域,layoutDisplayFrame代表当前窗口栈所在区域//大多数情况下这两个值都相等applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);// 计算超出屏幕区域的部分,省略.....if (windowsAreFloating && !mWindowFrames.mFrame.isEmpty()) {//悬浮窗口的情况,省略......} else if (mAttrs.type == TYPE_DOCK_DIVIDER) {//窗口类型为TYPE_DOCK_DIVIDER,省略......} else {//mContentFrame的值来自DisplayFrames,为除开状态栏,导航栏的区域 Rect(0, 57 - 800, 396)//这里mContentFrame取值为mContentFrame和mFrame中较小的区域mWindowFrames.mContentFrame.set(Math.max(mWindowFrames.mContentFrame.left, mWindowFrames.mFrame.left),Math.max(mWindowFrames.mContentFrame.top, mWindowFrames.mFrame.top),Math.min(mWindowFrames.mContentFrame.right, mWindowFrames.mFrame.right),Math.min(mWindowFrames.mContentFrame.bottom, mWindowFrames.mFrame.bottom));//mVisibleFrame的值来自DisplayFrames,为除开状态栏,导航栏的区域 Rect(0, 57 - 800, 396)//这里mVisibleFrame取值为mVisibleFrame和mFrame中较小的区域mWindowFrames.mVisibleFrame.set(Math.max(mWindowFrames.mVisibleFrame.left, mWindowFrames.mFrame.left),Math.max(mWindowFrames.mVisibleFrame.top, mWindowFrames.mFrame.top),Math.min(mWindowFrames.mVisibleFrame.right, mWindowFrames.mFrame.right),Math.min(mWindowFrames.mVisibleFrame.bottom, mWindowFrames.mFrame.bottom));//mStableFrame的值来自DisplayFrames,为除开状态栏,导航栏的区域 Rect(0, 57 - 800, 396)//这里mStableFrame取值为mStableFrame和mFrame中较小的区域mWindowFrames.mStableFrame.set(Math.max(mWindowFrames.mStableFrame.left, mWindowFrames.mFrame.left),Math.max(mWindowFrames.mStableFrame.top, mWindowFrames.mFrame.top),Math.min(mWindowFrames.mStableFrame.right, mWindowFrames.mFrame.right),Math.min(mWindowFrames.mStableFrame.bottom, mWindowFrames.mFrame.bottom));//上述三个区域mContentFrame,mVisibleFrame,mStableFrame最后得到的值相等//mFrame为计算出来的窗口实际尺寸}if (isFullscreenAndFillsDisplay && !windowsAreFloating) {//全屏并且非悬浮窗口,省略......}if (mAttrs.type == TYPE_DOCK_DIVIDER) {//类型为TYPE_DOCK_DIVIDER的窗口,省略......} else {//mTmpRect保存了屏幕的尺寸,Rect(0, 0 - 800, 480),将这个尺寸保存到DisplayContent中去getDisplayContent().getBounds(mTmpRect);mWindowFrames.calculateInsets(windowsAreFloating, isFullscreenAndFillsDisplay, mTmpRect);}......//将mFrame保存到mCompatFramemWindowFrames.mCompatFrame.set(mWindowFrames.mFrame);......if (mIsWallpaper && (fw != mWindowFrames.mFrame.width()|| fh != mWindowFrames.mFrame.height())) {//壁纸窗口......}......}

computeFrameLw方法的核心是计算出mWindowFrames.mFrame的值,这个值就是窗口的实际尺寸,计算方法applyGravityAndUpdateFrame.

1.5 mFrame计算

对于分屏窗口,这里接收的两个参数相等都是分屏栈的大小 Rect(400, 57 - 800, 396)

private void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {  // Rect(400, 57 - 800, 396)final int pw = containingFrame.width();final int ph = containingFrame.height();final Task task = getTask();//当前窗口是否占满父容器,对分屏窗口来说inNonFullscreenContainer为true,即不会占满父容器final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds();//是否允许当前窗口的大小无限制,对分屏窗口来说noLimits为falsefinal boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;//是否占满整个屏幕,对分屏窗口来说fitToDisplay为falsefinal boolean fitToDisplay = (task == null || !inNonFullscreenContainer)|| ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);float x, y;int w,h;//是否运行在兼容模式,这里为falsefinal boolean inSizeCompatMode = inSizeCompatMode();//是否指定窗口缩放系数if ((mAttrs.flags & FLAG_SCALED) != 0) {//没有指定,省略.......} else {if (mAttrs.width == MATCH_PARENT) {//如果分屏窗口宽指定为MATCH_PARENT,则w等于栈宽度w = pw;} else if (inSizeCompatMode) {//兼容模式w = (int)(mRequestedWidth * mGlobalScale + .5f);} else {//否则w等于分屏应用请求的宽度w = mRequestedWidth;}if (mAttrs.height == MATCH_PARENT) {//如果分屏窗口高指定为MATCH_PARENT,则h等于栈高度h = ph;} else if (inSizeCompatMode) {//兼容模式h = (int)(mRequestedHeight * mGlobalScale + .5f);} else {//否则h等于分屏应用请求的高度h = mRequestedHeight;}}if (inSizeCompatMode) {//兼容模式   x = mAttrs.x * mGlobalScale;y = mAttrs.y * mGlobalScale;} else {x = mAttrs.x;y = mAttrs.y;}//非占满父容器,并且非子窗口if (inNonFullscreenContainer && !layoutInParentFrame()) {//这里是确保窗口的宽高是合理的,对于Activity类型窗口,其最大宽高只能等于所在栈的宽高w = Math.min(w, pw);h = Math.min(h, ph);}// 给mFrame赋值,这里会考虑当前窗口的gravity,x,y的位置,Margin来最终计算mFrameGravity.apply(mAttrs.gravity, w, h, containingFrame,(int) (x + mAttrs.horizontalMargin * pw),(int) (y + mAttrs.verticalMargin * ph), mWindowFrames.mFrame);if (fitToDisplay) {//这里是确定计算出来的窗口尺寸在屏幕区域之内Gravity.applyDisplay(mAttrs.gravity, displayFrame, mWindowFrames.mFrame);}//给mCompatFrame设置mFrame同样的值mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame);if (inSizeCompatMode) {mWindowFrames.mCompatFrame.scale(mInvGlobalScale);}}

窗口的尺寸计算到此就完成了,最终的结果是保存在mFrame中,最后这个值会返回给APP进程,APP进程ViewRootImpl中调用的relayoutWindow方法主要目的就是请求WMS对窗口进行计算得到mFrame的值,最后APP将此值保存在了ViewRootImpl的成员变量mWinFrame中。

总结,窗口尺寸的计算依赖应用进程和WMS协同,应用进程首次添加到WMS时,WMS返回一个不保证正确的尺寸值(这个尺寸对于Activity窗口来说一般为其栈的边界,非Activity窗口一般为屏幕尺寸),应用进程用这个尺寸对自己的根View进行测量,测量完成之后通过WMS.relayout对应用请求的尺寸进行再次计算,因为WMS这边要考虑屏幕尺寸,状态栏,导航栏等,所以WMS这一步计算是保证应用请求的尺寸是合理的。

AndroidQ 分屏窗口尺寸计算 (WMS部分)相关推荐

  1. AndroidQ 分屏窗口模式 (AMS部分)

    1. 多窗口 1.1 栈 Android7.0开始支持多窗口,多窗口分为三种,画中画,分屏,自由窗口,多窗口的核心原理其实就是分栈和设置栈边界, 分栈即把不同窗口模式下的Activity放在不同的Ac ...

  2. 怎样调整vim分屏窗口的宽度和高度?

    怎样调整vim分屏窗口的宽度和高度? 太大了怎么办 小熊科技视... 加载更多- 96980人看了这个视频 1 2 3 4 5 6 7 分步阅读 vim编辑器可以上下.左右随意分屏,特别有用.但是默认 ...

  3. Ubuntu上实现多分屏窗口管理

    Ubuntu上实现多分屏窗口管理 Gnome与Gnome shell拓展插件 Put Windows Gnome与Gnome shell拓展插件 Gnome是ubuntu的默认桌面环境,用户可以通过安 ...

  4. 三星android分屏视图怎么关闭,三星S9/S9+分屏窗口设置及使用教程

    今天来告诉大家在三星S9/S9+上面的另一种多窗口解决方案--分屏窗口. 分屏窗口呢也一共提供了两种方式+一种隐藏方式,分别是分屏及对齐窗口,下面呢就跟大家一一道来. 类型I[分屏视图] 首先是打开这 ...

  5. 【分屏】2秒钟实现 Windows窗口多分屏的进阶技巧

    当我们在工作的时候难免会同时打开多个页面,尤其是摘抄文本,对照报告内容,复制文件到某几个文件夹中时. 这个时候就得重复几个动作,切换到工作窗口.操作.再打开另一个工作窗口.操作.切换到原来窗口 --- ...

  6. linux 工具——终端分屏与vim分屏

    preface:不知不觉在终端下学习干活一年多,终端开多了成了习惯,之前嫌麻烦没用分屏,而当真正用起来比想象中的简单,终端下的分屏命令 tmux及vim自带分屏命令vsp都相当不错,加快干活效率. 1 ...

  7. ipad怎么和mac分屏_将Mac屏幕扩展到iPad有多好用?我甚至有了入手iPad Pro的冲动...

    这次我手机先不升 iOS 13,不够稳,但 iPadOS 更新这么多,我是绝对会升的. 手捧 11 英寸 iPad Pro 几个月,但却一直只把它当做大号 iPod Touch 的朋友在看完<i ...

  8. mac 桌面分屏软件_6款好用的Mac分屏软件推荐

    桌面分屏是我们工作中经常会用到的功能,但是Mac上自带的分屏功能非常有限,必须进入全屏才能使用,管理窗口时使用起来并不方便.本文中我们就来推荐几款好用的Mac分屏软件,让你的窗口管理更简单 Magne ...

  9. duilib 分屏显示bug

    在分屏窗口中最大化,点击任务栏切换最大,最小化,出现图1情况. 图1 Demo说明:从WindowImplBase派生出来的窗体,未重写父类方法. 注意:分屏的显示器分辨率大于电脑主显示的分辨率. B ...

最新文章

  1. python中的类的成员变量以及property函数
  2. 2-4. BCD解密(10)
  3. Linux脚本点空格,linux – 在bash脚本中使用引号和空格的awk
  4. AUTOSAR从入门到精通番外篇(二)-一文读懂ld链接脚本文件
  5. ssh免密登陆机制示意图
  6. 做系统的U盘如何格式化
  7. 数据算法之二叉树删除(BinaryTreeL Remove)的Java实现
  8. bootstrap modal远程加载的两种方式
  9. openssl CRL证书
  10. gitHub报错10054、443解决办法
  11. 什么是驻点和拐点_驻点、极值点、拐点间的“爱恨情仇”
  12. 拼多多的正品险是个假保险?
  13. 计算机专业术语(个人学习总结,不定期更新)
  14. 如何建设网站才有利于网站优化
  15. 2021广东高考成绩排名如何查询,2021广东省地区高考成绩排名查询,广东省高考各高中成绩喜报榜单...
  16. 腾讯零反射全动态Android插件框架Shadow解析
  17. Anaconda Navigator Applications 缺少Notebook等应用
  18. 三国刘备十大名言:三分天下要靠“混
  19. vue 网格组件_简单的Vue组件可显示带有事件的月网格日历
  20. java设备imei号_Android 获取imei号码,获取手机型号和系统版本号

热门文章

  1. python 多因素方差分析_SPSS分析技术:多元方差分析
  2. 针对面试官提出的WPF逻辑树和视觉树
  3. 编程题--疯狂序列----京东大数据笔试
  4. 程序中美元符号$是什么
  5. 针对m3u8视频加密的一些尝试
  6. springboot整合mybatis,使用逆向工程和使用通用mapper的方式
  7. 计算机视觉笔记及资料整理(含图像分割、目标检测)
  8. Elastic-Job原理--任务失败转移(五)
  9. 重学JavaSE —— Map、Set、Iterator(迭代器) 简单笔记
  10. linux下编译ts工程,linux下搭建生成HLS所需的.ts和.m3u8文件