这篇文章很多参考博客:http://blog.csdn.net/luoshengyang/article/details/8479101/
一、窗口简介
在Android系统中,Activity窗口的大小是由WindowManagerService服务来计算的。WindowManagerService服务会根据屏幕及其装饰区的大小来决定Activity窗口的大小。一个Activity窗口只有知道自己的大小之后,才能对它里面的UI元素进行测量、布局以及绘制。本文将详细分析WindowManagerService服务计算Activity窗口大小的过程。
一般来说,Activity窗口的大小等于整个屏幕的大小,但是它并不占据着整块屏幕。为了理解这一点,我们首先分析一下Activity窗口的区域是如何划分的。
我们知道,Activity窗口的上方一般会有一个状态栏,用来显示3G信号、电量使用等图标,如图1所示。
图1 Activity窗口的Content区域示意图
从Activity窗口剔除掉状态栏所占用的区域之后,所得到的区域就称为内容区域(Content Region)。顾名思义,内容区域就是用来显示Activity窗口的内容的。我们再抽象一下,假设Activity窗口的四周都有一块类似状态栏的区域,那么将这些区域剔除之后,得到中间的那一块区域就称为内容区域,而被剔除出来的区域所组成的区域就称为内容边衬区域(Content Insets)。Activity窗口的内容边衬区域可以用一个四元组(content-left, content-top, content-right, content-bottom)来描述,其中,content-left、content-right、content-top、content-bottom分别用来描述内容区域与窗口区域的左右上下边界距离。
我们还知道,Activity窗口有时候需要显示输入法窗口,如图2所示。
图2 Activity窗口的Visible区域示意图
这时候Activity窗口的内容区域的大小有可能没有发生变化,这取决于它的Soft Input Mode。我们假设Activity窗口的内容区域没有发生变化,但是它在底部的一些区域被输入法窗口遮挡了,即它在底部的一些内容是不可见的。从Activity窗口剔除掉状态栏和输入法窗口所占用的区域之后,所得到的区域就称为可见区域(Visible Region)。同样,我们再抽象一下,假设Activity窗口的四周都有一块类似状态栏和输入法窗口的区域,那么将这些区域剔除之后,得到中间的那一块区域就称为可见区域,而被剔除出来的区域所组成的区域就称为可见边衬区域(Visible Insets)。Activity窗口的可见边衬区域可以用一个四元组(visible-left, visible-top, visible-right, visible-bottom)来描述,其中,visible-left、visible-right、visible-top、visible-bottom分别用来描述可见区域与窗口区域的左右上下边界距离。
在大多数情况下,Activity窗口的内容区域和可见区域的大小是一致的,而状态栏和输入法窗口所占用的区域又称为屏幕装饰区。理解了这些概念之后,我们就可以推断,WindowManagerService服务实际上就是需要根据屏幕以及可能出现的状态栏和输入法窗口的大小来计算出Activity窗口的整体大小及其内容区域边衬和可见区域边衬的大小。有了这三个数据之后,Activity窗口就可以对它里面的UI元素进行测量、布局以及绘制等操作了。
二、窗口函数调用流程
应用程序进程是从ViewRoot类的成员函数performTraversals开始,向WindowManagerService服务请求计算一个Activity窗口的大小的,因此,接下来我们就从ViewRoot类的成员函数performTraversals开始分析一个Activity窗口大小的计算过程,如图3所示。
图3 Activity窗口大小的计算过程
2.1 ViewRootImpl的performTraversals函数
这个函数比较复杂我们,进行分段讲解:
-
private void performTraversals() {
-
// cache mView since it is used so much below...
-
final View host = mView;
-
-
mIsInTraversal =
true;
-
mWillDrawSoon =
true;
-
boolean windowSizeMayChange =
false;
-
boolean newSurface =
false;
-
boolean surfaceChanged =
false;
-
WindowManager.LayoutParams lp = mWindowAttributes;
-
-
int desiredWindowWidth;
-
int desiredWindowHeight;
-
-
......
-
mWindowAttributesChangesFlag =
0;
-
-
Rect frame = mWinFrame;
-
if (mFirst) {
-
mFullRedrawNeeded =
true;
-
mLayoutRequested =
true;
-
-
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
-
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
-
// NOTE -- system code, won't try to do compat mode.
-
Point size =
new Point();
-
mDisplay.getRealSize(size);
-
desiredWindowWidth = size.x;
-
desiredWindowHeight = size.y;
-
}
else {
-
DisplayMetrics packageMetrics =
-
mView.getContext().getResources().getDisplayMetrics();
-
desiredWindowWidth = packageMetrics.widthPixels;
-
desiredWindowHeight = packageMetrics.heightPixels;
-
}
-
......
-
-
}
else {
-
desiredWindowWidth = frame.width();
-
desiredWindowHeight = frame.height();
-
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
-
mFullRedrawNeeded =
true;
-
mLayoutRequested =
true;
-
windowSizeMayChange =
true;
-
}
-
}
这段代码用来获得Activity窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight。
注意,Activity窗口当前的宽度和高度是保存ViewRootImpl类的成员变量mWinFrame中的。ViewRootImpl类的另外两个成员变量mWidth和mHeight也是用来描述Activity窗口当前的宽度和高度的,但是它们的值是由应用程序进程上一次主动请求WindowManagerService服务计算得到的,并且会一直保持不变到应用程序进程下一次再请求WindowManagerService服务来重新计算为止。Activity窗口的当前宽度和高度有时候是被WindowManagerService服务主动请求应用程序进程修改的,修改后的值就会保存在ViewRootImpl类的成员变量mWinFrame中,它们可能会与ViewRootImpl类的成员变量mWidth和mHeight的值不同。
如果Activity窗口是第一次被请求执行测量、布局和绘制操作,即ViewRootImpl类的成员变量mFirst的值等于true,那么它的当前宽度desiredWindowWidth和当前高度desiredWindowHeight就等于屏幕的宽度和高度,否则的话,它的当前宽度desiredWindowWidth和当前高度desiredWindowHeight就等于保存在ViewRootImpl类的成员变量mWinFrame中的宽度和高度值。
如果Activity窗口不是第一次被请求执行测量、布局和绘制操作,并且Activity窗口主动上一次请求WindowManagerService服务计算得到的宽度mWidth和高度mHeight不等于Activity窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight,那么就说明Activity窗口的大小发生了变化,这时候变量windowSizeMayChange的值就会被标记为true,以便接下来可以对Activity窗口的大小变化进行处理。
我们继续往下阅读代码:
-
boolean insetsChanged =
false;
-
-
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
-
if (layoutRequested) {
-
-
final Resources res = mView.getContext().getResources();
-
-
if (mFirst) {
-
// make sure touch mode code executes by setting cached value
-
// to opposite of the added touch mode.
-
mAttachInfo.mInTouchMode = !mAddedTouchMode;
-
ensureTouchModeLocally(mAddedTouchMode);
-
}
else {
-
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
-
insetsChanged =
true;
-
}
-
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
-
insetsChanged =
true;
-
}
-
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
-
insetsChanged =
true;
-
}
-
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
-
mAttachInfo.mVisibleInsets.
set(mPendingVisibleInsets);
-
if (DEBUG_LAYOUT) Log.v(TAG,
"Visible insets changing to: "
-
+ mAttachInfo.mVisibleInsets);
-
}
-
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
-
insetsChanged =
true;
-
}
-
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
-
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
-
windowSizeMayChange =
true;
-
-
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
-
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
-
// NOTE -- system code, won't try to do compat mode.
-
Point size =
new Point();
-
mDisplay.getRealSize(size);
-
desiredWindowWidth = size.x;
-
desiredWindowHeight = size.y;
-
}
else {
-
DisplayMetrics packageMetrics = res.getDisplayMetrics();
-
desiredWindowWidth = packageMetrics.widthPixels;
-
desiredWindowHeight = packageMetrics.heightPixels;
-
}
-
}
-
}
-
-
// Ask host how big it wants to be
-
windowSizeMayChange |= measureHierarchy(host, lp, res,
-
desiredWindowWidth, desiredWindowHeight);
-
}
这段代码用来在Activity窗口主动请求WindowManagerService服务计算大小之前,对它的顶层视图进行一次测量操作。
在分析这段代码之前,我们首先解释一下ViewRootImpl类的成员变量mAttachInfo和mPendingContentInsets、mPendingVisibleInsets。ViewRoot类的成员变量mAttachInfo指向的一个AttachInfo对象,这个AttachInfo对象用来描述Activity窗口的属性,例如,这个AttachInfo对象的成员变量mContentInsets和mVisibleInsets分别用来描述Activity窗口上一次主动请求WindowManagerService服务计算得到的内容边衬大小和可见边衬大小,即Activity窗口的当前内容边衬大小和可见边衬大小。ViewRootImpl类的成员变量mPendingContentInsets和mPendingVisibleInsets也是用来描述Activity窗口的内容边衬大小和可见边衬大小的,不过它们是由WindowManagerService服务主动请求Activity窗口设置的,但是尚未生效。
我们分两种情况来分析这段代码。
第一种情况是Activity窗口是第一次被请求执行测量、布局和绘制操作,即ViewRootImpl类的成员变量mFirst的值等于true,那么调用ensureTouchModeLocally函数。
第二种情况是Activity窗口不是第一次被请求执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于false,那么这段代码就会检查Activity窗口是否被WindowManagerService服务主动请求设置了一个新的内容边衬大小mPendingContentInsets和一个新的可见边衬大小mPendingVisibleInsets。如果是的话,那么就会分别将它们保存在ViewRoot类的成员变量mAttachInfo所指向的一个AttachInfo对象的成员变量mContentInsets和成员变量mVisibleInsets中。注意,如果Activity窗口被WindowManagerService服务主动请求设置了一个新的内容边衬大小mPendingContentInsets,那么这段代码同时还需要同步调用Activity窗口的顶层视图host的成员函数fitSystemWindows来将它的四个内边距(mPaddingLeft,mPaddingTop,mPaddingRight,mPaddingBottom)的大小设置为新的内容边衬大小,并且将变量insetsChanged的值设置为true,表明Activity窗口的内容边衬大小发生了变化。
在第二种情况下,如果Activity窗口的宽度被设置为ViewGroup.LayoutParams.WRAP_CONTENT或者高度被设置为ViewGroup.LayoutParams.WRAP_CONTENT,那么就意味着Activity窗口的大小要等于内容区域的大小。但是由于Activity窗口的大小是需要覆盖整个屏幕的,因此,这时候就会Activity窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight设置为屏幕的宽度和高度。也就是说,如果我们将Activity窗口的宽度和高度设置为ViewGroup.LayoutParams.WRAP_CONTENT,实际上就意味着它的宽度和高度等于屏幕的宽度和高度。这种情况也意味着Acitivity窗口的大小发生了变化,因此,就将变量windowSizeMayChange的值设置为true。
经过上面的一系列处理之后,这段代码就会调用ViewRoot类的成员函数getRootMeasureSpec来根据Activity窗口的当前宽度和宽度测量规范以及高度和高度测量规范来计算得到它的顶层视图host的宽度测量规范childWidthMeasureSpec和高度测量规范childHeightMeasureSpec。有了这两个规范之后,就可以调用measureHierarchy来执行大小测量的工作了。
我们继续往下阅读代码:
-
boolean windowShouldResize = layoutRequested && windowSizeMayChange
-
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
-
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
-
frame.width() < desiredWindowWidth && frame.width() != mWidth)
-
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
-
frame.height() < desiredWindowHeight && frame.height() != mHeight));
-
-
// Determine whether to compute insets.
-
// If there are no inset listeners remaining then we may still need to compute
-
// insets in case the old insets were non-empty and must be reset.
-
final boolean computesInternalInsets =
-
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
-
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
这段代码主要是做两件事情。
第一件事情是检查是否需要处理Activity窗口的大小变化事件。如果满足以下条件,那么就需要处理,即将变量windowShouldResize的值设置为true:
1. ViewRootImpl类的成员变量mLayoutRequest的值等于true,这说明应用程序进程正在请求对Activity窗口执行一次测量、布局和绘制操作;
2. 变量windowSizeMayChange的值等于true,这说明前面检测到了Activity窗口的大小发生了变化;
3. 前面我们已经Activity窗口的顶层视图host的大小重新进行了测量。如果测量出来的宽度host.mMeasuredWidth和高度host.mMeasuredHeight和Activity窗口的当前宽度mWidth和高度mHeight一样,那么即使条件1和条件2能满足,那么也是可以认为是Activity窗口的大小是没有发生变化的。换句话说,只有当测量出来的大小和当前大小不一致时,才认为Activity窗口大小发生了变化。另一方面,如果测量出来的大小和当前大小一致,但是Activity窗口的大小被要求设置成WRAP_CONTENT,即设置成和屏幕的宽度desiredWindowWidth和高度desiredWindowHeight一致,但是WindowManagerService服务请求Activity窗口设置的宽度frame.width()和高度frame.height()与它们不一致,而且与Activity窗口上一次请求WindowManagerService服务计算的宽度mWidth和高度mHeight也不一致,那么也是认为Activity窗口大小发生了变化的。
第二件事情是检查Activity窗口是否需要指定有额外的内容边衬区域和可见边衬区域。如果有的话,那么变量attachInfo所指向的一个AttachInfo对象的成员变量mTreeObserver所描述的一个TreeObserver对象的成员函数hasComputeInternalInsetsListerner的返回值ComputeInternalInsets就会等于true。Activity窗口指定额外的内容边衬区域和可见边衬区域是为了放置一些额外的东西。
我们继续往下阅读代码:
-
if (mFirst || windowShouldResize || insetsChanged ||
-
viewVisibilityChanged || params != null) {
-
-
if (viewVisibility == View.VISIBLE) {
-
// If this window is giving internal insets to the window
-
// manager, and it is being added or changing its visibility,
-
// then we want to first give the window manager "fake"
-
// insets to cause it to effectively ignore the content of
-
// the window during layout. This avoids it briefly causing
-
// other windows to resize/move based on the raw frame of the
-
// window, waiting until we can finish laying out this window
-
// and get back to the window manager with the ultimately
-
// computed insets.
-
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
-
}
这段代码以及接下来的两段代码都是在满足下面的条件之一的情况下执行的:
1. Activity窗口是第一次执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true。
2. 前面得到的变量windowShouldResize的值等于true,即Activity窗口的大小的确是发生了变化。
3. 前面得到的变量insetsChanged的值等于true,即Activity窗口的内容区域边衬发生了变化。
4. Activity窗口的可见性发生了变化,即变量viewVisibilityChanged的值等于true。
5. Activity窗口的属性发生了变化,即变量params指向了一个WindowManager.LayoutParams对象。
在满足上述条件之一,并且Activity窗口处于可见状态,即变量viewVisibility的值等于View.VISIBLE,那么就需要检查接下来请求WindowManagerService服务计算大小时,是否要告诉WindowManagerService服务它指定了额外的内容区域边衬和可见区域边衬,但是这些额外的内容区域边衬和可见区域边衬又还有确定。这种情况发生在Activity窗口第一次执行测量、布局和绘制操作或者由不可见变化可见时。因此,当前面得到的变量computesInternalInsets等于true时,即Activity窗口指定了额外的内容区域边衬和可见区域边衬,那么就需要检查ViewRoot类的成员变量mFirst或者变量viewVisibilityChanged的值是否等于true。如果这些条件能满足,那么变量insetsPending的值就会等于true,表示Activity窗口有额外的内容区域边衬和可见区域边衬等待指定。
我们继续往下阅读代码:
-
try {
-
......
-
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
-
-
-
if (mPendingConfiguration.seq !=
0) {
-
updateConfiguration(mPendingConfiguration, !mFirst);
-
mPendingConfiguration.seq =
0;
-
}
-
-
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);
-
if (contentInsetsChanged) {
-
if (mWidth >
0 && mHeight >
0 && lp != null &&
-
((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
-
& View.SYSTEM_UI_LAYOUT_FLAGS) ==
0 &&
-
mSurface != null && mSurface.isValid() &&
-
!mAttachInfo.mTurnOffWindowResizeAnim &&
-
mAttachInfo.mHardwareRenderer != null &&
-
mAttachInfo.mHardwareRenderer.isEnabled() &&
-
lp != null && !PixelFormat.formatHasAlpha(lp.format)
-
&& !mBlockResizeBuffer) {
-
-
disposeResizeBuffer();
-
}
-
mAttachInfo.mContentInsets.
set(mPendingContentInsets);
-
}
-
if (overscanInsetsChanged) {
-
mAttachInfo.mOverscanInsets.
set(mPendingOverscanInsets);
-
contentInsetsChanged =
true;
-
}
-
if (stableInsetsChanged) {
-
mAttachInfo.mStableInsets.
set(mPendingStableInsets);
-
contentInsetsChanged =
true;
-
}
-
if (contentInsetsChanged || mLastSystemUiVisibility !=
-
mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
-
|| mLastOverscanRequested != mAttachInfo.mOverscanRequested
-
|| outsetsChanged) {
-
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
-
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
-
mAttachInfo.mOutsets.
set(mPendingOutsets);
-
mApplyInsetsRequested =
false;
-
dispatchApplyInsets(host);
-
}
-
if (visibleInsetsChanged) {
-
mAttachInfo.mVisibleInsets.
set(mPendingVisibleInsets);
-
}
-
......
-
}
catch (RemoteException e) {
-
}
-
-
mAttachInfo.mWindowLeft = frame.left;
-
mAttachInfo.mWindowTop = frame.top;
-
-
if (mWidth != frame.width() || mHeight != frame.height()) {
-
mWidth = frame.width();
-
mHeight = frame.height();
-
}
这段代码主要就是调用ViewRootImpl类的另外一个成员函数relayoutWindow来请求WindowManagerService服务计算Activity窗口的大小以及内容区域边衬大小和可见区域边衬大小。计算完毕之后,Activity窗口的大小就会保存在ViewRootImpl类的成员变量mWinFrame中,而Activity窗口的内容区域边衬大小和可见区域边衬大小分别保存在ViewRootImpl类的成员变量mPendingContentInsets和mPendingVisibleInsets中。
如果这次计算得到的Activity窗口的内容区域边衬大小mPendingContentInsets和可见区域边衬大小mPendingVisibleInsets与上一次计算得到的不一致,即与ViewRootImpl类的成员变量mAttachInfo所指向的一个AttachInfo对象的成员变量mContentInsets和mVisibleInsets所描述的大小不一致,那么变量contentInsetsChanged和visibleInsetsChanged的值就会等于true,表示Activity窗口的内容区域边衬大小和可见区域边衬大小发生了变化。
由于变量frame和ViewRootImpl类的成员变量mWinFrame引用的是同一个Rect对象,因此,这时候变量frame描述的也是Activity窗口请求WindowManagerService服务计算之后得到的大小。这段代码分别将计算得到的Activity窗口的左上角坐标保存在变量attachInfo所指向的一个AttachInfo对象的成员变量mWindowLeft和mWindowTop中,并且将计算得到的Activity窗口的宽度和高度保存在ViewRootImpl类的成员变量mWidth和mHeight中。
我们继续往下阅读代码:
-
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
-
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
-
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
-
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
-
-
// Ask host how big it wants to be
-
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-
-
// Implementation of weights from WindowManager.LayoutParams
-
// We just grow the dimensions as needed and re-measure if
-
// needs be
-
int width = host.getMeasuredWidth();
-
int height = host.getMeasuredHeight();
-
boolean measureAgain =
false;
-
-
if (lp.horizontalWeight >
0.0f) {
-
width += (
int) ((mWidth - width) * lp.horizontalWeight);
-
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
-
MeasureSpec.EXACTLY);
-
measureAgain =
true;
-
}
-
if (lp.verticalWeight >
0.0f) {
-
height += (
int) ((mHeight - height) * lp.verticalWeight);
-
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
-
MeasureSpec.EXACTLY);
-
measureAgain =
true;
-
}
-
-
if (measureAgain) {
-
if (DEBUG_LAYOUT) Log.v(TAG,
-
"And hey let's measure once more: width=" + width
-
+
" height=" + height);
-
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-
}
-
-
layoutRequested =
true;
//代表可以进行应用布局了
-
}
这段代码用来检查是否需要重新测量Activity窗口的大小。如果满足以下条件之一,那么就需要重新测量:
1. Activity窗口的触摸模式发生了变化,并且由此引发了Activity窗口当前获得焦点的控件发生了变化,即变量focusChangedDueToTouchMode的值等于true。这个检查是通过调用ViewRootImpl类的成员函数ensureTouchModeLocally来实现的。
2. Activity窗口前面测量出来的宽度host.mMeasuredWidth和高度host.mMeasuredHeight不等于WindowManagerService服务计算出来的宽度mWidth和高度mHeight。
3. Activity窗口的内容区域边衬大小和可见区域边衬大小发生了变化,即前面得到的变量contentInsetsChanged的值等于true。
重新计算了一次之后,如果Activity窗口的属性lp表明需要对测量出来的宽度width和高度height进行扩展,即变量lp所指向的一个WindowManager.LayoutParams对象的成员变量horizontalWeight和verticalWeight的值大于0.0,那么就需要对Activity窗口的顶层视图host的最大可用空间进行扩展后再进行一次测量工作。
我们继续往下阅读最后一段代码:
-
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
-
boolean triggerGlobalLayoutListener = didLayout
-
|| mAttachInfo.mRecomputeGlobalAttributes;
-
if (didLayout) {
-
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
-
......
-
}
-
-
if (triggerGlobalLayoutListener) {
-
mAttachInfo.mRecomputeGlobalAttributes =
false;
-
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
-
}
-
-
if (computesInternalInsets) {
-
// Clear the original insets.
-
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
-
insets.reset();
-
-
// Compute new insets in place.
-
mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
-
mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
-
-
// Tell the window manager.
-
if (insetsPending || !mLastGivenInsets.equals(insets)) {
-
mLastGivenInsets.
set(insets);
-
-
// Translate insets to screen coordinates if needed.
-
final Rect contentInsets;
-
final Rect visibleInsets;
-
final Region touchableRegion;
-
if (mTranslator != null) {
-
contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
-
visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
-
touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
-
}
else {
-
contentInsets = insets.contentInsets;
-
visibleInsets = insets.visibleInsets;
-
touchableRegion = insets.touchableRegion;
-
}
-
-
try {
-
mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
-
contentInsets, visibleInsets, touchableRegion);
-
}
catch (RemoteException e) {
-
}
-
}
-
}
经过前面漫长的操作后,Activity窗口的大小测量工作终于尘埃落定,这时候就可以对Activity窗口的内容进行布局了,前提是ViewRoot类的成员变量layoutRequested 的值等于true。对Activity窗口的内容进行布局是通过调用performLayout来实现的。
从前面的描述可以知道,当变量computesInternalInsets的值等于true时,就表示Activity窗口指定有额外的内容区域边衬和可见区域边衬,这时候就是时候把它们告诉给WindowManagerService服务了,以便WindowManagerService服务下次可以知道Activity窗口的真实布局。Activity窗口额外指定的内容区域边衬大小和可见区域边衬大小是通过调用变量attachInfo所指向的一个AttachInfo对象的成员变量mTreeObserver所描述的一个TreeObserver对象的成员函数dispatchOnComputeInternalInsets来计算的。计算完成之后,就会保存在变量attachInfo所指向的一个AttachInfo对象的成员变量mGivenInternalInsets中,并且会通过ViewRoot类的静态成员变量sWindowSession所指向一个Binder代理对象来设置到WindowManagerService服务中去。
注意,如果ViewRoot类的成员变量mTranslator指向了一个Translator对象,那么就说明Activity窗口是运行兼容模式中,这时候就需要将前面计算得到的内容区域边衬大小和可见区域边衬大小转化到兼容模式下,然后才可以保存在变量attachInfo所指向的一个AttachInfo对象的成员变量mGivenInternalInsets中,以及设置到WindowManagerService服务中去。
另外,只有前面得到的变量insetsPending的值等于true,即Activity窗口正在等待告诉WindowManagerService服务它有额外指定的内容区域边衬和可见区域边衬,或者Activty窗口额外指定的内容区域边衬和可见区域边衬发生了变化,即Activty窗口上一次额外指定的内容区域边衬和可见区域边衬mLastGivenInsets不等于当前这次指定的内容区域边衬和可见区域边衬insets,Activity窗口额外指定的内容区域边衬和可见区域边衬才会被设置到WindowManagerService服务中去。
ViewRoot类的成员函数再接下来的工作就是绘制Activity窗口的UI了,这个之前我们的博客分析过了。
下面就是看ViewRootImpl的relayoutWindow了。
2.2 relayoutWindow
我们先看ViewRootImpl的relayoutWindow函数:
-
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
-
boolean insetsPending) throws RemoteException {
-
......
-
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,
-
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
-
mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface);
-
-
if (restore) {
-
params.restore();
-
}
-
-
if (mTranslator != null) {
-
mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
-
mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets);
-
mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
-
mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
-
mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets);
-
}
-
return relayoutResult;
-
}
我们来看下参数:
1. ViewRoot类的成员变量mWindow,用来标志要计算的是哪一个Activity窗口的大小。
2. Activity窗口的顶层视图经过测量后得到的宽度和高度。注意,传递给WindowManagerService服务的宽度和高度是已经考虑了Activity窗口所设置的缩放因子了的。
3. Activity窗口的可见状态,即参数viewVisibility。
4. Activity窗口是否有额外的内容区域边衬和可见区域边衬等待告诉给WindowManagerService服务,即参数insetsPending。
5. ViewRoot类的成员变量mWinFrame,这是一个输出参数,用来保存WindowManagerService服务计算后得到的Activity窗口的大小。
6. ViewRoot类的成员变量mPendingContentInsets,这是一个输出参数,用来保存WindowManagerService服务计算后得到的Activity窗口的内容区域边衬大小。
7. ViewRoot类的成员变量mPendingVisibleInsets,这是一个输出参数,用来保存WindowManagerService服务计算后得到的Activity窗口的可见区域边衬大小。
8. ViewRoot类的成员变量mPendingConfiguration,这是一个输出参数,用来保存WindowManagerService服务返回来的Activity窗口的配置信息。
9. ViewRoot类的成员变量mSurface,这是一个输出参数,用来保存WindowManagerService服务返回来的Activity窗口的绘图表面。
得到了Activity窗口的大小以及内容区域边衬大小和可见区域边衬大小之后,如果Activity窗口是运行在兼容模式中,即ViewRoot类的成员变量mTranslator指向了一个Translator对象,那么就需要调用它的成员函数translateRectInScreenToAppWindow来对它们进行转换。
接下来,我们继续分析Session类的成员函数relayout,以便可以了解WindowManagerService服务是如何计算一个Activity窗口的大小的。
最后就到WMS的relayoutWindow函数了,我们放到下篇博客再分析。