WindowManagerService架构剖析之窗口分组与分层

WindowManagerService工作方式

《WindowManagerService架构剖析之addWindow流程》
《WindowManagerService架构剖析之窗口分组与分层》
《WindowManagerService架构剖析之token分析》

一、Window介绍

Window定义了Android中顶层的显示系统和行为规则,Android中的View都是以Window为模板,都是附在WIndow上,从Activity中setContentView(...)中就可以看出Window以及PhoneWindow(Window的子类)就是Android显示层级的最顶层(这里说的最顶层不是显示在最上层,只是说这个Window是整个UI显示的基础)

2.1 Window 类型

每个Window都有type,就是窗口类型,我们在WMS中addWindow的时候会根据当前的窗口类型采取相应的策略。通常而言,窗口分为3类:窗口类型定义在WindowManager.java

2.1.1 系统窗口类型
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
//显示状态栏,被放置在屏幕最上方,其他的window都在它的下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
//searchbar的window,只能有一个。
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
//接电话的界面的window
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
//系统警告类的window,例如一些电量警告等等
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
//屏保窗口
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
//toast窗口
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
        public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
        public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
        public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;
        public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
        public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
        public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
        public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
        public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
        public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
        public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
        public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
        public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
        public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
        public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;
        public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
        public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
        public static final int LAST_SYSTEM_WINDOW      = 2999;

系统窗口类型在2000 ~ 2999,随便找一个系统窗口类型赋值的地方,如下:

BaseErrorDialog.java
class BaseErrorDialog extends AlertDialog {
    public BaseErrorDialog(Context context) {
        super(context, com.android.internal.R.style.Theme_Dialog_AppError);
        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
//......
        getWindow().setAttributes(attrs);
    }
}
2.1.2.应用窗口类型
WindowManager.java
        //作为普通应用程序窗口类型的开始
        public static final int FIRST_APPLICATION_WINDOW = 1;
        //作为所有应用程序的基础窗口
        public static final int TYPE_BASE_APPLICATION   = 1;
        //一个普通的应用程序窗口,当前的token一定要设置成Activity的token,表明当前的window是有归属的
        public static final int TYPE_APPLICATION        = 2;
        //特殊的应用程序窗口,用于程序启动的时候显示,当程序可以显示window之前使用这个window来显示一些东西
        public static final int TYPE_APPLICATION_STARTING = 3;
        //在应用程序显示之前,使用此标识来表明windowManager会等待这个window绘制完毕
        public static final int TYPE_DRAWN_APPLICATION = 4;
        //应用程序窗口类型的结束
        public static final int LAST_APPLICATION_WINDOW = 99;

应用窗口类型在1~99找到应用窗口类型赋值的地方:根据上一篇文章对应用窗口启动过程的分析来看,如下:

ActivityThread.java
final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
//......
        r = performResumeActivity(token, clearHide, reason);
//......
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
//......
        wm.addView(decor, l);
//......
}
2.1.3 子窗口类型
WindowManager.java
        public static final int FIRST_SUB_WINDOW = 1000;
//一个应用程序顶部的panel,显示在依附的window上面
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
//显示media的window,显示在依附的window下面
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
//一个子panel,显示在依附的window上面,并且也显示在任何其他TYPE_APPLICATION_PANEL类型的window上面
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
        public static final int LAST_SUB_WINDOW = 1999;

子窗口类型在1000~1999,通常所说的popupwindow就是子窗口,看一下赋值的例子。

PopupWindow.java
public class PopupWindow {
        private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
        protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
//......
                p.type = mWindowLayoutType;
//......
                return p;
        }
        private void invokePopup(WindowManager.LayoutParams p) {
//......
                  mWindowManager.addView(decorView, p);
//......
    }
}

问题:Dialog是子窗口吗?2.2 Window 分组会分析这个问题。
小结:
1.type越小,越在上面,但是window的层级不单单是这个决定的,下面会详细分析。
2.type只能在这个范围之内,不在此范围,报错。

PhoneWindowManager.java
public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
        int type = attrs.type;
        if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW)
                || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW)
                || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) {
            return WindowManagerGlobal.ADD_INVALID_TYPE;
        }
//......
    }

2.2 Window 分组

学习Window分组之前,建议先看一下《WindowManagerService架构剖析之token分析》https://www.jianshu.com/p/23bce4f5f8ea 这样理解起来容易一些。
这篇文章分析了一个道理:token,AMS-ActivityRecord中appToken,Window中mAppToken,WMS中的windowToken都代表同一个token,这个token就是标识window是否属于同一分组。
为什么这么说?光说不行,我们用源码分析一下就知道了。还是要扯到WMS中的addWindow方法,这个方法太重要了,这些天看这个代码,每次都有新的体会,每次理解都深刻一点。这次我们剔掉不重要的代码,只看关键部分。

WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
//......
      final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
//......
      win.mToken.addWindow(win);
//......
}

WindowState就是windowManager中的窗口,一个WindowState表示一个window,一个WindowState持有一个WindowToken对象(mToken),这个WindowToken对象就是AMS中传入的appToken,但是这个addWindow是什么意思?

WindowToken.java
protected final WindowList mChildren = new WindowList(); //step1:
void addWindow(final WindowState win) {
        if (win.isChildWindow()) {  //step2:
            return;
        }
        if (!mChildren.contains(win)) {  //step3:
            addChild(win, mWindowComparator);
            mService.mWindowsChanged = true;
        }
    }

private final Comparator mWindowComparator =
            (WindowState newWindow, WindowState existingWindow) -> {
        final WindowToken token = WindowToken.this;
        if (newWindow.mToken != token) {
            throw new IllegalArgumentException("newWindow=" + newWindow
                    + " is not a child of token=" + token);
        }

        if (existingWindow.mToken != token) {
            throw new IllegalArgumentException("existingWindow=" + existingWindow
                    + " is not a child of token=" + token);
        }

        return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
    };

1.mChildren列表就是当前window存储的子window列表
2.如果当前创建的window已经是子window,那么表示已经添加过了,不需要重复添加,直接return
3.如果当前window的存储的子window列表中没有child,则addChild
4.WindowToken 继承 WindowContainer
5.比较的原则是什么:比较两个window的mBaseLayer,至于什么是mBaseLayer,2.3 Window层级 会介绍。
6.mWindowComparator里面也能看出来,如果newWindow和existingWindow的token不一样,是不能比较的,直接throw异常了。

WindowContainer.java
protected void addChild(E child, Comparator comparator) {
        if (child.getParent() != null) {
            throw new IllegalArgumentException("addChild: container=" + child.getName()
                    + " is already a child of container=" + child.getParent().getName()
                    + " can't add to container=" + getName());
        }

        int positionToAdd = -1;
        if (comparator != null) {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                if (comparator.compare(child, mChildren.get(i)) < 0) {
                    positionToAdd = i;
                    break;
                }
            }
        }

        if (positionToAdd == -1) {
            mChildren.add(child);
        } else {
            mChildren.add(positionToAdd, child);
        }
        // Set the parent after we've actually added a child in case a subclass depends on this.
        child.setParent(this);
    }

这段代码十分简单,找到当前child在mChildren列表中的位置(排序算法),然后将child插入相应的位置,保证插入child之后mChildren也是按序排列的。
当前window和child window列表都持有同一个token,这个token标识当前window同一分组。

现在可以分析2.1小节提出的问题了,Dialog是子窗口类型吗?


WindowManagerService架构剖析之窗口分组与分层_第1张图片
Dialog-addView.jpg

关键的点有:
1.getAttributes

Window.java
private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();
public final WindowManager.LayoutParams getAttributes() {
        return mWindowAttributes;
    }
WindowManager.java
public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;
            format = PixelFormat.OPAQUE;
        }

有点意思了,当前的Dialog的window type是TYPE_APPLICATION,上面的窗口类型介绍了这是应用程序的窗口类型。说明这个Dialog是属于应用程序窗口的一种。
2.adjustLayoutParamsForSubWindow

Window.java
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
//......
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
//......
        } else {
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
//......
        }
//......
        if (mHardwareAccelerated ||
                (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
            wp.flags |= FLAG_HARDWARE_ACCELERATED;
        }
    }

这个函数别看很长,但是对我们而言,有用的只有几行:
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
说明这个是直接使用mContainer.mAppToken,而这个mContainer,就是Dialog基于的Activity,这个明确了。
小结:
1.Dialog不是子窗口,是应用窗口的一种。
2.Dialog和它基于的Activity公用同一个token,说明它们是属于同一组。

PopupWindow就是名副其实的子窗口类型的,这里暂且不展开讲解,PopupWindow的token获取和Dialog还有不同,大家感兴趣可以去看下。(根据PopupWindow的锚点Window确定)。


WindowManagerService架构剖析之窗口分组与分层_第2张图片
Window分组.jpg

2.3 Window 层级

前面铺垫了这么多,又是分组又是token比较,其实已经讲了一些mBaseLayer相关的知识了,这时候Windiw分层已经呼之欲出了。所谓分层,就是显示的时候有上下次序,上面的window会覆盖下面的window,我们需要知道哪些window应该放在上面,依据是什么?这是本小节探讨的问题。
window之所以要分层级,因为我们看到的手机直观上是二维的,但是实际上是一个三维的显示过程,就像一个三维的坐标系一样。


WindowManagerService架构剖析之窗口分组与分层_第3张图片
Window层级图 (1).jpg

还是要讲WMS-addWindow,这个地方太重要了,添加窗口的时候就应该确定好了窗口的层级,显示的时候才可以根据当前的层级来确定window应该在哪一层显示。
前面我们讨论过WindowState对象对应Window,那么WindowState中肯定有标识当前window层级的变量和计算当前window层级的方法。WMS-addWindow中构造了一个WindowState对象。

WindowState.java
final int mBaseLayer;
final int mSubLayer;
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
           WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
           int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
//......
        mToken = token;
//......
        mWindowId = new WindowId(this);
//......
        if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
//......
            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
            mIsChildWindow = true;
            parentWindow.addChild(this, sWindowSubLayerComparator);
//......
        } else {
//......
            mBaseLayer = mPolicy.getWindowLayerLw(this)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            mSubLayer = 0;
            mIsChildWindow = false;
            mLayoutAttached = false;
//......
        }
//......
    }

这段我们我们可以抽取出下面的信息:
1.WindowState构造的时候初始化当前的mBaseLayer和mSubLayer,这两个参数应该是决定z-order的两个因素,单还有没有其他的因素,暂时不得而知。
2.mBaseLayer在window策略中定义了决定其计算的函数,mSubLayer只有当前window是子窗口的时候才会计算,否则直接初始化0
PhoneWindowManager实现WindowManagerPolicy接口。

WindowManagerPolicy.java
default int getWindowLayerLw(WindowState win) {
        return getWindowLayerFromTypeLw(win.getBaseType(), win.canAddInternalSystemWindow());
    }

default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
        if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
            return APPLICATION_LAYER;
        }

        switch (type) {
            case TYPE_WALLPAPER:
                return  1;
            case TYPE_PRESENTATION:
            case TYPE_PRIVATE_PRESENTATION:
                return  APPLICATION_LAYER;
            case TYPE_DOCK_DIVIDER:
                return  APPLICATION_LAYER;
            case TYPE_QS_DIALOG:
                return  APPLICATION_LAYER;
            case TYPE_PHONE:
                return  3;
    //......
            case TYPE_BOOT_PROGRESS:
                return  32;
            case TYPE_POINTER:
                return  33;
            default:
                return APPLICATION_LAYER;
        }
    }

default int getSubWindowLayerFromTypeLw(int type) {
        switch (type) {
            case TYPE_APPLICATION_PANEL:
            case TYPE_APPLICATION_ATTACHED_DIALOG:
                return APPLICATION_PANEL_SUBLAYER;
            case TYPE_APPLICATION_MEDIA:
                return APPLICATION_MEDIA_SUBLAYER;
            case TYPE_APPLICATION_MEDIA_OVERLAY:
                return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
            case TYPE_APPLICATION_SUB_PANEL:
                return APPLICATION_SUB_PANEL_SUBLAYER;
            case TYPE_APPLICATION_ABOVE_SUB_PANEL:
                return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;
        }
        return 0;
    }

baseLayer这儿实际上就是映射windowType到相应的到1~33区间,这个baseLayer会在后续计算真正window层级的时候用到。可见WindowState构造函数中只是粗略确定当前的baseLayer和subLayer,后面肯定还会根据这两个基础参数计算出最终的层级。
1.mBaseLayer是基础序,当前窗口类型确定的情况下,基础序是不变的。依附与同一个parent 窗口的 子窗口的mBaseLayer是相同的。
2.mSubLayer就是比较相同分组下的子窗口的序,简称子序。从代码上来看,mSubLayer的区间是-2~3
计算的公式是:
3.windowType越大,z-order不一定最大,因为windowType在计算过程中被重新映射了,上面就是映射的过程。

mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
static final int TYPE_LAYER_MULTIPLIER = 10000;
static final int TYPE_LAYER_OFFSET = 1000;

从这个计算公式来讲,只要parentWindow类型不同,那么mBaseLayer决定的大区间肯定是不同的,保证了每个windowType决定的mBaseLayer都会有相当大的缓冲区间,切不会出现不同windowType相等的情况。
接下来还有两个最终的步骤:

WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
//......
      final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
//......
      win.mToken.addWindow(win);
//......
      displayContent.assignWindowLayers(false /* setLayoutNeeded */);
//......
}

1.win.mToken.addWindow(win);
这个函数其实在2.2 Window分组的时候已经详细分析过,这个方法的主要作用就是讲当前的放入有序windowList数组中正确的位置。根据token找到归属的window列表,就是将window分组完成。
2.displayContent.assignWindowLayers(false /* setLayoutNeeded */);
这个方法非常关键,这是计算z-order值的过程,这个方法执行完成之后,z-order最终确定,z-order越大,表明window层级越在上面。下面看看详细的计算过程。


WindowManagerService架构剖析之窗口分组与分层_第4张图片
z-order计算调用过程.jpg

真正的计算过程代码如下:

WindowLayersController.java
private final Consumer mAssignWindowLayersConsumer = w -> {
        boolean layerChanged = false;

        int oldLayer = w.mLayer;
//step1:
        if (w.mBaseLayer == mCurBaseLayer) {
            mCurLayer += WINDOW_LAYER_MULTIPLIER;
        } else {
            mCurBaseLayer = mCurLayer = w.mBaseLayer;
        }
//step2:
        assignAnimLayer(w, mCurLayer);
//step3:
        if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
            layerChanged = true;
            mAnyLayerChanged = true;
        }
//step4:
        if (w.mAppToken != null) {
            mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
                    w.mWinAnimator.mAnimLayer);
        }
        if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
            mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
                    w.mWinAnimator.mAnimLayer);
        }
        if (w.getAppToken() != null && StackId.isResizeableByDockedStack(w.getStackId())) {
            mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
                    w.mWinAnimator.mAnimLayer);
        }
//step5:
        collectSpecialWindows(w);

        if (layerChanged) {
            w.scheduleAnimationIfDimming();
        }
    };

这个是使用了java8推出的函数式编程:java.util.function.Consumer接口。注册的接口在合适的时机accept(...)一下,触发一下要执行的函数,这种写法符合lambda规则,代码看上去比较简洁。
step1:如果当前window和之前的window是同一类型的,为了以示区分,后一个窗口+5,偏移一下,标识区别。如果不是同一类型,那么更新一下mCurBaseLayer和mCurLayer
step2:更新动画层的layer,动画显示的时候,该窗口的层级会有变化。
step3:当前的层级有变化或者动画层级有变化,置位标识值,后面会用到。
step4:同时更新当前应用最高层级、IME最高层级、常驻栏最高层级
step5:如果当前的window是特殊窗口,将他们放入特定的数组中,这儿没有更新layer
1.这儿为什么没有遍历windowList?
整个accept(...)回调就在windowList的遍历中,此回调值计算单个window的最终layer
2.更新之后的layer,会通过setLayer将计算好的z-order值更新到SurfaceFlinger中,进而确定Surface的次序。

接下来准备分析一下窗口大小计算、窗口动画等等。

你可能感兴趣的:(WindowManagerService架构剖析之窗口分组与分层)