Android6.0 WMS(五) WMS计算Activity窗口大小的过程分析(二)WMS的relayoutWindow

既上一篇博客,这篇我们分析WMS的relayoutWindow函数。

relayoutWindow

我们先看下relayoutWindow函数

    public int relayoutWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int requestedWidth,
            int requestedHeight, int viewVisibility, int flags,
            Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Configuration outConfig,
            Surface outSurface) {
        ......

        synchronized(mWindowMap) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            WindowStateAnimator winAnimator = win.mWinAnimator;
            if (viewVisibility != View.GONE && (win.mRequestedWidth != requestedWidth
                    || win.mRequestedHeight != requestedHeight)) {
                win.mLayoutNeeded = true;
                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 = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
            configChanged = updateOrientationFromAppTokensLocked(false);
            performLayoutAndPlaceSurfacesLocked();
            .....
            
            outFrame.set(win.mCompatFrame);
            outOverscanInsets.set(win.mOverscanInsets);
            outContentInsets.set(win.mContentInsets);
            outVisibleInsets.set(win.mVisibleInsets);
            outStableInsets.set(win.mStableInsets);
            outOutsets.set(win.mOutsets);

            inTouchMode = mInTouchMode;
            ......
        }
        ......

        return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0)
                | (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0)
                | (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0);
    }

参数client是一个Binder代理对象,它引用了运行在应用程序进程这一侧中的一个W对象,用来标志一个Activity窗口。在应用程序进程这一侧的每一个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窗口的大小计算过程。


performLayoutAndPlaceSurfacesLocked

我们再来看performLayoutAndPlaceSurfacesLocked函数,直接调用了performLayoutAndPlaceSurfacesLockedLoop函数。

    private final void performLayoutAndPlaceSurfacesLocked() {
        int loopCount = 6;
        do {
            mTraversalScheduled = false;
            performLayoutAndPlaceSurfacesLockedLoop();
            mH.removeMessages(H.DO_TRAVERSAL);
            loopCount--;
        } while (mTraversalScheduled && loopCount > 0);
        mInnerFields.mWallpaperActionPending = false;
    }

performLayoutAndPlaceSurfacesLockedLoop函数:

    private final void performLayoutAndPlaceSurfacesLockedLoop() {
        if (mInLayout) {
            return;
        }

        if (mWaitingForConfig) {
            return;
        }

        if (!mDisplayReady) {
            // Not yet initialized, nothing to do.
            return;
        }

        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
        mInLayout = true;

        boolean recoveringMemory = false;
        if (!mForceRemoves.isEmpty()) {
            recoveringMemory = true;
            // Wait a little bit for things to settle down, and off we go.
            while (!mForceRemoves.isEmpty()) {
                WindowState ws = mForceRemoves.remove(0);
                Slog.i(TAG, "Force removing: " + ws);
                removeWindowInnerLocked(ws);
            }

            Object tmp = new Object();
            synchronized (tmp) {
                try {
                    tmp.wait(250);
                } catch (InterruptedException e) {
                }
            }
        }

        try {
            performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);

            mInLayout = false;

            if (needsLayout()) {
                if (++mLayoutRepeatCount < 6) {
                    requestTraversalLocked();
                } else {
                    Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
                    mLayoutRepeatCount = 0;
                }
            } else {
                mLayoutRepeatCount = 0;
            }

            if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
                mH.removeMessages(H.REPORT_WINDOWS_CHANGE);
                mH.sendEmptyMessage(H.REPORT_WINDOWS_CHANGE);
            }
        } catch (RuntimeException e) {
            mInLayout = false;
            Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
        }

        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    }

从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的过程中,于是就不用往下执行了。


performLayoutAndPlaceSurfacesLockedInner刷新UI

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

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

WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLockedInner是一个巨无霸的函数,承载了WindowManagerService服务的核心功能。对于这样一个巨无霸函数,要逐行地分析它的实现是很困难的,因为要理解各种上下文信息,才可以清楚地知道它的执行过程。这里我们就大概地分析它的实现框架,以后再逐步地分析它的具体实现:

        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来实现。到SurfaceFlinger的各个Layer设置属性是在WindowState的WindowStateAnimator的setSurfaceBoundariesLocked函数中调用mSurfaceControl变量设置一些属性。

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

       上述三个操作是WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLockedInner的实现关键所在,理解了这三个操作,基本也就可以理解WindowManagerService服务刷新系统UI的过程了。

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

    private final void performLayoutLockedInner(final DisplayContent displayContent,
                                    boolean initial, boolean updateInputWindows) {
        if (!displayContent.layoutNeeded) {
            return;
        }
        displayContent.layoutNeeded = false;
        WindowList windows = displayContent.getWindowList();
        boolean isDefaultDisplay = displayContent.isDefaultDisplay;

        DisplayInfo displayInfo = displayContent.getDisplayInfo();
        final int dw = displayInfo.logicalWidth;
        final int dh = displayInfo.logicalHeight;

        if (mInputConsumer != null) {
            mInputConsumer.layout(dw, dh);
        }

        final int N = windows.size();
        int i;

        mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation);
        if (isDefaultDisplay) {
            // Not needed on non-default displays.
            mSystemDecorLayer = mPolicy.getSystemDecorLayerLw();
            mScreenRect.set(0, 0, dw, dh);
        }

        mPolicy.getContentRectLw(mTmpContentRect);
        displayContent.resize(mTmpContentRect);

        int seq = mLayoutSeq+1;
        if (seq < 0) seq = 0;
        mLayoutSeq = seq;

        boolean behindDream = false;

        // First perform layout of any root windows (not attached
        // to another window).
        int topAttached = -1;
        for (i = N-1; i >= 0; i--) {
            final WindowState win = windows.get(i);
            final boolean gone = (behindDream && mPolicy.canBeForceHidden(win, win.mAttrs))
                    || win.isGoneForLayoutLw();


            if (!gone || !win.mHaveFrame || win.mLayoutNeeded
                    || ((win.isConfigChanged() || win.setInsetsChanged()) &&
                            ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
                            (win.mHasSurface && win.mAppToken != null &&
                            win.mAppToken.layoutConfigChanges)))) {
                if (!win.mLayoutAttached) {
                    if (initial) {
                        win.mContentChanged = false;
                    }
                    if (win.mAttrs.type == TYPE_DREAM) {
                        behindDream = true;
                    }
                    win.mLayoutNeeded = false;
                    win.prelayout();
                    mPolicy.layoutWindowLw(win, null);
                    win.mLayoutSeq = seq;
                } else {
                    if (topAttached < 0) topAttached = i;
                }
            }
        }

        boolean attachedBehindDream = false;

        for (i = topAttached; i >= 0; i--) {
            final WindowState win = windows.get(i);

            if (win.mLayoutAttached) {
                if (attachedBehindDream && mPolicy.canBeForceHidden(win, win.mAttrs)) {
                    continue;
                }
                if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
                        || !win.mHaveFrame || win.mLayoutNeeded) {
                    if (initial) {
                        //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
                        win.mContentChanged = false;
                    }
                    win.mLayoutNeeded = false;
                    win.prelayout();
                    mPolicy.layoutWindowLw(win, win.mAttachedWindow);
                    win.mLayoutSeq = seq;
                }
            } else if (win.mAttrs.type == TYPE_DREAM) {
                // Don't layout windows behind a dream, so that if it
                // does stuff like hide the status bar we won't get a
                // bad transition when it goes away.
                attachedBehindDream = behindDream;
            }
        }
        ......
        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来执行一些清理工作。

        按照父子关系来划分,系统中的窗口可以分为父窗口和子窗口两种。如果一个WindowState对象的成员变量mLayoutAttached的值等于false,那么它所描述的窗口就可以作为一个父窗口,否则的话,它所描述的窗口就是一个子窗口。由于子窗口的大小计算是依赖于其父窗口的,因此,在计算各个窗口的大小的过程中,即在上述的第2阶段中,按照以下方式来进行:
        1.  先计算父窗口的大小。一般来说,能够作为父窗口的,是那些Activity窗口。如果一个窗口是Activity窗口,那么用来描述它的一个WindowState对象的成员变量mAppToken就不等于null,并且指向的是一个AppWindowToken对象。这个AppWindowToken对象主要是用来描述一个Activity,即与ActivityManagerService服务中的一个ActivityRecord对象对应。一个Activity窗口只有在两种情况下才会被计算大小:第一种情况是窗口不是处于不可见状态的;第二种情况是窗口从来还没有被计算过大小,即用来描述该Activity窗口的WindowState对象的成员变量mHaveFrame的值等于false,这种情况一般发生在窗口刚刚被添加到WindowManagerService的过程中。一个Activity窗口的不可见状态由它本身的状态、它所在的窗口结构树状态以及它所属的Activity的状态有关,也就是说,如果一个Activity窗口本身是可见的,但是由于它的父窗口、它所在的窗口组的根窗口或者它所属的Activity是不可见的,那么该Activity窗口也是不可见的。一个Activity窗口的不可见状态由以下因素决定:

        1). 它本身处于不可见状态,即对应的WindowState对象的成员变量mViewVisibility的值等于View.GONE;

        2). 它本身处于正在退出的状态,即对应的WindowState对象的成员变量mExiting的值等于true;

        3). 它本身处于正在销毁的状态,即对应的WindowState对象的成员变量mDestroying的值等于true;

        4). 它的父窗口处于不可见状态,即对应的WindowState对象的成员变量mAttachedHidden的值等于true;

        5). 它所在窗口结构树中的根窗口处于不可见状态,即对应的WindowState对象的成员变量mRootToken所描述的一个WindowToken对象的成员变量hidden的值等于true;

        6). 它所属的Activity处于不可见状态,即对应的WindowState对象的成员变量mAppToken所描述的一个AppWindowToken对象的成员变量hiddenRequested的值等于true。

        除了上述六个因素之外,如果一个Activity窗口没有被它所运行在的应用程序进程主动请求WindowManagerService服务对它进行布局,即对应的WindowState对象的成员变量mRelayoutCalled的值等于false,那么此时也是不需要计算Activity窗口的大小的。

       一个Activity窗口的大小一旦确定是需要计算大小之后,PhoneWindowManager类的成员函数layoutWindowLw就被调用来计算它的大小。

       2. 接着计算子窗口的大小。前面在计算父窗口的大小过程中,会记录位于系统最上面的一个子窗口在mWindows所描述的一个ArrayList的位置topAttached,接下来就可以从这个位置开始向下计算每一个子窗口的大小。一个子窗口在以下两种情况下,才会被计算大小:

       1). 它本身处于可见状态,即对应的WindowState对象的成员变量mViewVisibility的值不等于View.GONE,并且它所运行在的应用程序进程主动请求WindowManagerService服务对它进行布局,即对应的WindowState对象的成员变量mRelayoutCalled的值等于true。

       2). 它从来还没有被计算过大小,即用来描述该子窗口的WindowState对象的成员变量mHaveFrame的值等于false,这种情况一般发生在子窗口刚刚被添加到WindowManagerService的过程中。

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


PhoneWindowManager的beginLayoutLw

    public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
                              int displayRotation) {
        mDisplayRotation = displayRotation;
        final int overscanLeft, overscanTop, overscanRight, overscanBottom;
        if (isDefaultDisplay) {
            switch (displayRotation) {
                case Surface.ROTATION_90:
                    overscanLeft = mOverscanTop;
                    overscanTop = mOverscanRight;
                    overscanRight = mOverscanBottom;
                    overscanBottom = mOverscanLeft;
                    break;
                case Surface.ROTATION_180:
                    overscanLeft = mOverscanRight;
                    overscanTop = mOverscanBottom;
                    overscanRight = mOverscanLeft;
                    overscanBottom = mOverscanTop;
                    break;
                case Surface.ROTATION_270:
                    overscanLeft = mOverscanBottom;
                    overscanTop = mOverscanLeft;
                    overscanRight = mOverscanTop;
                    overscanBottom = mOverscanRight;
                    break;
                default:
                    overscanLeft = mOverscanLeft;
                    overscanTop = mOverscanTop;
                    overscanRight = mOverscanRight;
                    overscanBottom = mOverscanBottom;
                    break;
            }
        } else {
            overscanLeft = 0;
            overscanTop = 0;
            overscanRight = 0;
            overscanBottom = 0;
        }
        mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
        mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
        mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
        mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
        mSystemLeft = 0;
        mSystemTop = 0;
        mSystemRight = displayWidth;
        mSystemBottom = displayHeight;
        mUnrestrictedScreenLeft = overscanLeft;
        mUnrestrictedScreenTop = overscanTop;
        mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
        mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
        mRestrictedScreenLeft = mUnrestrictedScreenLeft;
        mRestrictedScreenTop = mUnrestrictedScreenTop;
        mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
        mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
        mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
                = mCurLeft = mUnrestrictedScreenLeft;
        mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
                = mCurTop = mUnrestrictedScreenTop;
        mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
                = mCurRight = displayWidth - overscanRight;
        mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
                = mCurBottom = displayHeight - overscanBottom;
        mDockLayer = 0x10000000;
        mStatusBarLayer = -1;

        // start with the current dock rect, which will be (0,0,displayWidth,displayHeight)
        final Rect pf = mTmpParentFrame;
        final Rect df = mTmpDisplayFrame;
        final Rect of = mTmpOverscanFrame;
        final Rect vf = mTmpVisibleFrame;
        final Rect dcf = mTmpDecorFrame;
        final Rect osf = mTmpOutsetFrame;
        pf.left = df.left = of.left = vf.left = mDockLeft;
        pf.top = df.top = of.top = vf.top = mDockTop;
        pf.right = df.right = of.right = vf.right = mDockRight;
        pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;

Android能够支持智能电视作为显示设备、电视或者大的液晶显示器四周有一圈黑色的区域,称为overscan(过扫描)区域,这个区域也是显示屏的一部分,但是通常不能显示。
在PhoneWindowManager中如下表示
    int mOverscanLeft = 0;
    int mOverscanTop = 0;
    int mOverscanRight = 0;
    int mOverscanBottom = 0;
这4个变量分别表示overscan区距离四个方向的距离。这4个变量的初始化在配置显示设备是完成的。这个函数在WMS中,该函数使用了保存在显示设备对象中的overscan数据来调用PhoneWindowManager的setDisplayOverscan函数来设置这4个变量

    private void configureDisplayPolicyLocked(DisplayContent displayContent) {
        mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
                displayContent.mBaseDisplayWidth,
                displayContent.mBaseDisplayHeight,
                displayContent.mBaseDisplayDensity);

        DisplayInfo displayInfo = displayContent.getDisplayInfo();
        mPolicy.setDisplayOverscan(displayContent.getDisplay(),
                displayInfo.overscanLeft, displayInfo.overscanTop,
                displayInfo.overscanRight, displayInfo.overscanBottom);
    }
setDisplayOverscan函数如下:

    @Override
    public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
        if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
            mOverscanLeft = left;
            mOverscanTop = top;
            mOverscanRight = right;
            mOverscanBottom = bottom;
        }
    }

屏幕上除了overscan区域还有状态栏,导航栏、输入法区域,PhoneWindowManager定义各种区域代表屏幕上不同区域的组合。

屏幕窗口的区域划分

1.overscanScreen区域,这个区域包括屏幕的overscan区域,相当于整个屏幕

2.RestrictedOverScanScreen区域,包括overscan区,不包含导航栏、因此这个区域上面到屏幕的顶部,下面就到导航栏的顶部。

3.RestrictedScreen区域,这个区域不包含overscan区域不包含导航栏

4.UnRestrictedScreen区域,不包含屏幕的overscan区域、包含状态栏和导航栏

5.stableFullScreen区域,包含状态栏、输入法、不包含导航栏

6.Decor区域,不包含状态栏、不包含导航栏、包含输入法区域

7.Curren区域、不包含状态栏、不包含导航栏、不包含输入法区域

PhoneWindowManager一共定义了10种区域,剩下的system区域、Stable区域、Content区域的范围和Decor区域相同。这些区域在不同场合含义不同,但是值是相同的。

beginLayoutLw方法逻辑很简单,就是做了下面工作。

首先根据各个区域是否延伸到屏幕的overscan区域来对各个区域进行初始化赋值。这个时候各个区域的大小或者等于整个屏幕的大小或者等于不包含overscan区域的大小。

然后计算出导航栏的尺寸,再调整和导航栏有关系的区域大小,这些区域要去掉导航栏所占的区域。

对状态栏也是类似,先计算状态栏的尺寸,在调整和状态栏有关系区域的大小。


PhoneWindowManager的layoutWindowLw函数

    public void layoutWindowLw(WindowState win, WindowState attached) {
        // We've already done the navigation bar and status bar. If the status bar can receive
        // input, we need to layout it again to accomodate for the IME window.
        if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
            return;
        }
        final WindowManager.LayoutParams attrs = win.getAttrs();
        final boolean isDefaultDisplay = win.isDefaultDisplay();
        final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
                (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
        if (needsToOffsetInputMethodTarget) {
            if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
            offsetInputMethodWindowLw(mLastInputMethodWindow);
        }

        final int fl = PolicyControl.getWindowFlags(win, attrs);
        final int sim = attrs.softInputMode;
        final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);

        final Rect pf = mTmpParentFrame;
        final Rect df = mTmpDisplayFrame;
        final Rect of = mTmpOverscanFrame;
        final Rect cf = mTmpContentFrame;
        final Rect vf = mTmpVisibleFrame;
        final Rect dcf = mTmpDecorFrame;
        final Rect sf = mTmpStableFrame;
        Rect osf = null;
        dcf.setEmpty();

        final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
                && mNavigationBar != null && mNavigationBar.isVisibleLw());

        final int adjust = sim & SOFT_INPUT_MASK_ADJUST;

        if (isDefaultDisplay) {
            sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
        } else {
            sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
        }

        if (!isDefaultDisplay) {
            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, adjust, attached, true, pf, df, of, cf, vf);
            } else {
                // Give the window full screen.
                pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
                pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
                pf.right = df.right = of.right = cf.right
                        = mOverscanScreenLeft + mOverscanScreenWidth;
                pf.bottom = df.bottom = of.bottom = cf.bottom
                        = mOverscanScreenTop + mOverscanScreenHeight;
            }
        } else if (attrs.type == TYPE_INPUT_METHOD) {
            pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
            pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
            pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
            // IM dock windows layout below the nav bar...
            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
            // ...with content insets above the nav bar
            cf.bottom = vf.bottom = mStableBottom;
            // IM dock windows always go to the bottom of the screen.
            attrs.gravity = Gravity.BOTTOM;
            mDockLayer = win.getSurfaceLayer();
        }  .....

计算窗口大小是在layoutWindowLw中完成的,该方法不会直接计算出窗口大小,而是先计算出窗口能够扩展的最大空间,然后再调用WindowState的computeFrameLw来计算出窗口的最终大小。
layoutWindowLw方法、主要是计算6个矩形区域的大小,然后根据计算出来的值调用WindowState的computeFrameLw方法来计算窗口大小。
6个区域如下:
pf(parentFrame):表示窗口父窗口的大小
df(deviceFrame):设备的屏幕大小
of(OverScanFrame):设备屏幕大小、和df值一样
cf(ContentFrame):窗口内容区域大小
vf(VisibleFrame):窗口可见内容区域大小
dcf(DecorFrame):装饰区域大小。除去状态栏和导航栏的区域。
这6个区域和上面介绍的屏幕区域有关,但是他们只是一些临时值,为计算窗口的大小服务,因此它们的值并不完全等于上一节介绍的屏幕区域的值。
在layoutWindowLw方法中,如果当前显示设备不是缺省的设备、对于子窗口,则调用setAttachedWindowFrames来处理,如果是非子窗口,将pf、df、of、cf设置为全屏大小。
对于是缺省的设备,如果当前是输入法窗口,pf、df、of大小被设置成不包含状态栏,但是包含导航栏的区域大小,cf、vf设置为不包含状态栏也不包含导航栏的区域大小。窗口属性的gravity设置为Gravity.BOTTOM(底部对齐)。
下面也是根据窗口的类型决定窗口所占的区域是否包含状态栏和导航栏,而且有些窗口还要扩展到OverScan区域。(这里不详细介绍)
得到了窗口的6个区域后就调用computeFrameLw函数来计算窗口的大小了。

PhoneWindowManager的computeFrameLw函数

我们先来看下WindowState描述窗口大小相关的变量,当然我们主要还是要算mFrame变量
    final Rect mFrame = new Rect();//真实窗口大小
    final Rect mLastFrame = new Rect();
    final Rect mCompatFrame = new Rect();//兼容模式下窗口大小

    final Rect mContainingFrame = new Rect();

    final Rect mParentFrame = new Rect();// 父窗口大小
    final Rect mDisplayFrame = new Rect();//显示设备大小

    final Rect mOverscanFrame = new Rect();//Overscan区域大小

    // The display frame minus the stable insets. This value is always constant regardless of if
    // the status bar or navigation bar is visible.
    final Rect mStableFrame = new Rect();

    final Rect mDecorFrame = new Rect();//Decor区域大小

    final Rect mContentFrame = new Rect();//内容区域大小

    final Rect mVisibleFrame = new Rect();//可见区域大小
WindowState类还有一个成员变量mHaveFrame用来描述一个窗口的大小是否计算过了。当WindowState类的成员函数computeFrameLw被调用的时候,就说明一个相应的窗口的大小得到计算了,因此,WindowState类的成员函数computeFrameLw一开始就会将成员变量mHaveFrame的值设置为true。
我们来看下下面一些变量的赋值:
        mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
                Math.max(mContentFrame.top, mFrame.top),
                Math.min(mContentFrame.right, mFrame.right),
                Math.min(mContentFrame.bottom, mFrame.bottom));

        mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
                Math.max(mVisibleFrame.top, mFrame.top),
                Math.min(mVisibleFrame.right, mFrame.right),
                Math.min(mVisibleFrame.bottom, mFrame.bottom));

        mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
                Math.max(mStableFrame.top, mFrame.top),
                Math.min(mStableFrame.right, mFrame.right),
                Math.min(mStableFrame.bottom, mFrame.bottom));

        mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0),
                Math.max(mOverscanFrame.top - mFrame.top, 0),
                Math.max(mFrame.right - mOverscanFrame.right, 0),
                Math.max(mFrame.bottom - mOverscanFrame.bottom, 0));

        mContentInsets.set(mContentFrame.left - mFrame.left,
                mContentFrame.top - mFrame.top,
                mFrame.right - mContentFrame.right,
                mFrame.bottom - mContentFrame.bottom);

        mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
                mVisibleFrame.top - mFrame.top,
                mFrame.right - mVisibleFrame.right,
                mFrame.bottom - mVisibleFrame.bottom);

        mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
                Math.max(mStableFrame.top - mFrame.top, 0),
                Math.max(mFrame.right - mStableFrame.right, 0),
                Math.max(mFrame.bottom - mStableFrame.bottom, 0));

        mCompatFrame.set(mFrame);

PhoneWindowManager的finishLayoutLw

该函数只是一个空实现

    @Override
    public void finishLayoutLw() {
        return;
    }

设置relayoutWindow的输出变量

最后我们会在WMS中的relayoutWindow函数中把一些输出设置到相关输出变量中。
            outFrame.set(win.mCompatFrame);//这个值一般和mFrame相同
            outOverscanInsets.set(win.mOverscanInsets);
            outContentInsets.set(win.mContentInsets);
            outVisibleInsets.set(win.mVisibleInsets);
            outStableInsets.set(win.mStableInsets);
            outOutsets.set(win.mOutsets);




  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值