一般来说,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元素进行测量、布局以及绘制等操作了。
从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文可以知道,应用程序进程是从ViewRoot类的成员函数performTraversals开始,向WindowManagerService服务请求计算一个Activity窗口的大小的,因此,接下来我们就从ViewRoot类的成员函数performTraversals开始分析一个Activity窗口大小的计算过程,如图3所示。
图3 Activity窗口大小的计算过程
这个过程可以分为11个步骤,接下来我们就详细分析每一个步骤。
Step 1. ViewRoot.performTraversals
这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中,它的实现很复杂,一共有600-行,不过大部分代码都是用来计算Activity窗口的大小的,我们分段来阅读:
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
- ......
- private void performTraversals() {
- ......
- final View host = mView;
- ......
- int desiredWindowWidth;
- int desiredWindowHeight;
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
- ......
- Rect frame = mWinFrame;
- if (mFirst) {
- ......
- DisplayMetrics packageMetrics =
- mView.getContext().getResources().getDisplayMetrics();
- desiredWindowWidth = packageMetrics.widthPixels;
- desiredWindowHeight = packageMetrics.heightPixels;
- } else {
- desiredWindowWidth = frame.width();
- desiredWindowHeight = frame.height();
- if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
- ......
- windowResizesToFitContent = true;
- }
- }
注意,Activity窗口当前的宽度和高度是保存ViewRoot类的成员变量mWinFrame中的。ViewRoot类的另外两个成员变量mWidth和mHeight也是用来描述Activity窗口当前的宽度和高度的,但是它们的值是由应用程序进程上一次主动请求WindowManagerService服务计算得到的,并且会一直保持不变到应用程序进程下一次再请求WindowManagerService服务来重新计算为止。Activity窗口的当前宽度和高度有时候是被WindowManagerService服务主动请求应用程序进程修改的,修改后的值就会保存在ViewRoot类的成员变量mWinFrame中,它们可能会与ViewRoot类的成员变量mWidth和mHeight的值不同。
如果Activity窗口是第一次被请求执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true,那么它的当前宽度desiredWindowWidth和当前高度desiredWindowHeight就等于屏幕的宽度和高度,否则的话,它的当前宽度desiredWindowWidth和当前高度desiredWindowHeight就等于保存在ViewRoot类的成员变量mWinFrame中的宽度和高度值。
如果Activity窗口不是第一次被请求执行测量、布局和绘制操作,并且Activity窗口主动上一次请求WindowManagerService服务计算得到的宽度mWidth和高度mHeight不等于Activity窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight,那么就说明Activity窗口的大小发生了变化,这时候变量windowResizesToFitContent的值就会被标记为true,以便接下来可以对Activity窗口的大小变化进行处理。
我们继续往下阅读代码:
- boolean insetsChanged = false;
- if (mLayoutRequested) {
- ......
- if (mFirst) {
- host.fitSystemWindows(mAttachInfo.mContentInsets);
- ......
- } else {
- if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) {
- mAttachInfo.mContentInsets.set(mPendingContentInsets);
- host.fitSystemWindows(mAttachInfo.mContentInsets);
- insetsChanged = true;
- ......
- }
- if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) {
- mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
- ......
- }
- if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
- || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
- windowResizesToFitContent = true;
- DisplayMetrics packageMetrics =
- mView.getContext().getResources().getDisplayMetrics();
- desiredWindowWidth = packageMetrics.widthPixels;
- desiredWindowHeight = packageMetrics.heightPixels;
- }
- }
- childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
- ......
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- ......
- }
在分析这段代码之前,我们首先解释一下ViewRoot类的成员变量mAttachInfo和mPendingContentInsets、mPendingVisibleInsets。ViewRoot类的成员变量mAttachInfo指向的一个AttachInfo对象,这个AttachInfo对象用来描述Activity窗口的属性,例如,这个AttachInfo对象的成员变量mContentInsets和mVisibleInsets分别用来描述Activity窗口上一次主动请求WindowManagerService服务计算得到的内容边衬大小和可见边衬大小,即Activity窗口的当前内容边衬大小和可见边衬大小。ViewRoot类的成员变量mPendingContentInsets和mPendingVisibleInsets也是用来描述Activity窗口的内容边衬大小和可见边衬大小的,不过它们是由WindowManagerService服务主动请求Activity窗口设置的,但是尚未生效。
我们分两种情况来分析这段代码。
第一种情况是Activity窗口是第一次被请求执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true,那么这段代码在测量Activity窗口的顶层视图host的大小之前,首先会调用这个顶层视图host的成员函数fitSystemWindows来设置它的四个内边距(mPaddingLeft,mPaddingTop,mPaddingRight,mPaddingBottom)的大小设置为Activity窗口的初始化内容边衬大小。这样做的目的是可以在Activity窗口的四周留下足够的区域来放置可能会出现的系统窗口,也就是状态栏和输入法窗口。
第二种情况是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窗口的大小发生了变化,因此,就将变量windowResizesToFitContent的值设置为true。
经过上面的一系列处理之后,这段代码就会调用ViewRoot类的成员函数getRootMeasureSpec来根据Activity窗口的当前宽度和宽度测量规范以及高度和高度测量规范来计算得到它的顶层视图host的宽度测量规范childWidthMeasureSpec和高度测量规范childHeightMeasureSpec。有了这两个规范之后,就可以调用Activity窗口的顶层视图host的成员函数measure来执行大小测量的工作了。这个大小测量的过程可以参考前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文。
我们继续往下阅读代码:
- boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent
- && ((mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight)
- || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
- frame.width() < desiredWindowWidth && frame.width() != mWidth)
- || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
- frame.height() < desiredWindowHeight && frame.height() != mHeight));
- final boolean computesInternalInsets =
- attachInfo.mTreeObserver.hasComputeInternalInsetsListeners();
这段代码主要是做两件事情。
第一件事情是检查是否需要处理Activity窗口的大小变化事件。如果满足以下条件,那么就需要处理,即将变量windowShouldResize的值设置为true:
1. ViewRoot类的成员变量mLayoutRequest的值等于true,这说明应用程序进程正在请求对Activity窗口执行一次测量、布局和绘制操作;
2. 变量windowResizesToFitContent的值等于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窗口有额外的内容区域边衬和可见区域边衬等待指定。
我们继续往下阅读代码:
- boolean contentInsetsChanged = false;
- boolean visibleInsetsChanged;
- ......
- try {
- ......
- relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
- contentInsetsChanged = !mPendingContentInsets.equals(
- mAttachInfo.mContentInsets);
- visibleInsetsChanged = !mPendingVisibleInsets.equals(
- mAttachInfo.mVisibleInsets);
- if (contentInsetsChanged) {
- mAttachInfo.mContentInsets.set(mPendingContentInsets);
- host.fitSystemWindows(mAttachInfo.mContentInsets);
- ......
- }
- if (visibleInsetsChanged) {
- mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
- ......
- }
- ......
- } catch (RemoteException e) {
- }
- ......
- attachInfo.mWindowLeft = frame.left;
- attachInfo.mWindowTop = frame.top;
- // !!FIXME!! This next section handles the case where we did not get the
- // window size we asked for. We should avoid this by getting a maximum size from
- // the window session beforehand.
- mWidth = frame.width();
- mHeight = frame.height();
如果这次计算得到的Activity窗口的内容区域边衬大小mPendingContentInsets和可见区域边衬大小mPendingVisibleInsets与上一次计算得到的不一致,即与ViewRoot类的成员变量mAttachInfo所指向的一个AttachInfo对象的成员变量mContentInsets和mVisibleInsets所描述的大小不一致,那么变量contentInsetsChanged和visibleInsetsChanged的值就会等于true,表示Activity窗口的内容区域边衬大小和可见区域边衬大小发生了变化。
由于变量frame和ViewRoot类的成员变量mWinFrame引用的是同一个Rect对象,因此,这时候变量frame描述的也是Activity窗口请求WindowManagerService服务计算之后得到的大小。这段代码分别将计算得到的Activity窗口的左上角坐标保存在变量attachInfo所指向的一个AttachInfo对象的成员变量mWindowLeft和mWindowTop中,并且将计算得到的Activity窗口的宽度和高度保存在ViewRoot类的成员变量mWidth和mHeight中。
我们继续往下阅读代码:
- boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
- (relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0);
- if (focusChangedDueToTouchMode || mWidth != host.mMeasuredWidth
- || mHeight != host.mMeasuredHeight || contentInsetsChanged) {
- childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
- ......
- // Ask host how big it wants to be
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- // Implementation of weights from WindowManager.LayoutParams
- // We just grow the dimensions as needed and re-measure if
- // needs be
- int width = host.mMeasuredWidth;
- int height = host.mMeasuredHeight;
- 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) {
- ......
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- mLayoutRequested = true;
- }
1. Activity窗口的触摸模式发生了变化,并且由此引发了Activity窗口当前获得焦点的控件发生了变化,即变量focusChangedDueToTouchMode的值等于true。这个检查是通过调用ViewRoot类的成员函数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 = mLayoutRequested;
- ......
- if (didLayout) {
- ......
- host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
- ......
- }
- if (computesInternalInsets) {
- ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets;
- final Rect givenContent = attachInfo.mGivenInternalInsets.contentInsets;
- final Rect givenVisible = attachInfo.mGivenInternalInsets.visibleInsets;
- givenContent.left = givenContent.top = givenContent.right
- = givenContent.bottom = givenVisible.left = givenVisible.top
- = givenVisible.right = givenVisible.bottom = 0;
- attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
- Rect contentInsets = insets.contentInsets;
- Rect visibleInsets = insets.visibleInsets;
- if (mTranslator != null) {
- contentInsets = mTranslator.getTranslatedContentInsets(contentInsets);
- visibleInsets = mTranslator.getTranslatedVisbileInsets(visibleInsets);
- }
- if (insetsPending || !mLastGivenInsets.equals(insets)) {
- mLastGivenInsets.set(insets);
- try {
- sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
- contentInsets, visibleInsets);
- } catch (RemoteException e) {
- }
- }
- }
- ......
- }
- ......
从前面的描述可以知道,当变量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了,这个过程同样可以参考前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文。
接下来,我们继续分析ViewRoot类的成员函数relayoutWindow的实现,以便可以了解它是如何请求WindowManagerService服务计算Activity窗口的大小的。
Step 2. ViewRoot.relayoutWindow
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
- ......
- private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
- boolean insetsPending) throws RemoteException {
- float appScale = mAttachInfo.mApplicationScale;
- ......
- int relayoutResult = sWindowSession.relayout(
- mWindow, params,
- (int) (mView.mMeasuredWidth * appScale + 0.5f),
- (int) (mView.mMeasuredHeight * appScale + 0.5f),
- viewVisibility, insetsPending, mWinFrame,
- mPendingContentInsets, mPendingVisibleInsets,
- mPendingConfiguration, mSurface);
- ......
- if (mTranslator != null) {
- mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
- mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
- mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
- }
- return relayoutResult;
- }
- ......
- }
从前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一文可以知道,ViewRoot类的静态成员变量sWindowSession是一个Binder代理对象,它引用了运行在WindowManagerService服务这一侧的一个Session对象,ViewRoot类的成员函数relayoutWindow通过调用这个Session对象的成员函数relayout来请求WindowManagerService服务计算Activity窗口的大小,其中,传递给WindowManagerService服务的参数包括:
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窗口的大小的。
Step 3. Session.relayout
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private final class Session extends IWindowSession.Stub
- implements IBinder.DeathRecipient {
- ......
- public int relayout(IWindow window, WindowManager.LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewFlags,
- boolean insetsPending, Rect outFrame, Rect outContentInsets,
- Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
- //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid());
- int res = relayoutWindow(this, window, attrs,
- requestedWidth, requestedHeight, viewFlags, insetsPending,
- outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface);
- //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid());
- return res;
- }
- ......
- }
- ......
- }
Session类的成员函数relayout的实现很简单,它只是调用了WindowManagerService类的成员函数relayoutWindow来进一步计算参数window所描述的一个Activity窗品的大小,接下来我们就继续分析WindowManagerService类的成员函数relayoutWindow的实现。
Step 4. WindowManagerService.relayoutWindow
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- public int relayoutWindow(Session session, IWindow client,
- WindowManager.LayoutParams attrs, int requestedWidth,
- int requestedHeight, int viewVisibility, boolean insetsPending,
- Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
- Configuration outConfig, Surface outSurface) {
- ......
- synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client, false);
- ......
- win.mRequestedWidth = requestedWidth;
- win.mRequestedHeight = requestedHeight;
- ......
- final boolean scaledWindow =
- ((win.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0);
- if (scaledWindow) {
- // requested{Width|Height} Surface's physical size
- // attrs.{width|height} Size on screen
- win.mHScale = (attrs.width != requestedWidth) ?
- (attrs.width / (float)requestedWidth) : 1.0f;
- win.mVScale = (attrs.height != requestedHeight) ?
- (attrs.height / (float)requestedHeight) : 1.0f;
- } else {
- win.mHScale = win.mVScale = 1;
- }
- ......
- win.mGivenInsetsPending = insetsPending;
- ......
- performLayoutAndPlaceSurfacesLocked();
- ......
- outFrame.set(win.mFrame);
- outContentInsets.set(win.mContentInsets);
- outVisibleInsets.set(win.mVisibleInsets);
- ......
- }
- return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0)
- | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0);
- }
- ......
- }
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
参数client是一个Binder代理对象,它引用了运行在应用程序进程这一侧中的一个W对象,用来标志一个Activity窗口。从前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一文可以知道,在应用程序进程这一侧的每一个W对象,在WindowManagerService服务这一侧都有一个对应的WindowState对象,用来描述一个Activity窗口的状态。因此,WindowManagerService类的成员函数relayoutWindow首先通过调用另外一个成员函数windowForClientLocked来获得与参数client所对应的一个WindowState对象win,以便接下来可以对它进行操作。
本文我们只关注WindowManagerService类的成员函数relayoutWindow中与窗口大小计算有关的逻辑,计算过程如下所示:
1. 参数requestedWidth和requestedHeight描述的是应用程序进程请求设置Activity窗口中的宽度和高度,它们会被记录在WindowState对象win的成员变量mRequestedWidth和mRequestedHeight中。
2. WindowState对象win的成员变量mAttr,它指向的是一个WindowManager.LayoutParams对象,用来描述Activity窗口的布局参数。其中,这个WindowManager.LayoutParams对象的成员变量width和height是用来描述Activity窗口的宽度和高度的。当这个WindowManager.LayoutParams对象的成员变量flags的WindowManager.LayoutParams.FLAG_SCALED位不等于0的时候,就说明需要给Activity窗口的大小设置缩放因子。缩放因子分为两个维度,分别是宽度缩放因子和高度缩放因子,保存在WindowState对象win的成员变量HScale和VScale中,计算方法分别是用应用程序进程请求设置Activity窗口中的宽度和高度除以Activity窗口在布局参数中所设置的宽度和高度。
3. 参数insetsPending用来描述Activity窗口是否有额外的内容区域边衬和可见区域边衬未设置,它被记录在WindowState对象win的成员变量mGivenInsetsPending中。
4. 调用WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked来计算Activity窗口的大小。计算完成之后,参数client所描述的Activity窗口的大小、内容区域边衬大小和可见区域边边衬大小就会分别保存在WindowState对象win的成员变量mFrame、mContentInsets和mVisibleInsets中。
5. 将WindowState对象win的成员变量mFrame、mContentInsets和mVisibleInsets的值分别拷贝到参数出数outFrame、outContentInsets和outVisibleInsets中,以便可以返回给应用程序进程。
经过上述五个操作后,Activity窗口的大小计算过程就完成了,接下来我们继续分析WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked的实现,以便可以详细了解Activity窗口的大小计算过程。
Step 5. WindowManagerService.performLayoutAndPlaceSurfacesLocked
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private final void performLayoutAndPlaceSurfacesLocked() {
- if (mInLayout) {
- ......
- return;
- }
- ......
- boolean recoveringMemory = false;
- if (mForceRemoves != null) {
- recoveringMemory = true;
- // Wait a little it for things to settle down, and off we go.
- for (int i=0; i<mForceRemoves.size(); i++) {
- WindowState ws = mForceRemoves.get(i);
- Slog.i(TAG, "Force removing: " + ws);
- removeWindowInnerLocked(ws.mSession, ws);
- }
- mForceRemoves = null;
- ......
- }
- mInLayout = true;
- try {
- performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
- int i = mPendingRemove.size()-1;
- if (i >= 0) {
- while (i >= 0) {
- WindowState w = mPendingRemove.get(i);
- removeWindowInnerLocked(w.mSession, w);
- i--;
- }
- mPendingRemove.clear();
- mInLayout = false;
- assignLayersLocked();
- mLayoutNeeded = true;
- performLayoutAndPlaceSurfacesLocked();
- } else {
- mInLayout = false;
- ......
- }
- ......
- } catch (RuntimeException e) {
- mInLayout = false;
- ......
- }
- }
- ......
- }
从WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked的名称可以推断出,它执行的操作绝非是计算窗口大小这么简单。计算窗口大小只是其中的一个小小功能点,它主要的功能是用来刷新系统的UI。在我们这个情景中,为什么需要刷新系统的UI呢?Activity窗口在其属性发生了变化,例如,可见性、大小发生了变化,又或者它新增、删除了子视图,都需要重新计算大小,而这些变化都是要求WindowManagerService服务重新刷新系统的UI的。事实上,刷新系统的UI是WindowManagerService服务的主要任务,在新增和删除了窗口、窗口动画显示过程、窗口切换过程中,WindowManagerService服务都需要不断地刷新系统的UI。
WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked主要是通过调用另外一个成员函数performLayoutAndPlaceSurfacesLockedInner来刷新系统的UI的,而在刷新的过程中,就会对系统中的各个窗口的大小进行计算。
在调用成员函数performLayoutAndPlaceSurfacesLockedInner来刷新系统UI的前后,WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked还会执行以下两个操作:
1. 调用前,检查系统中是否存在强制删除的窗口。有内存不足的情况下,有一些窗口就会被回收,即要从系统中删除,这些窗口会保存在WindowManagerService类的成员变量mForceRemoves所描述的一个ArrayList中。如果存在这些窗口,那么WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked就会调用另外一个成员函数removeWindowInnerLocked来删除它们,以便可以回收它们所占用的内存。
2. 调用后,检查系统中是否有窗口需要移除。如果有的话,那么WindowManagerService类的成员变量mPendingRemove所描述的一个ArrayList的大小就会大于0。这种情况下,WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked就会调用另外一个成员函数removeWindowInnerLocked来移除这些窗口。注意,WindowManagerService类的成员函数removeWindowInnerLocked只是用来移除窗口,但是并没有回收这些窗口所占用的内存。等到合适的时候,例如,内存不足时,才会考虑回收这些窗口所占用的内存。移除一个窗口的操作也是很复杂的,除了要将窗口从WindowManagerService类的相关成员变量中移除之外,还要考虑重新调整输入法窗口和壁纸窗口,因为被移除的窗口可能要求显示壁纸和输入法窗口,当它被移除之后,就要将壁纸窗口和输入法窗口调整到合适的Z轴位置上去,以便可以交给下一个需要显示壁纸和输入法窗口的窗口使用。此外,在移除了窗口之后,WindowManagerService服务还需要重新计算现存的其它窗口的Z轴位置,以便可以正确地反映系统当前的UI状态,这是通过调用WindowManagerService类的成员函数assignLayersLocked来实现的。重新计算了现存的其它窗口的Z轴位置之后,又需要再次刷新系统的UI,即要对WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked进行递归调用,并且在调用前,将WindowManagerService类的成员变量mLayoutNeeded的值设置为true。由此就可见,系统UI的刷新过程是非常复杂的。
注意,为了防止在刷新系统UI的过程中被重复调用,WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked在刷新系统UI之前,即调用成员函数performLayoutAndPlaceSurfacesLockedInner之前,会将WindowManagerService类的成员变量mInLayout的值设置为true,并且在调用之后,重新将这个成员变量的值设置为false。这样,WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked就可以在一开始的时候检查成员变量mInLayout的值是否等于true,如果等于的话,那么就说明WindowManagerService服务正在刷新系统UI的过程中,于是就不用往下执行了。
接下来,我们就继续分析WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLockedInner的实现,以便可以了解Activity窗口的大小计算过程。
Step 6. WindowManagerService.performLayoutAndPlaceSurfacesLockedInner
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private final void performLayoutAndPlaceSurfacesLockedInner(
- boolean recoveringMemory) {
- ......
- Surface.openTransaction();
- ......
- try {
- ......
- int repeats = 0;
- int changes = 0;
- do {
- repeats++;
- if (repeats > 6) {
- ......
- break;
- }
- // FIRST LOOP: Perform a layout, if needed.
- if (repeats < 4) {
- changes = performLayoutLockedInner();
- if (changes != 0) {
- continue;
- }
- } else {
- Slog.w(TAG, "Layout repeat skipped after too many iterations");
- changes = 0;
- }
- // SECOND LOOP: Execute animations and update visibility of windows.
- ......
- } while (changes != 0);
- // THIRD LOOP: Update the surfaces of all windows.
- ......
- } catch (RuntimeException e) {
- ......
- }
- ......
- Surface.closeTransaction();
- ......
- // Destroy the surface of any windows that are no longer visible.
- ......
- // Time to remove any exiting tokens?
- ......
- // Time to remove any exiting applications?
- ......
- }
- ......
- }
未完待续。。。
- 相关文章
- Android窗口管理服务WindowManagerService的简要介绍和学 (1人浏览)
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (5人浏览)
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (2人浏览)
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (4人浏览)
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (1人浏览)
- Android应用程序窗口(Activity)的视图对象(View)的创建过程分 (2人浏览)
- Android应用程序窗口(Activity)的窗口对象(Window)的创建过 (0人浏览)