Window大小位置测量以及View绘制流程

ViewRootImpl开始去寻找DecorView的测量绘制流程,过程涉及到Window的大小位置的测量。基于api 25(7.1.1)。

DecorView 的测量、布局、绘制流程

下图是整体的绘制的流程。
View绘制流程
第1步,这里可以简单理解是系统会ViewRootImpl#performTraversals来开始绘制。
performTraversals整体做了4件事情:

  1. performMeasure:用来测量View大小
  2. relayoutWindow:用来测量Window大小
  3. performLayout:用来对View布局
  4. performDraw:处理View的绘制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//ViewRootImpl.java
private void performTraversals() { //开始遍历绘制
// cache mView since it is used so much below...
final View host = mView;
Rect frame = mWinFrame; //mWinFrame 代表的WMS 测量的Window 大小
if (host == null || !mAdded)
return;
if (mFirst) { //mFirst 最开始true,代表的是第一次执行performTraversals
host.dispatchAttachedToWindow(mAttachInfo, 0); //向子类分发attachWindow 事件,只有第一次的时候才会。这里其实是很重要的把mAttachInfo 传进去了
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
} else { //判断大小是否变化了
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
if (viewVisibilityChanged) { //判断可见性是否变化了
}
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
host.clearAccessibilityFocus();
}
boolean insetsChanged = false; //边衬区是否变化了
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); //是否需要layout
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else { //处理的是边衬区是否变化
}
windowSizeMayChange |= measureHierarchy(host, lp, res, //开始测量大小
desiredWindowWidth, desiredWindowHeight);
}
if (mFirst || mAttachInfo.mViewVisibilityChanged) { //第一次或者可见性变化
}
if (mFirst || windowShouldResize || insetsChanged || ////第一次进来、window 需要重新测量、边衬区改变、view可见性改变、params 不为null 体现的是Window 的属性变化等、强制刷新
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
try {
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); //调整Window 大小
final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
final boolean stableInsetsChanged = !mPendingStableInsets.equals(
mAttachInfo.mStableInsets);
final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
final boolean surfaceSizeChanged = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
final boolean alwaysConsumeNavBarChanged =
mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
//处理边衬区的变化
if (!hadSurface) { //处理Surface
} else if (!mSurface.isValid()) {
} else if ((surfaceGenerationId != mSurface.getGenerationId()
|| surfaceSizeChanged)
&& mSurfaceHolder == null
&& mAttachInfo.mHardwareRenderer != null) {
}
} catch (RemoteException e) {
}
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
if (mSurfaceHolder != null) { //代表已经拥有了Surface
if (mSurface.isValid()) {
} else if (hadSurface) {
}
}
final ThreadedRenderer hardwareRenderer = mAttachInfo.mHardwareRenderer;
if (hardwareRenderer != null && hardwareRenderer.isEnabled()) { //是否使用硬件加速
}
if (!mStopped || mReportNextDraw) {
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) { //focus 改变、大小发生变化、边衬区发生变化、更新配置
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //重新测量大小
layoutRequested = true;
}
}
} else {
maybeHandleWindowMove(frame);
}
if (didLayout) { //是否需要处理布局
performLayout(lp, mWidth, mHeight); //开始布局
}
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
if (computesInternalInsets) {
}
if (mFirst) { //第一次获取焦点
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
} else {
}
}
}
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
if (regainedFocus) {
mLostWindowFocus = false;
} else if (!hasWindowFocus && mHadWindowFocus) {
mLostWindowFocus = true;
}
if (changedVisibility || regainedFocus) {
// Toasts are presented as notifications - don't present them as windows as well
boolean isToast = (mWindowAttributes == null) ? false
: (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);
if (!isToast) {
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
}
if (!cancelDraw && !newSurface) {
performDraw(); //处理绘制
} else {
if (isViewVisible) {
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
}
}
mIsInTraversal = false;
}

第35行开始测量View,第83行是测量完Window以后发现大小变化以后重新测量View
第43行测量Window
第91行开始对View布局。
第124行开始绘制View

第3步,ViewRootImpl#measureHierarchy测量布局大小。这里的测量跟普通View的测量不一样。自定义View那些事之前有涉及到。

第4步,ViewRootImpl#relayoutWindow用来测量Window大小。

这里可以从多个情况去看里面的实现:

  1. 最普通的Activity打开
  2. 设置全屏的Activity(WindowManager.LayoutParams.FLAG_FULLSCREEN)
  3. 隐藏Navigation的情况

第5步,Session#relayout最终调用到服务端计算。

1
2
3
4
5
6
7
//IWindowSession.aidl
int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
int flags, out Rect outFrame, out Rect outOverscanInsets,
out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets,
out Rect outOutsets, out Rect outBackdropFrame, out Configuration outConfig,
out Surface outSurface);

注意从outFrame开始后面的所有参数都是out 类型的,即服务端计算好以后会将值返回给客户端。

第6步,WindowManagerService#relayoutWindow的逻辑,主要是创建Surface的逻辑以及计算Window大小位置(这个会在下面单独分析)。

第8步,ViewRootImpl#performDraw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//ViewRootImpl.java
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { //脏区域不为空、在动画中、
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this); //使用硬件绘制
} else {
if (mAttachInfo.mHardwareRenderer != null &&
!mAttachInfo.mHardwareRenderer.isEnabled() &&
mAttachInfo.mHardwareRenderer.isRequested()) { //如果正在请求硬件绘制,会重新初始化
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { //使用软件绘制
return;
}
}
}
}

会分为硬件和软件两种绘制方法,软件绘制是通过Surface#lockCanvas获取到Canvas以后调用View#draw进行绘制。硬件绘制后续展示。

总结:到此结束了View的测量(measure)、布局(layout)和绘制(draw)。

Window 测量流程

主要是对上面第6步的展开分析,View最终有多大需要通过测量Window以后才能准确知道。最终展示的大小可以通过adb shell dumpsys window windows查看(查看Frames:节点就行,里面会有containing=parent=display=等)。
先来分析三个类中关于Window大小:

  1. 了解ViewRootImpl中成员变量的含义
  2. 了解WindowState中成员变量的含义
  3. 了解PhoneWindowManager中成员变量的含义

ViewRootImpl中成员变量的含义

1
2
3
4
5
6
7
8
9
10
11
12
//ViewRootImpl.java
int mWidth; //代表实际展示的宽
int mHeight; //代表实际展示的高
final Rect mWinFrame; //WMS 提供的Window 的大小
final Rect mPendingOverscanInsets = new Rect();
final Rect mPendingVisibleInsets = new Rect();
final Rect mPendingStableInsets = new Rect();
final Rect mPendingContentInsets = new Rect();
final Rect mPendingOutsets = new Rect();
final Rect mPendingBackDropFrame = new Rect();

上面7个Rect属性的表达方式不同:mWinFrame的4个值分别代表的是左上角和右下角的坐标。而以Inset(这里翻译成边衬区)结尾的几个值代表的是距离左、上、右、下的值。

  • mPendingVisibleInsets:假设一个Window 的四周都有被一块类似输入框的区域遮住(注意是遮住,那块区域还是存在),得到中间的那一块区域就称为可见区域,而被剔除出来的区域所组成的区域就称为可见边衬区域(Visible Insets)
  • mPendingContentInsets:假设一个Window 的四周都有一块类似状态栏的区域,那么将这些区域剔除之后,得到中间的那一块区域就称为内容区域,而被剔除出来的区域所组成的区域就称为内容边衬区域(Content Insets)(来自老罗博客,最下面有地址)
  • mPendingStableInsets:代表的是剔除System Bar 所占据的位置以后的区域(不管Status Bar 和Navigation Bar 是否可见都会计算在内)。

WindowState中成员变量的含义

之前(WMS之addView流程)已经知道了WindowState是WMS 用来标识一个Window的。这里主要去看WindowState中关于大小和位置的参数。

Field 含义
mRequestedWidth
mRequestedHeight
mLastRequestedWidth
mLastRequestedHeight
应用请求Window 的大小
mVisibleInsets
mLastVisibleInsets
mVisibleInsetsChanged
Window 的可见边衬区
mContentInsets
mLastContentInsets
mContentInsetsChanged
Window 的内容边衬区
mOverscanInsets
mLastOverscanInsets
mOverscanInsetsChanged
Window 的过扫描边衬区
mStableInsets
mLastStableInsets
mStableInsetsChanged
Window 被系统Window 遮住的固定边衬区
mOutsets
mLastOutsets
mOutsetsChanged
不在Window 的Surface 区域但可以绘制的区域(不理解)
mFrame
mLastFrame
mFrameSizeChanged
最后展示的区域
mCompatFrame
mContainingFrame
mParentFrame
mDisplayFrame
mOverscanFrame
mStableFrame
mDecorFrame
mContentFrame
mVisibleFrame
mOutsetFrame
mInsetFrame

PhoneWindowManager中成员变量的含义

Overscan 区域的意思:Overscan 代表“过扫描”,有些显示屏(比如电视)可能存在失真现象,且越靠近边缘越严重。为了避开这个“固有缺陷”,不少厂商都把扫描调整到画面的5%~10%,这样造成的结果就是画面很可能显示不全,损失的部分称为“Overscan”。

Field 含义
mOverscanScreenLeft
mOverscanScreenTop
mOverscanScreenWidth
mOverscanScreenHeight
屏幕的真实坐标,包含了过扫描的区域。代表的是左上角坐标和屏幕宽高
mUnrestrictedScreenLeft
mUnrestrictedScreenTop
mUnrestrictedScreenWidth
mUnrestrictedScreenHeight
不包含过扫描区域,包含了状态栏的区域的大小
mRestrictedOverscanScreenLeft
mRestrictedOverscanScreenTop
mRestrictedOverscanScreenWidth
mRestrictedOverscanScreenHeight
与mOverscanScreen* 类似,但是可以移动到过扫描区域
mRestrictedScreenLeft
mRestrictedScreenTop
mRestrictedScreenWidth
mRestrictedScreenHeight
屏幕当前的区域,但是不包含状态栏
mSystemLeft
mSystemTop
mSystemRight
mSystemBottom
所有可见的UI元素区域(包含了系统状态栏区域)
mStableLeft
mStableTop
mStableRight
mStableBottom
不包含状态栏的区域(不管状态栏是否可见)
mStableFullscreenLeft
mStableFullscreenTop
mStableFullscreenRight
mStableFullscreenBottom
与mStable*类似。不包含状态栏的区域
mCurLeft
mCurTop
mCurRight
mCurBottom
包含状态栏、输入法的内容区域
mContentLeft
mContentTop
mContentRight
mContentBottom
内容区域
mVoiceContentLeft
mVoiceContentTop
mVoiceContentRight
mVoiceContentBottom
语音区域
mDockLeft
mDockTop
mDockRight
mDockBottom
输入法区域

PhoneWindowManager中还有一系列关于WindowState计算的值。下面的9个值都是Rect,代表的都是左上角和右下角的坐标。

Field 含义 备注
mTmpParentFrame 父窗口区域 一般就是mTmpDisplayFrame
mTmpDisplayFrame Window完整展示区域 StatusBar一般是全屏,因为StatusBar是下拉展示全屏
mTmpOverscanFrame 左上角(overscanLeft,overscanTop)
右下角(displayWidth-overscanRight,displayHeight-overscanBottom)
去掉overscan 区域以后的区域
mTmpContentFrame Window内容区域
mTmpVisibleFrame Window可见区域
mTmpDecorFrame
mTmpStableFrame 左上角(0,statusBarHeight)
右下角(displayWidth,displayHeight-navigationBarHeight)
没有计算overscan
mTmpNavigationFrame NavigationBar对应的坐标
mTmpOutsetFrame Surface区域外

流程

首先上一张测量的流程图。
WMS 中Window 测量流程

第1步,从WindowManagerService#relayoutWindow调起的。

第3步,WindowSurfacePlacer#performSurfacePlacementInner,还有一个调用可以关注下WinowState#reportResized()

第5步,WindowSurfacePlacer#performLayoutLockedInner。测量的关键方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//WindowSurfacePlacer.java
final void performLayoutLockedInner(final DisplayContent displayContent, //开始Layout
boolean initial, boolean updateInputWindows) {
//...
final int N = windows.size();
int i;
mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation,
mService.mCurConfiguration.uiMode); //先初始化相关数据
int topAttached = -1;
//第一个遍历处理的是非stub window(没有attached 到其他window 的那些窗口)
for (i = N-1; i >= 0; i--) {
final WindowState win = windows.get(i);
final boolean gone = (behindDream && mService.mPolicy.canBeForceHidden(win, win.mAttrs))
|| win.isGoneForLayoutLw();
if (!gone || !win.mHaveFrame || win.mLayoutNeeded
|| ((win.isConfigChanged() || win.setReportResizeHints())
&& !win.isGoneForLayoutLw() &&
((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
(win.mHasSurface && win.mAppToken != null &&
win.mAppToken.layoutConfigChanges)))) { //可见、mHaveFrame为true 代表已经计算过、需要布局
if (!win.mLayoutAttached) { //不是子Window
if (initial) {
win.mContentChanged = false;
}
if (win.mAttrs.type == TYPE_DREAM) {
behindDream = true;
}
win.mLayoutNeeded = false;
win.prelayout();
mService.mPolicy.layoutWindowLw(win, null);
win.mLayoutSeq = seq;
} else {
if (topAttached < 0) topAttached = i;
}
}
}
//第二次遍历处理的是attach 的window。因为子Window 需要父Window 的相关属性才能计算
for (i = topAttached; i >= 0; i--) {
final WindowState win = windows.get(i);
if (win.mLayoutAttached) {
if (attachedBehindDream && mService.mPolicy.canBeForceHidden(win, win.mAttrs)) {
continue;
}
if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
|| !win.mHaveFrame || win.mLayoutNeeded) {
if (initial) {
win.mContentChanged = false;
}
win.mLayoutNeeded = false;
win.prelayout();
mService.mPolicy.layoutWindowLw(win, win.mAttachedWindow);
}
} else if (win.mAttrs.type == TYPE_DREAM) {
attachedBehindDream = behindDream;
}
}
mService.mInputMonitor.setUpdateInputWindowsNeededLw();
if (updateInputWindows) {
mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
}
mService.mPolicy.finishLayoutLw();
mService.mH.sendEmptyMessage(UPDATE_DOCKED_STACK_DIVIDER);
}

主要是3个方法和2个遍历。

3个方法:

  1. PhoneWindowManager#beginLayoutLw:初始化值操作。第7行
  2. PhoneWindowManager#layoutWindowLw:测量每一个WindowState。第30行
  3. PhoneWindowManager#finishLayoutLw:结束操作。第61行

2个遍历:

  1. 遍历非Stub Window,处理测量流程。第11行
  2. 遍历Stub Window,处理测量流程(因为子Window 需要依赖父Window 的测量结果)。第38行

第6步,PhoneWindowManager#beginLayoutLw初始化相关值。
这个方法前部分全是赋值操作,上面介绍过这些值代表的含义。主要看
最后的layoutNavigationBarlayoutStatusBar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//PhoneWindowManager.java
private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation, //displayWidth、displayHeight 代表的屏幕的宽高。displayRotation 代表屏幕方向
int uiMode, int overscanLeft, int overscanRight, int overscanBottom, Rect dcf, //uiMode 用来区分是什么设备(比如手机、手表等等)
boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
boolean statusBarExpandedNotKeyguard) {
if (mNavigationBar != null) {
boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
//根据displayRotation 来获取NavigationBar 的位置。可能在底部、左边、右边
mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight,
displayRotation);
if (mNavigationBarPosition == NAV_BAR_BOTTOM) { //case 1 Navigation 在底部(竖屏)
int top = displayHeight - overscanBottom
- getNavigationBarHeight(displayRotation, uiMode); //屏幕高度 - 底部过扫描高度 - NavigationBar高度。得到的NavigationBar的左上角的y坐标(即top)
mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom); //mTmpNavigationFrame记录的是NavigationBar的位置(左上角和右下角坐标)
mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
if (transientNavBarShowing) {
mNavigationBarController.setBarShowingLw(true);
} else if (navVisible) {
mNavigationBarController.setBarShowingLw(true);
mDockBottom = mTmpNavigationFrame.top; //NavigationBar可见需要重新计算输入的底部位置
mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
} else {
mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard); //这里去隐藏Navigation Bar
}
if (navVisible && !navTranslucent && !navAllowedHidden
&& !mNavigationBar.isAnimatingLw()
&& !mNavigationBarController.wasRecentlyTranslucent()) {
mSystemBottom = mTmpNavigationFrame.top;
}
} else if (mNavigationBarPosition == NAV_BAR_RIGHT) { //case 2 Navigation 在右边
} else if (mNavigationBarPosition == NAV_BAR_LEFT) { //case 3 Navigation 在左边(屏幕旋转导致)
}
mContentTop = mVoiceContentTop = mCurTop = mDockTop;
mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
mContentRight = mVoiceContentRight = mCurRight = mDockRight;
mStatusBarLayer = mNavigationBar.getSurfaceLayer();
mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame, //最终调用computeFrameLw计算出mNavigationBar 中的值
mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
mTmpNavigationFrame, mTmpNavigationFrame);
if (mNavigationBarController.checkHiddenLw()) {
return true;
}
}
return false;
}

NavigationBar的计算分3种请求(NavigationBar可以随着屏幕的旋转出现在底、左、右)。
主要是分析了在底部的逻辑:
第12行计算出NavigationBar左上角坐标。底部NavigationBar默认宽度是屏幕宽度。
第14行就可以计算出底部NavigationBar的在屏幕的位置信息,保存在mTmpNavigationFrame。然后就会修改一些相关的值。
第39行,调用WindowState#computeFrameLw(这个方法后续分析)对WindowState内部属性赋值。
layoutStatusBar主要是执行了WindowState#computeFrameLw

第7步,PhoneWindowManager#layoutWindowLw。这个逻辑会对当前展示的所有Window单独处理的。这里处理的顺序是从用户看到层级展示的(即优先处理的是layer 比较大的Window)。
所有WindowState都会走这里的逻辑,这肯定是会有性能影响的,所以执行是有条件的,可以看第5步中的代码。
layoutWindowLw逻辑里面分成了6个case 来计算不用的WindowState的大小和位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//PhoneWindowManager.java
public void layoutWindowLw(WindowState win, WindowState attached) { //会在layout 的时候处理每一个attached window
if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) { //StatusBar 和NavigationBar 不用处理
return;
}
//计算WindowState的数据
final Rect pf = mTmpParentFrame;
final Rect df = mTmpDisplayFrame;
final Rect of = mTmpOverscanFrame;
final Rect cf = mTmpContentFrame;
final Rect vf = mTmpVisibleFrame;
final Rect dcf = mTmpDecorFrame;
final Rect sf = mTmpStableFrame;
Rect osf = null;
dcf.setEmpty();
if (!isDefaultDisplay) { //case 1
} else if (attrs.type == TYPE_INPUT_METHOD) { //case 2
} else if (attrs.type == TYPE_VOICE_INTERACTION) { //case 3
} else if (attrs.type == TYPE_WALLPAPER) { //case 4
} else if (win == mStatusBar) { //case 5
} else { //case 6 计算普通Window 的值
//6.1 计算的是DecorFrame
dcf.left = mSystemLeft;
dcf.top = mSystemTop;
dcf.right = mSystemRight;
dcf.bottom = mSystemBottom;
final boolean inheritTranslucentDecor = (attrs.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
final boolean isAppWindow = //是否是应用Window
attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&
attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
final boolean topAtRest =
win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 //6.1.1判断没有设置FULLSCREEN 的情况
&& (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0
&& (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0
&& (fl & WindowManager.LayoutParams.
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
dcf.top = mStableTop;
}
if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0
&& (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
&& (fl & WindowManager.LayoutParams.
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) { //6.1.2判断没有设置HIDE_NAVIGATION 的情况
// Ensure policy decor includes navigation bar
dcf.bottom = mStableBottom;
dcf.right = mStableRight;
}
}
if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
== (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
//6.2 计算的是pf、df、of
if (attached != null) { //case 含有FLAG_LAYOUT_IN_SCREEN 或者FLAG_LAYOUT_INSET_DECOR 的子窗口
setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
} else { //case 含有FLAG_LAYOUT_IN_SCREEN 或者FLAG_LAYOUT_INSET_DECOR 的非子窗口
if (attrs.type == TYPE_STATUS_BAR_PANEL
|| attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
} else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
} else if (canHideNavigationBar()
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { //隐藏了Navigation
pf.left = df.left = mOverscanScreenLeft;
pf.top = df.top = mOverscanScreenTop;
pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
of.left = mUnrestrictedScreenLeft;
of.top = mUnrestrictedScreenTop;
of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
} else {
pf.left = df.left = mRestrictedOverscanScreenLeft;
pf.top = df.top = mRestrictedOverscanScreenTop;
pf.right = df.right = mRestrictedOverscanScreenLeft
+ mRestrictedOverscanScreenWidth;
pf.bottom = df.bottom = mRestrictedOverscanScreenTop
+ mRestrictedOverscanScreenHeight;
// We need to tell the app about where the frame inside the overscan
// is, so it can inset its content by that amount -- it didn't ask
// to actually extend itself into the overscan region.
of.left = mUnrestrictedScreenLeft;
of.top = mUnrestrictedScreenTop;
of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
}
//6.3计算cf
if ((fl & FLAG_FULLSCREEN) == 0) {
if (win.isVoiceInteraction()) {
cf.left = mVoiceContentLeft;
cf.top = mVoiceContentTop;
cf.right = mVoiceContentRight;
cf.bottom = mVoiceContentBottom;
} else {
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
cf.left = mDockLeft;
cf.top = mDockTop;
cf.right = mDockRight;
cf.bottom = mDockBottom;
} else {
cf.left = mContentLeft;
cf.top = mContentTop;
cf.right = mContentRight;
cf.bottom = mContentBottom;
}
}
} else {
cf.left = mRestrictedScreenLeft;
cf.top = mRestrictedScreenTop;
cf.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight;
}
applyStableConstraints(sysUiFl, fl, cf); //这里会重新计算cf 的值?
if (adjust != SOFT_INPUT_ADJUST_NOTHING) { //d计算vd 的值
vf.left = mCurLeft;
vf.top = mCurTop;
vf.right = mCurRight;
vf.bottom = mCurBottom;
} else {
vf.set(cf);
}
}
} else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
& (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) { //Case 2
} else if (attached != null) { //Case 3 子窗口的情况
} else { //Case 4 最后一种情况
}
}
if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR
&& !win.isInMultiWindowMode()) { //特殊处理,如果flag 包含了FLAG_LAYOUT_NO_LIMITS
df.left = df.top = -10000;
df.right = df.bottom = 10000;
if (attrs.type != TYPE_WALLPAPER) {
of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
}
}
final boolean useOutsets = shouldUseOutsets(attrs, fl);
if (isDefaultDisplay && useOutsets) { //跟手表相关(手表可能有一块区域(“下巴”)无法绘制)
}
win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf); //把所有的值传给WindowState 去计算
// Dock windows carve out the bottom of the screen, so normal windows
// can't appear underneath them.
if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleOrBehindKeyguardLw()
&& win.isDisplayedLw() && !win.getGivenInsetsPendingLw()) { //输入法Window 特殊处理
}
if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw()
&& !win.getGivenInsetsPendingLw()) { //voice 界面特殊处理
}
}
  1. !isDefaultDisplay:代表不是默认屏(即DisplayContent.mDisplayId != 0),一般为false 所以不考虑。第16行
  2. attrs.type == TYPE_INPUT_METHOD:代表的是输入法。第17行
  3. attrs.type == TYPE_VOICE_INTERACTION:代表的是语音界面。第18行
  4. attrs.type == TYPE_WALLPAPER:壁纸窗口。第19行
  5. win == mStatusBar:代表的是状态栏。第20行
  6. 其他:代表ActivityDialogPopupWindowToast等。

主要分析下第6种情况,这里面又分成了4种情况:

  1. Window含有FLAG_LAYOUT_IN_SCREENFLAG_LAYOUT_INSET_DECORflag。(典型的应用WindowActivity初始化PhoneWindow的时候会设置)。第51行
  2. Window含有FLAG_LAYOUT_IN_SCREENflag,并且View设置了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN或者SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION的标志。即全屏显示。第126行
  3. attached != null即子Window。第129行
  4. 其他情况:窗口必须在所有的装饰里面展示。第130行

本来准备分析一个Activity的测量过程的,但是有太多不同配置了。这个如果是有碰到问题,可以具体的根据设置的属性查阅。

后续的处理就是WindowState#computeFrameLw来计算每一个WindowState的位置和大小,主要是根据上面PhoneWindowManager#layoutWindowLw计算出的值。

第8步,PhoneWindowManager#finishLayoutLw结束,目前没有任何实现。

总结:Window的测量并不是一个简单的过程,一个最简单的Activity展示出来就需要考虑输入框、StatusBarNavigationBar等众多Window,还需要考虑Window设置的flags 以及View设置的mSystemUiVisibility 等。

参考

Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析
《深入理解Android内核设计思想》