多窗口的核心原理其实就是分栈和设置栈边界

【Id:0】Home Stack,这个是Launcher所在的Stack。 其实还有一些系统界面也运行在这个Stack上,例如近期任务
【Id:1】FullScren Stack,全屏的Activity所在的Stack。 但其实在分屏模式下,Id为1的Stack只占了半个屏幕。
【Id:2】Freeform模式的Activity所在Stack
【Id:3】Docked Stack 下文中我们将看到,在分屏模式下,屏幕有一半运行了一个固定的应用,这个就是这里的Docked Stack
【Id:4】Pinned Stack 这个是画中画Activity所在的Stack

AMS和WMS在应用窗口这块是有对应关系:
ActivityDisplay----------DisplayContent
ActivityStack------------TaskStack
TaskRecord-------------Task
ActivityRecord----------AppWindowToken
AMS这边依次为:
ActivityDisplay->ActivityStack->TaskRecord->ActivityRecord
WMS依次分为:
DisplayContent(TaskStackContainer)->TaskStack->Task->AppWindowToken

/**
这里我们分析的是Activity的DecorView窗口视图添加的逻辑,所以此时不存在父视图的概念,
不会走到这里,此时的panelParentView为null
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//顶层DecorView
mView = 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);
......
}
}
}
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) {
int res = mPolicy.checkAddPermission(attrs, appOp);
//...
synchronized(mWindowMap) {
//...
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent == null) {
Slog.w(TAG, "Attempted to add window to a display that does not exist: "
+ displayId + ". Aborting.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
//...
WindowToken token = mTokenMap.get(attrs.token);
//...
// 新的WindowState对象在其构造函数中根据窗口类型初始化了其主序mBaseLayer和mSubLayer
win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
......
return res;
}
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
...
//如果是子窗口,使用它依附的窗口类型来计算
if ((mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW)) {
mBaseLayer = mPolicy.windowTypeToLayerLw(
attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET;//计算mBaseLayer,关键点1
//表示子窗口和父窗口的相对位置
mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);//计算mSubLayer,关键点2
...
} else {//非子窗口,直接使用窗口类型来计算
//注:TYPE_LAYER_MULTIPLIER的值是10000,TYPE_LAYER_OFFSET的值是1000
mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
* WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET;
mSubLayer = 0;//无效,仅在子窗口中有用
...
}
...
}
//根据类型返回窗口的种类,从1-31,
public int windowTypeToLayerLw(int type) {
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return 2;
}
switch (type) {
case TYPE_UNIVERSE_BACKGROUND:
return 1;
case TYPE_PRIVATE_PRESENTATION:
return 2;
case TYPE_WALLPAPER:
// wallpaper is at the bottom, though the window manager may move it.
return 2;
case TYPE_PHONE:
return 3;
case TYPE_SEARCH_BAR:
return 4;
...
case TYPE_HIDDEN_NAV_CONSUMER:
return 30;
/// M:JB migration
case TYPE_TOP_MOST:
return 31;
}
Log.e(TAG, "Unknown window type: " + type);
return 2;
}
public int subWindowTypeToLayerLw(int type) {
switch (type) {
case TYPE_APPLICATION_PANEL:
case TYPE_APPLICATION_ATTACHED_DIALOG:
return APPLICATION_PANEL_SUBLAYER;//等于1
case TYPE_APPLICATION_MEDIA:
return APPLICATION_MEDIA_SUBLAYER;//等于-2
case TYPE_APPLICATION_MEDIA_OVERLAY:
return APPLICATION_MEDIA_OVERLAY_SUBLAYER;//等于-1
case TYPE_APPLICATION_SUB_PANEL:
return APPLICATION_SUB_PANEL_SUBLAYER;//等于2
}
Log.e(TAG, "Unknown sub-window type: " + type);
return 0;
}
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) {
......
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;
}
//重点方法getLayoutHintLw
if (displayPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,
outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
}
return res;
}
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.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();
private void performTraversals() {
//DecorView
final 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这三种类型窗口返回true
if (shouldUseDisplaySize(lp)) {
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
//将Activity宽高保存在这两个变量中,w = 400,h = 339
desiredWindowWidth = 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);
......
}
.....
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
//缩放系数 等于1
float 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;
}
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);
......
}
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();
.....
}
computeFrameLw方法的核心是计算出mWindowFrames.mFrame的值,这个值就是窗口的实际尺寸
@Override
public void computeFrameLw() {
if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
return;
}
mHaveFrame = true;
//获取窗口对应的task,非Activity窗口为空
final Task task = getTask();
//是否为全屏,非多窗口模式并且getBounds等于Display的getBounds,我们分析的是分屏窗口,所以这里为false
final boolean isFullscreenAndFillsDisplay = !inMultiWindowMode() && matchesDisplayBounds();
//分屏不是悬浮窗口,此处为false
final 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得到的尺寸和实际尺寸的偏移量,大多数情况为0
layoutXDiff = 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方法最终要计算的就是它的值
//目前这里还是0
final 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保存到mCompatFrame
mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame);
......
if (mIsWallpaper && (fw != mWindowFrames.mFrame.width()
|| fh != mWindowFrames.mFrame.height())) {
//壁纸窗口
......
}
......
}
窗口的尺寸计算到此就完成了,最终的结果是保存在mFrame中,最后这个值会返回给APP进程,APP进程ViewRootImpl中调用的relayoutWindow方法主要目的就是请求WMS对窗口进行计算得到mFrame的值,最后APP将此值保存在了ViewRootImpl的成员变量mWinFrame中。
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为false
final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
//是否占满整个屏幕,对分屏窗口来说fitToDisplay为false
final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
|| ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
float x, y;
int w,h;
//是否运行在兼容模式,这里为false
final 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来最终计算mFrame
Gravity.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);
}
}
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
我想用这两种语言中的任何一种(最好是ruby)制作一个窗口管理器。老实说,除了我需要加载某种X模块外,我不知道从哪里开始。因此,如果有人有线索,如果您能指出正确的方向,那就太好了。谢谢 最佳答案 XCB,X的下一代API使用XML格式定义X协议(protocol),并使用脚本生成特定语言绑定(bind)。它在概念上与SWIG类似,只是它描述的不是CAPI,而是X协议(protocol)。目前,C和Python存在绑定(bind)。理论上,Ruby端口只是编写一个从XML协议(protocol)定义语言到Ruby的翻译器的问题。生
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定
我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项