WindowManagerService计算窗口大小的过程和窗口组织方式分析

根据老罗的博客简化了点:http://blog.csdn.net/luoshengyang/article/details/8462738

我们知道,在Android系统中,同一时刻,只有一个Activity窗口是激活的,但是,对于WindowManagerService服务来说,这并不意味着它每次只需要管理一个Activity窗口,例如,在两个Activity窗口的切换过程中,前后两个Activity窗口实际上都是可见的。即使在只有一个Activity窗口是可见的时候,WindowManagerService服务仍然需要同时管理着多个窗口,这是因为可见的Activity窗口可能还会被设置了壁纸窗口(Wallpaper Winodw)或者弹出了子窗口(Sub Window),以及可能会出现状态栏(Status Bar)以及输入法窗口(Input Method Window)

,如图1所示。

《WindowManagerService计算窗口大小的过程和窗口组织方式分析》

图1 Activity窗口及其子窗口、壁纸窗口、输入法窗口和状态栏的位置结构

WindowManagerService服务只要将它所管理的各个窗品的位置以及大小告诉SurfaceFlinger服务,后者可以帮帮它计算出各个窗口的可见区域了。注意,这里,这里所说的窗口位置包括窗口在X、Y和Z轴的位置。

WindowManagerService服务大致按照以下方式来控制哪些窗口需要显示的以及要显在哪里:

1. 只要对每一个Activity窗口设置一个不同的Z轴位置,然后就可以使得位于最上面的,即当前被激活的Activity窗口,才是可见的。

2. 每一个子窗口的Z轴位置都比它的父窗口大,但是大小要比父窗口小,这时候Activity窗口及其所弹出的子窗口都可以同时显示出来。

3. 对于非全屏Activity窗口来说,它会在屏幕的上方留出一块区域,用来显示状态栏。这块留出来的区域对于屏幕来说,称为装饰区(decoration),而对于Activity窗口来说,称为内容边衬区(Content Inset)。

4. 输入法窗口只有在需要的时候才会出现,它同样是出现在屏幕的装饰区或者说Activity窗口的内容边衬区的。

5. 对于壁纸窗口,它出现需要壁纸的Activity窗口的下方,这时候要求Activity窗口是半透明的,这样就可以将它后面的壁纸窗口一同显示出来。

6. 两个Activity窗口在切换过程,实际上就是前一个窗口显示退出动画而后一个窗口显示开始动画的过程,而在动画的显示过程,窗口的大小会有一个变化的过程,这样就导致前后两个Activity窗口的大小不再都等于屏幕的大小,因而它们就有可能同时都处于可见的状态。事实上,Activity窗口的切换过程是相当复杂的,因为即将要显示的Activity窗口可能还会被设置一个启动窗口(Starting Window)。一个被设置了启动窗口的Activity窗口要等到它的启动窗口显示了之后才可以显示出来。

从以上六点就可以看出,窗口在X、Y和Z轴的位置及其大小的计算非常重要,它们共同决定了一个窗口是否是整体可见的,还是部分可见的,或者整体不可见的。在Android系统中,WindowManagerService服务是通过一个实现了WindowManagerPolicy接口的策略类来计算一个窗口的位置和大小的。例如,在Phone平台上,这个策略类就是PhoneWindowManager。这样做的好处就是对于不同的平台实现不同的策略类来达到不同的窗口控制模式。

      从上面的描述就可以看出,WindowManagerService服务除了要与Activity窗口所运行在的应用程序进程打交道之外,还需要与SurfaceFlinger服务以及窗口管理策略类PhoneWindowManager交互,如图2所示。

《WindowManagerService计算窗口大小的过程和窗口组织方式分析》

图2 WindowManagerService服务与Activity窗口、SurfaceFlinger服务、PhoneWindowManager策略的关系图

一、
窗口大小的计算

一般来说,Activity窗口的大小等于整个屏幕的大小,但是它并不占据着整块屏幕。为了理解这一点,我们首先分析一下Activity窗口的区域是如何划分的。

        我们知道,Activity窗口的上方一般会有一个状态栏,用来显示3G信号、电量使用等图标,如图1所示。

《WindowManagerService计算窗口大小的过程和窗口组织方式分析》

图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所示。

《WindowManagerService计算窗口大小的过程和窗口组织方式分析》

图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元素进行测量、布局以及绘制等操作了。

应用程序进程是从ViewRootImpl类的成员函数performTraversals开始,向WindowManagerService服务请求计算一个Activity窗口的大小的,因此,接下来我们就从ViewRoot类的成员函数performTraversals开始分析一个Activity窗口大小的计算过程,如图3所示。

《WindowManagerService计算窗口大小的过程和窗口组织方式分析》

图3 Activity窗口大小的计算过程

Step 1. ViewRoot.performTraversals

这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中,它的实现很复杂,一共有600-行,不过大部分代码都是用来计算Activity窗口的大小的,我们分段来阅读:

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窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight。

注意,Activity窗口当前的宽度和高度是保存在成员变量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);  
  
    ......  
} 

在分析这段代码之前,我们首先解释一下ViewRootImpl类的成员变量mAttachInfo和mPendingContentInsets、mPendingVisibleInsets。ViewRootImpl类的成员变量mAttachInfo指向的一个AttachInfo对象,这个AttachInfo对象用来描述Activity窗口的属性,例如,这个AttachInfo对象的成员变量mContentInsets和mVisibleInsets分别用来描述Activity窗口上一次主动请求WindowManagerService服务计算得到的内容边衬大小和可见边衬大小,即Activity窗口的当前内容边衬大小和可见边衬大小。ViewRootImpl类的成员变量mPendingContentInsets和mPendingVisibleInsets也是用来描述Activity窗口的内容边衬大小和可见边衬大小的,不过它们是由WindowManagerService服务主动请求Activity窗口设置的,但是尚未生效。

这段代码用来在Activity窗口主动请求WindowManagerService服务计算大小之前,对它的顶层视图进行一次测量操作。

我们分两种情况来分析这段代码。

        第一种情况是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。

我们继续往下阅读代码:

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. ViewRootImpl类的成员变量mLayoutRequested的值等于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);  
  <span style="white-space:pre">	</span>
        ......  
    }  

变量insetsPending的值等于true,表示Activity窗口有额外的内容区域边衬和可见区域边衬等待指定。

我们继续往下阅读代码:

<span style="color: rgb(51, 51, 51);">boolean contentInsetsChanged = false;  
boolean visibleInsetsChanged;  
......  
try {  
    ......  
    relayoutResult = </span><span style="color:#ff0000;">relayoutWindow</span><span style="color:#333333;">(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(); </span>

这段代码主要就是调用ViewRootImpl类的另外一个成员函数relayoutWindow来请求WindowManagerService服务计算Activity窗口的大小以及内容区域边衬大小和可见区域边衬大小。
计算完毕之后,Activity窗口的大小就会保存在ViewRootImpl类的成员变量mWinFrame中,而Activity窗口的内容区域边衬大小和可见区域边衬大小分别保存在ViewRootImpl类的成员变量mPendingContentInsets和mPendingVisibleInsets中。

        如果这次计算得到的Activity窗口的内容区域边衬大小mPendingContentInsets和可见区域边衬大小mPendingVisibleInsets与上一次计算得到的不一致,即与ViewRootImpl类的成员变量mAttachInfo所指向的一个AttachInfo对象的成员变量mContentInsets和mVisibleInsets所描述的大小不一致,那么变量contentInsetsChanged和visibleInsetsChanged的值就会等于true,表示Activity窗口的内容区域边衬大小和可见区域边衬大小发生了变化。

        由于变量frame和ViewRoot类的成员变量mWinFrame引用的是同一个Rect对象,因此,这时候变量frame描述的也是Activity窗口请求WindowManagerService服务计算之后得到的大小。这段代码分别将计算得到的Activity窗口的左上角坐标保存在变量attachInfo所指向的一个AttachInfo对象的成员变量mWindowLeft和mWindowTop中,并且将计算得到的Activity窗口的宽度和高度保存在ViewRootImpl类的成员变量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;  
} 

这段代码用来检查是否需要重新测量Activity窗口的大小。

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) {  
            }  
        }  
    }  
        
    ......  
}  
  
......  

Activity窗口额外指定的内容区域边衬大小和可见区域边衬大小是通过调用变量attachInfo所指向的一个AttachInfo对象的成员变量mTreeObserver所描述的一个TreeObserver对象的成员函数dispatchOnComputeInternalInsets来计算的。计算完成之后,就会保存在变量attachInfo所指向的一个AttachInfo对象的成员变量mGivenInternalInsets中,并且会通过ViewRoot类的静态成员变量sWindowSession所指向一个Binder代理对象来设置到WindowManagerService服务中去。

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;  
    }  
  
    ......  
}  

ViewRootImpl类的静态成员变量sWindowSession是一个Binder代理对象,它引用了运行在WindowManagerService服务这一侧的一个Session对象,ViewRootImpl类的成员函数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来对它们进行转换。

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;  
        }  
  
        ......  
    }  
  
    ......  
}  

这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

        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);  
    }  
  
    ......  
}  

 参数client是一个Binder代理对象,它引用了运行在应用程序进程这一侧中的一个W对象,用来标志一个Activity窗口。在应用程序进程这一侧的每一个W对象,在WindowManagerService服务这一侧都有一个对应的WindowState对象,用来描述一个Activity窗口的状态。因此,WindowManagerService类的成员函数relayoutWindow首先通过调用另外一个成员函数windowForClientLocked来获得与参数client所对应的一个WindowState对象win,以便接下来可以对它进行操作。

1. 参数requestedWidth和requestedHeight描述的是应用程序进程请求设置Activity窗口中的宽度和高度,它们会被记录在WindowState对象win的成员变量mRequestedWidth和mRequestedHeight中。

2. WindowState对象win的成员变量mAttr,它指向的是一个WindowManager.LayoutParams对象,用来描述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中,以便可以返回给应用程序进程。

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的,而在刷新的过程中,就会对系统中的各个窗口的大小进行计算。
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?  
        ......  
    }  
  
    ......  
}  

1. 在一个最多执行7次的while循环中,做两件事情:第一件事情是计算各个窗品的大小,这是通过调用另外一个成员函数performLayoutLockedInner来实现的;第二件事情是执行窗口的动画,主要是处理窗口的启动窗口显示动画和窗口切换过程中的动画,以及更新各个窗口的可见性。注意,每一次while循环执行之后,如果发现系统中的各个窗口的相应布局属性不再发生变化,那么就不行执行下一次的while循环了,即该while循环可能不用执行7次就结束了。窗口的动画显示过程和窗口的可见性更新过程是相当复杂的,它们也是WindowManagerService服务最为核的地方,在后面的文章中,我们再详细分析。

 2. 经过第1点的操作之后,接下来就可以将各个窗口的属性,例如,大小、位置等属性,通知SurfaceFlinger服务了,也就是让SurfaceFlinger服务更新它里面的各个Layer的属性值,以便可以对这些Layer执行可见性计算、合成等操作,最后渲染到硬件帧缓冲区中去。SurfaceFlinger服务计算系统中各个窗口,即各个Layer的可见性。注意,各个窗口的属性更新操作是被包含在SurfaceFlinger服务的一个事务中的,即一个Transaction中,这样做是为了避免每更新一个窗口的一个属性就触发SurfaceFlinger服务重新计算各个Layer的可见性,以及对各个Layer进行合并和渲染的操作。启动SurfaceFlinger服务的一个事务可以通过调用Surface类的静态成员函数openTransaction来实现,而关闭SurfaceFlinger服务的一个事务可以通过调用Surface类的静态成员函数closeTransaction来实现。

3. 经过第1点和第2点的操作之后,一次系统UI的刷新过程就完成了,这时候就会将系统中的那些不会再显示的窗口的绘图表面销毁掉,并且将那些已经完成退出了的窗口令牌
WindowToken移除掉,以及将那些已经退出了的Activity窗口令牌
AppWindowToken也移除掉。这一步实际执行的是窗口清理操作。

 Step 7. WindowManagerService.performLayoutLockedInner

public class WindowManagerService extends IWindowManager.Stub  
        implements Watchdog.Monitor {  
    ......  
  
    final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager();  
    ......  
  
    /** 
     * Z-ordered (bottom-most first) list of all Window objects. 
     */  
    final ArrayList<WindowState> mWindows = new ArrayList<WindowState>();  
    ......  
  
    private final int performLayoutLockedInner() {  
        ......  
  
        final int dw = mDisplay.getWidth();  
        final int dh = mDisplay.getHeight();  
  
        final int N = mWindows.size();  
        int i;  
  
        ......  
  
        mPolicy.beginLayoutLw(dw, dh);  
  
        int seq = mLayoutSeq+1;  
        if (seq < 0) seq = 0;  
        mLayoutSeq = seq;  
  
        // First perform layout of any root windows (not attached  
        // to another window).  
        int topAttached = -1;  
        for (i = N-1; i >= 0; i--) {  
            WindowState win = mWindows.get(i);  
            ......  
  
            final AppWindowToken atoken = win.mAppToken;  
            final boolean gone = win.mViewVisibility == View.GONE  
                    || !win.mRelayoutCalled  
                    || win.mRootToken.hidden  
                    || (atoken != null && atoken.hiddenRequested)  
                    || win.mAttachedHidden  
                    || win.mExiting || win.mDestroying;  
            ......  
  
            if (!gone || !win.mHaveFrame) {  
                if (!win.mLayoutAttached) {  
                    mPolicy.layoutWindowLw(win, win.mAttrs, null);  
                    win.mLayoutSeq = seq;  
                    ......  
                } else {  
                    if (topAttached < 0) topAttached = i;  
                }  
            }  
        }  
        ......  
  
        for (i = topAttached; i >= 0; i--) {  
            WindowState win = mWindows.get(i);  
  
            // If this view is GONE, then skip it -- keep the current  
            // frame, and let the caller know so they can ignore it  
            // if they want.  (We do the normal layout for INVISIBLE  
            // windows, since that means "perform layout as normal,  
            // just don't display").  
            if (win.mLayoutAttached) {  
                ......  
  
                if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)  
                        || !win.mHaveFrame) {  
                    mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);  
                    win.mLayoutSeq = seq;  
                    ......  
                }  
            }  
        }  
  
        ......  
  
        return mPolicy.finishLayoutLw();  
    }  
  
    ......  
}  

在分析WindowManagerService类的成员函数performLayoutLockedInner的实现之前,我们首先介绍WindowManagerService类的两个成员变量mPolicy和mWindows:

        1. mPolicy指向的是一个窗口管理策略类,它是通过调用PolicyManager类的静态成员函数makeNewWindowManager来初始化的,在Phone平台中,它指向的是便是一个PhoneWindowManager对象,主要是用来制定窗口的大小计算策略。

        2. mWindows指向的是一个类型为WindowState的ArrayList,它里面保存的就是系统中的所有窗口,这些窗口是按照Z轴位置从小到大的顺序保存在这个ArrayList中的,也就是说,第i个窗口位于第i-1个窗口的上面,其中,i > 0。

理解了这两个成员变量的含义之后,我们就分析WindowManagerService类的成员函数performLayoutLockedInner的执行过程,主要是分三个阶段:

        1. 准备阶段:调用PhoneWindowManager类的成员函数beginLayoutLw来设置屏幕的大小。屏幕的大小可以通过调用WindowManagerService类的成员变量mDisplay所描述的一个Display对象的成员函数getWidth和getHeight来获得。

        2. 计算阶段:调用PhoneWindowManager类的成员函数layoutWindowLw来计算各个窗口的大小、内容区域边衬大小以及可见区域边衬大小。

        3. 结束阶段:调用PhoneWindowManager类的成员函数finishLayoutLw来执行一些清理工作。

接下来,我们就分别分析PhoneWindowManager类的成员函数beginLayoutLw、layoutWindowLw和finishLayoutLw的实现,以便可以了解Activity窗口的大小计算过程。

Step 8. PhoneWindowManager.beginLayoutLw

public class PhoneWindowManager implements WindowManagerPolicy {  
    ......  
  
    WindowState mStatusBar = null;  
    ......  
  
    // The current size of the screen.  
    int mW, mH;  
    // During layout, the current screen borders with all outer decoration  
    // (status bar, input method dock) accounted for.  
    int mCurLeft, mCurTop, mCurRight, mCurBottom;  
    // During layout, the frame in which content should be displayed  
    // to the user, accounting for all screen decoration except for any  
    // space they deem as available for other content.  This is usually  
    // the same as mCur*, but may be larger if the screen decor has supplied  
    // content insets.  
    int mContentLeft, mContentTop, mContentRight, mContentBottom;  
    // During layout, the current screen borders along with input method  
    // windows are placed.  
    int mDockLeft, mDockTop, mDockRight, mDockBottom;  
    // During layout, the layer at which the doc window is placed.  
    int mDockLayer;  
      
    static final Rect mTmpParentFrame = new Rect();  
    static final Rect mTmpDisplayFrame = new Rect();  
    static final Rect mTmpContentFrame = new Rect();  
    static final Rect mTmpVisibleFrame = new Rect();  
    ......  
  
    public void beginLayoutLw(int displayWidth, int displayHeight) {  
        mW = displayWidth;  
        mH = displayHeight;  
        mDockLeft = mContentLeft = mCurLeft = 0;  
        mDockTop = mContentTop = mCurTop = 0;  
        mDockRight = mContentRight = mCurRight = displayWidth;  
        mDockBottom = mContentBottom = mCurBottom = displayHeight;  
        mDockLayer = 0x10000000;  
  
        // decide where the status bar goes ahead of time  
        if (mStatusBar != null) {  
            final Rect pf = mTmpParentFrame;  
            final Rect df = mTmpDisplayFrame;  
            final Rect vf = mTmpVisibleFrame;  
            pf.left = df.left = vf.left = 0;  
            pf.top = df.top = vf.top = 0;  
            pf.right = df.right = vf.right = displayWidth;  
            pf.bottom = df.bottom = vf.bottom = displayHeight;  
  
            mStatusBar.computeFrameLw(pf, df, vf, vf);  
            if (mStatusBar.isVisibleLw()) {  
                // If the status bar is hidden, we don't want to cause  
                // windows behind it to scroll.  
                mDockTop = mContentTop = mCurTop = mStatusBar.getFrameLw().bottom;  
                ......  
            }  
        }  
    }  
   
    ......  
} 

在分析PhoneWindowManager类的成员函数beginLayoutLw的实现之前,我们首先介绍PhoneWindowManager类的五组成员变量。

        第一组成员变量是mW和mH,它们分别用来描述当前这轮窗口大小计算过程的屏幕宽度和高度。

        第二组成员变量是mCurLeft、mCurTop、mCurRight和mCurBottom,它们组成一个四元组(mCurLeft, mCurTop, mCurRight, mCurBottom),用来描述当前这轮窗口大小计算过程的屏幕装饰区,它对应于前面所提到的Activity窗口的可见区域边衬。

        第三组成员变量是mContentLeft、mContentTop、mContentRight和mContentBottom,它们组成一个四元组(mContentLeft, mContentTop, mContentRight, mContentBottom),也是用来描述当前这轮窗口大小计算过程的屏幕装饰区,不过它对应的是前面所提到的Activity窗口的内容区域边衬。

        第四组成员变量是mDockLeft、mDockTop、mDockRight、mDockBottom和mDockLayer,其中,前四个成员变量组成一个四元组(mDockLeft, mDockTop, mDockRight, mDockBottom),用来描述当前这轮窗口大小计算过程中的输入法窗口所占据的位置,后一个成员变量mDockLayer用来描述输入法窗品的Z轴位置,输入法窗口的Z轴被初始化为0x10000000,这个值是相当大的了,可以保证输入法窗口作为顶层窗口出现

        第五组成员变量是mTmpParentFrame、mTmpDisplayFrame、mTmpContentFrame和mTmpVisibleFrame,它们是一组临时Rect区域,用来作为参数传递给具体的窗口计算大小的,避免每次都创建一组新的Rect区域来作来参数传递窗口。

这一步执行完成之后,返回到前面的Step 7中,即WindowManagerService类的成员函数performLayoutLockedInner,接下来就会调用PhoneWindowManager类的成员函数layoutWindowLw来计算系统中各个可见窗口的大小。

Step 9. PhoneWindowManager.layoutWindowLw

public class PhoneWindowManager implements WindowManagerPolicy {  
    ......  
  
    public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs,  
            WindowState attached) {  
        // we've already done the status bar  
        if (win == mStatusBar) {  
            return;  
        }  
        ......  
  
        final int fl = attrs.flags;  
        final int sim = attrs.softInputMode;  
  
        final Rect pf = mTmpParentFrame;  
        final Rect df = mTmpDisplayFrame;  
        final Rect cf = mTmpContentFrame;  
        final Rect vf = mTmpVisibleFrame;  
  
        if (attrs.type == TYPE_INPUT_METHOD) {  
            pf.left = df.left = cf.left = vf.left = mDockLeft;  
            pf.top = df.top = cf.top = vf.top = mDockTop;  
            pf.right = df.right = cf.right = vf.right = mDockRight;  
            pf.bottom = df.bottom = cf.bottom = vf.bottom = mDockBottom;  
            // IM dock windows always go to the bottom of the screen.  
            attrs.gravity = Gravity.BOTTOM;  
            mDockLayer = win.getSurfaceLayer();  
        } else {  
            if ((fl &  
                    (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR))  
                    == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {  
                // This is the case for a normal activity window: we want it  
                // to cover all of the screen space, and it can take care of  
                // moving its contents to account for screen decorations that  
                // intrude into that space.  
                if (attached != null) {  
                    // If this window is attached to another, our display  
                    // frame is the same as the one we are attached to.  
                    setAttachedWindowFrames(win, fl, sim, attached, true, pf, df, cf, vf);  
                } else {  
                    pf.left = df.left = 0;  
                    pf.top = df.top = 0;  
                    pf.right = df.right = mW;  
                    pf.bottom = df.bottom = mH;  
                    if ((sim & SOFT_INPUT_MASK_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;  
                    }  
                    vf.left = mCurLeft;  
                    vf.top = mCurTop;  
                    vf.right = mCurRight;  
                    vf.bottom = mCurBottom;  
                }  
            }  
  
            ......  
        }  
  
        win.computeFrameLw(pf, df, cf, vf);  
  
        // Dock windows carve out the bottom of the screen, so normal windows  
        // can't appear underneath them.  
        if (attrs.type == TYPE_INPUT_METHOD && !win.getGivenInsetsPendingLw()) {  
            int top = win.getContentFrameLw().top;  
            top += win.getGivenContentInsetsLw().top;  
            if (mContentBottom > top) {  
                mContentBottom = top;  
            }  
            top = win.getVisibleFrameLw().top;  
            top += win.getGivenVisibleInsetsLw().top;  
            if (mCurBottom > top) {  
                mCurBottom = top;  
            }  
            ......  
        }  
    }  
  
    ......  
}

第一个参数win描述的是当前要计算大小的窗口,第二个参数attrs描述的是窗口win的布局参数,第三个参数attached描述的是窗口win的父窗口,如果它的值等于null,就表示窗口win没有父窗口。

在计算一个窗口的大小的时候,我们需要四个参数。第一个参数是父窗口的大小pf,第二个参数是屏幕的大小df,第三个参数是内容区域边衬大小cf,第四个参数是可见区域边衬大小vf。 

得到了用来计算窗口win四个参数pf、 df、cf和vf之后,就可以调用参数win所描述的一个WindowState对象的成员函数computeFrameLw来计算窗口win的具体大小了。计算的结果便得到了窗口win的大小,以及它的内容区域边衬大小和可见区域边衬大小。

接下来,我们就继续分析WindowState类的成员函数computeFrameLw的实现,以便可以了解Activity窗口的大小计算的具体过程。

Step 10. WindowState.computeFrameLw

public class WindowManagerService extends IWindowManager.Stub  
        implements Watchdog.Monitor {  
    ......  
  
    private final class WindowState implements WindowManagerPolicy.WindowState {  
        ......  
  
        boolean mHaveFrame;  
        ......  
  
        // "Real" frame that the application sees.  
        final Rect mFrame = new Rect();  
        ......  
  
        final Rect mContainingFrame = new Rect();  
        final Rect mDisplayFrame = new Rect();  
        final Rect mContentFrame = new Rect();  
        final Rect mVisibleFrame = new Rect();  
  
  
        public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) {  
            mHaveFrame = true;  
  
            final Rect container = mContainingFrame;  
            container.set(pf);  
  
            final Rect display = mDisplayFrame;  
            display.set(df);  
  
            if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) {  
                container.intersect(mCompatibleScreenFrame);  
                if ((mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) == 0) {  
                    display.intersect(mCompatibleScreenFrame);  
                }  
            }  
  
            final int pw = container.right - container.left;  
            final int ph = container.bottom - container.top;  
  
            int w,h;  
            if ((mAttrs.flags & mAttrs.FLAG_SCALED) != 0) {  
                w = mAttrs.width < 0 ? pw : mAttrs.width;  
                h = mAttrs.height< 0 ? ph : mAttrs.height;  
            } else {  
                w = mAttrs.width == mAttrs.MATCH_PARENT ? pw : mRequestedWidth;  
                h = mAttrs.height== mAttrs.MATCH_PARENT ? ph : mRequestedHeight;  
            }  
  
            final Rect content = mContentFrame;  
            content.set(cf);  
  
            final Rect visible = mVisibleFrame;  
            visible.set(vf);  
  
            final Rect frame = mFrame;  
            final int fw = frame.width();  
            final int fh = frame.height();  
  
            ......  
  
            Gravity.apply(mAttrs.gravity, w, h, container,  
                    (int) (mAttrs.x + mAttrs.horizontalMargin * pw),  
                    (int) (mAttrs.y + mAttrs.verticalMargin * ph), frame);  
  
            ......  
  
            // Now make sure the window fits in the overall display.  
            Gravity.applyDisplay(mAttrs.gravity, df, frame);  
  
            // Make sure the content and visible frames are inside of the  
            // final window frame.  
            if (content.left < frame.left) content.left = frame.left;  
            if (content.top < frame.top) content.top = frame.top;  
            if (content.right > frame.right) content.right = frame.right;  
            if (content.bottom > frame.bottom) content.bottom = frame.bottom;  
            if (visible.left < frame.left) visible.left = frame.left;  
            if (visible.top < frame.top) visible.top = frame.top;  
            if (visible.right > frame.right) visible.right = frame.right;  
            if (visible.bottom > frame.bottom) visible.bottom = frame.bottom;  
  
            final Rect contentInsets = mContentInsets;  
            contentInsets.left = content.left-frame.left;  
            contentInsets.top = content.top-frame.top;  
            contentInsets.right = frame.right-content.right;  
            contentInsets.bottom = frame.bottom-content.bottom;  
  
            final Rect visibleInsets = mVisibleInsets;  
            visibleInsets.left = visible.left-frame.left;  
            visibleInsets.top = visible.top-frame.top;  
            visibleInsets.right = frame.right-visible.right;  
            visibleInsets.bottom = frame.bottom-visible.bottom;  
  
            if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) {  
                updateWallpaperOffsetLocked(this, mDisplay.getWidth(),  
                        mDisplay.getHeight(), false);  
            }  
  
            ......  
        }  
  
        ......  
    }  
  
    ......  
}

这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

WindowState类的成员变量mHaveFrame用来描述一个窗口的大小是否计算过了。当WindowState类的成员函数computeFrameLw被调用的时候,就说明一个相应的窗口的大小得到计算了,因此,WindowState类的成员函数computeFrameLw一开始就会将成员变量mHaveFrame的值设置为true。

WindowState类的成员变量mContainingFrame和mDisplayFrame描述的是当前正在处理的窗口的父窗口和屏幕的大小,它们刚好就分别等于参数pf和df的大小,因此,函数就直接将参数pf和df的值分别保存在WindowState类的成员变量mContainingFrame和mDisplayFrame中。如果当前正在处理的窗口运行在兼容模式,即WindowState类的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量flags的FLAG_COMPATIBLE_WINDOW位等于1,那么就需要将其父窗口的大小限制mContainingFrame在兼容模式下的屏幕区域中。兼容模式下的屏幕区域保存在WindowManagerService类的成员变量mCompatibleScreenFrame中,将父窗口的大小mContainingFrame与它执行一个相交操作,就可以将父窗品的大小限制兼容模式下的屏幕区域中。在当前正在处理的窗口运行在兼容模式的情况下,如果它的大小被限制在了兼容模式下的屏幕区域之中,即WindowState类的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量flags的FLAG_LAYOUT_NO_LIMITS位等于0,那么同样需要将屏幕大小mDisplayFrame限制在兼容模式下的屏幕区域mCompatibleScreenFrame,这也是通过执行一个相交操作来完成的。

        WindowState类的成员变量mContentFrame和mVisibleFrame描述的是当前正在处理的窗口的内容区域和可见区域大小,它们刚好就分别等于参数cf和vf的大小,因此,函数就直接将参数cf和vf的值分别保存在WindowState类的成员变量mContainingFrame和mDisplayFrame中。现在,就剩下窗口的大小还没有计算。一旦窗口大小确定下来之后,就可以继续计算窗口的内容区域边衬和可见区域边衬大小了。接下来我们就继续分析窗口大小的计算过程。

         WindowState类的成员变量mFrame描述的就是当前正在处理的窗品的大小,我们的目标就是计算它的值。一个窗口的大小是受以下因素影响的:

         1. 是否指定了缩放因子。如果一个窗口的大小被指定了缩放因子,即WindowState类的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量flags的FLAG_SCALED位等于1,那么该窗口的大小就是在它的布局参数中指定的,即是由WindowState类的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量width和height所指定的。但是,如果在布局参数中指定的窗口宽度或者高度小于0,那么就会使用其父窗口的大小来作为当前窗口的大小。当前窗口的父窗口的宽度和高度分别保存在变量pw和ph中。

         2. 是否指定了等于父窗口的大小。如果一个窗口的大小被指定为其父窗口的大小,即WindowState类的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量width和height的值等于mAttrs.MATCH_PARENT,那么该窗口的大小就会等于其父窗口的大小,即等于变量pw和ph所描述的宽度和高度。另一方面,如果一个窗口的大小没有指定为其父窗口的大小的话,那么它的大小就会等于应用程序进程请求WindowManagerService所设置的大小,即等于WindowState类的成员变量mRequestedWidth和mRequestedHeight所描述的宽度和高度。

         经过上述2个操作之后,我们就初步地得到了窗口的宽度w和高度h,但是,它们还不是最终的窗口大小,还要进一步地根据窗口的Gravity属性来作调整,这个调整分两步进行:

        1. 根据窗口的Gravity值,以及位置、初始大小和父窗口大小,来计算窗口的大小,并且保存在变量frame中,即保存在WindowState类的成员变量mFrame中,这是通过调用Gravity类的静态成员函数apply来实现的。其中,窗口的初始大小保存在变量w和h中,父窗口大小保存在变量container中,即WindowState类的成员变量mContainingFrame中,位置保存在WindowState类的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量x和y中。注意,如果窗口指定了相对父窗口的margin值,那么还需要相应的调整其位置值,即要在指定的位置值的基础上,再加上相对父窗口的margin值。一个窗口相对父窗口的margion是通过一个百分比来表示的,用这个百分比乘以父窗口的大小就可以得到绝对值。这个百分比又分为在水平方向和垂直方向两个值,分别保存在WindowState类的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量horizontalMargin和verticalMargin中。

        2. 前面计算得到的窗口大小没有考虑在屏幕的大小,因此,接下来还需要继续调用Gravity类的静态成员函数applyDisplay来将前面计算得到的窗口大小限制在屏幕区域df中,即限制在WindowState类的成员变量mDisplayFrame所描述的区域中。

        经过上述2个操作之后,窗口的最终大小就保存在变量frame中了,即WindowState类的成员变量mFrame中,接下来就可以计算窗品的内容区域边衬和可见区域边衬大小了。

        内容区域边衬和可见区域边衬大小的计算很简单的,只要将窗口的大小frame,即WindowState类的成员变量mFrame所描述的区域,分别减去变量content和visible,即WindowState类的成员变量mContentFrame和mVisibleFrame所描述的区域,就可以得到窗口的内容区域边衬和可见区域边衬大小,它们分别保存在WindowState类的成员变量mContentInsets和mVisibleInsets中。注意,在计算窗口的内容区域边衬和可见区域边衬大小之前,首先要保证窗口的内容区域和可见区域包含在整个窗口区域中,这一点是由中间的8个if语句来保证的。

        窗口上一次的大小保存在变量fw和fh中。如果当前正在处理的窗口是一个壁纸窗口,即WindowState类的成员变量mIsWallpaper的值等于true,并且该窗口的大小发生了变化,即变量fw和fh的所描述的窗口大小不等于变量frame描述的窗口大小,那么就需要调用WindowManagerService类的成员函数updateWallpaperOffsetLocked来更新壁纸的位置。在后面的文章中,我们再详细描述系统的壁纸窗口的位置是如何计算的。

        这一步执行完成之后,一个窗口的大小就计算完成了。从计算的过程可以知道,整个窗口大小保存在WindowState类的成员变量mFrame中,而窗品的内容区域边衬大小和可见区域边衬大小分别保在WindowState类的成员变量mContentInsets和mVisibleInsets中。这些值最终会通过前面的Step 4返回给应用程序进程。

        返回到前面的Step 7中,即WindowManagerService类的成员函数performLayoutLockedInner,接下来就会调用PhoneWindowManager类的成员函数finishLayoutLw来结束当前这轮窗口大小的计算工作。

Step 11. PhoneWindowManager.finishLayoutLw

public class PhoneWindowManager implements WindowManagerPolicy {  
    ......  
  
    public int finishLayoutLw() {  
        return 0;  
    }  
  
    ......  
} 

这个函数定义在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中。

PhoneWindowManager类的成员函数finishLayoutLw是设计来结束一轮窗口大小的计算过程中,不过目前它什么也不做,只是一个空实现。

至此,我们就分析完成Activity窗口的大小计算过程了。从这个计算过程中,我们就可以知道一个Activity窗口除了有一个整体大小之外,还有一个内容区域边衬大小和一个可见区域边衬大小。此外,我们还知道,一个Activity窗口的内容区域边衬大小和可见区域边衬大小是可能会受到与其所关联的输入法窗口的影响的,因为输入法窗口会叠加在该Activity窗口上面,这就涉及到了系统中的窗口的组织方式。

二、窗口的组织方式

Activity是以堆栈的形式组织在ActivityManagerService服务中的。与Activity类似,Android系统中的窗口也是以堆栈的形式组织在WindowManagerService服务中的,其中,Z轴位置较低的窗口位于Z轴位置较高的窗口的下面。在本文中,我们就详细分析WindowManagerService服务是如何以堆栈的形式来组织窗口的。

Activity管理服务ActivityManagerService中每一个ActivityRecord对象在Window管理服务WindowManagerService中都对应有一个AppWindowToken对象。此外,在输入法管理服务InputMethodManagerService中,每一个输入法窗口都对应有一个Binder对象,这个Binder对象在Window管理服务WindowManagerService又对应有一个WindowToken对象。与输入法窗口类似,在壁纸管理服务WallpaperManagerService中,每一个壁纸窗口都对应有一个Binder对象,这个Binder对象在Window管理服务WindowManagerService也对应有一个WindowToken对象。

在Window管理服务WindowManagerService中,无论是AppWindowToken对象,还是WindowToken对象,它们都是用来描述一组有着相同令牌的窗口的,每一个窗口都是通过一个WindowState对象来描述的。例如,一个Activity组件窗口可能有一个启动窗口(Starting Window),还有若干个子窗口,那么这些窗口就会组成一组,并且都是以Activity组件在Window管理服务WindowManagerService中所对应的AppWindowToken对象为令牌的。从抽象的角度来看,就是在Window管理服务WindowManagerService中,每一个令牌(AppWindowToken或者WindowToken)都是用来描述一组窗口(WindowState)的,并且每一个窗口的子窗口也是与它同属于一个组,即都有着相同的令牌。

 上述的窗口组织方式如图1所示:

《WindowManagerService计算窗口大小的过程和窗口组织方式分析》

图1 窗口在WindowManagerService服务中的组织方式

其中,Activity Stack是在ActivityManagerService服务中创建的,Token List和Window Stack是在WindowManagerService中创建的,而Binder for IM和Binder for WP分别是在InputMethodManagerService服务和WallpaperManagerService服务中创建的,用来描述一个输入法窗口和一个壁纸窗口。

图1中的对象的对应关系如下所示:

       1. ActivityRecord-J对应于AppWindowToken-J,后者描述的一组窗口是{WindowState-A, WindowState-B, WindowState-B-1},其中, WindowState-B-1是WindowState-B的子窗口。

       2. ActivityRecord-K对应于AppWindowToken-K,后者描述的一组窗口是{WindowState-C, WindowState-C-1, WindowState-D, WindowState-D-1},其中, WindowState-C-1是WindowState-C的子窗口,WindowState-D-1是WindowState-D的子窗口。

       3. ActivityRecord-N对应于AppWindowToken-N,后者描述的一组窗口是{WindowState-E},其中, WindowState-E是系统当前激活的Activity窗口。

       4. Binder for IM对应于WindowToken-I,后者描述的一组窗口是{WindowState-I},其中, WindowState-I是WindowState-E的输入法窗口。

       5. Binder for WP对应于WindowToken-W,后者描述的一组窗口是{WindowState-W},其中, WindowState-W是WindowState-E的壁纸窗口。

       从图1还可以知道,Window Stack中的WindowState是按照它们所描述的窗口的Z轴位置从低到高排列的。

 以上就是WindowManagerService服务组织系统中的窗口的抽象模型,接下来我们将分析AppWindowToken、WindowToken和WindowState的一些增加、移动和删除等操作,以便可以对这个抽象模型有一个更深刻的认识。

 1.  增加AppWindowToken

一个Activity组件在启动的过程中,ActivityManagerService服务会调用调用WindowManagerService类的成员函数addAppToken来为它增加一个AppWindowToken,如下所示:

public class WindowManagerService extends IWindowManager.Stub    
        implements Watchdog.Monitor {    
    ......    
    
    /**  
     * Mapping from a token IBinder to a WindowToken object.  
     */    
    final HashMap<IBinder, WindowToken> mTokenMap =    
            new HashMap<IBinder, WindowToken>();    
    
    /**  
     * The same tokens as mTokenMap, stored in a list for efficient iteration  
     * over them.  
     */    
    final ArrayList<WindowToken> mTokenList = new ArrayList<WindowToken>();    
    ......    
    
    /**  
     * Z-ordered (bottom-most first) list of all application tokens, for  
     * controlling the ordering of windows in different applications.  This  
     * contains WindowToken objects.  
     */    
    final ArrayList<AppWindowToken> mAppTokens = new ArrayList<AppWindowToken>();    
    ......    
    
    public void addAppToken(int addPos, IApplicationToken token,    
            int groupId, int requestedOrientation, boolean fullscreen) {    
        ......    
    
        synchronized(mWindowMap) {    
            AppWindowToken wtoken = findAppWindowToken(token.asBinder());    
            if (wtoken != null) {    
                ......    
                return;    
            }    
            wtoken = new AppWindowToken(token);    
            ......    
            mAppTokens.add(addPos, wtoken);    
            ......    
            mTokenMap.put(token.asBinder(), wtoken);    
            mTokenList.add(wtoken);    
    
            ......   
        }    
    }    
    
    ......    
}    

 WindowManagerService类有三个成员变量mTokenMap、mTokenList和mAppTokens,它们都是用来描述系统中的窗口的。

成员变量mTokenMap指向的是一个HashMap,它里面保存的是一系列的WindowToken对象,每一个WindowToken对象都是用来描述一个窗口的,并且是以描述这些窗口的一个Binder对象的IBinder接口为键值的。例如,对于Activity组件类型的窗口来说,它们分别是以用来描述它们的一个ActivityRecord对象的IBinder接口保存在成员变量mTokenMap所指向的一个HashMap中的。

 成员变量mTokenList指向的是一个ArrayList,它里面保存的也是一系列WindowToken对象,这些WindowToken对象与保存在成员变量mTokenMap所指向的一个HashMap中的WindowToken对象是一样的。成员变量mTokenMap和成员变量mTokenList的区别就在于,前者在给定一个IBinder接口的情况下,可以迅速指出是否存在一个对应的WindowToken对象,而后者可以迅速遍历WindowManagerService服务中的WindowToken对象。

成员变量mAppTokens指向的也是一个ArrayList,不过它里面保存的是一系列AppWindowToken对象,每一个AppWindowToken对象都是用来描述一个Activity组件窗口的,而这些AppWindowToken对象是以它们描述的窗口的Z轴坐标由小到大保存在这个ArrayList中的,这样我们就可以通过这个ArrayList来从上到下或者从下到上地遍历系统中的所有Activity组件窗口。由于这些AppWindowToken对象所描述的Activity组件窗口也是一个窗口,并且AppWindowToken类是从WindowToken继承下来的,因此,这些AppWindowToken对象还会同时被保存在成员变量mTokenMap所指向的一个HashMap和成员变量mTokenList所指向的一个ArrayList中。

WindowManagerService类的成员函数addAppToken首先调用另外一个成员函数findAppWindowToken来在成员变量mTokenMap所描述的一个HashMap检查是否已经存在一个AppWindowToken。如果已经存在的话,那么WindowManagerService类的成员函数addAppToken就什么也不做就返回了,否则的话,就会使用参数token来创建一个AppWindowToken对象,并且会将该AppWindowToken对象分别保存在WindowManagerService类的成员变量mTokenMap、mTokenList和mAppTokens中。

2. 删除AppWindowToken

public class WindowManagerService extends IWindowManager.Stub    
        implements Watchdog.Monitor {    
    ......    
    
    private void removeAppTokensLocked(List<IBinder> tokens) {  
        // XXX This should be done more efficiently!  
        // (take advantage of the fact that both lists should be  
        // ordered in the same way.)  
        int N = tokens.size();  
        for (int i=0; i<N; i++) {  
            IBinder token = tokens.get(i);  
            final AppWindowToken wtoken = findAppWindowToken(token);  
            if (!mAppTokens.remove(wtoken)) {  
                ......  
                i--;  
                N--;  
            }  
        }  
    }  
   
    ......    
}

参数tokens所描述的是一个IBinder接口列表,与这些IBinder接口所对应的AppWindowToken对象就是接下来要删除的。WindowManagerService类的成员函数removeAppTokensLocked通过一个for循环来依次调用另外一个成员函数findAppWindowToken,以便可以找到保存在列表tokens中的每一个IBinder接口所对应的AppWindowToken对象,然后将该AppWindowToken对象从WindowManagerService类的成员变量mAppTokens所描述的一个ArrayList中删除。

        注意,WindowManagerService类的成员函数removeAppTokensLocked是在内部使用的,它只是把一个AppWindowToken对象从成员变量mAppTokens中删除,而没有从另外两个成员变量mTokenMap和mTokenList中删除。

 







    原文作者:qq_29427129
    原文地址: https://blog.csdn.net/qq_29427129/article/details/52439969
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞