2【Android 12】WindowContainer类

在这里插入图片描述

根据之前的分析,知道了窗口在App端是以PhoneWindow的形式存在,承载了一个Activity的View层级结构。那么在WMS端,窗口是以一种什么样的形式接受着WMS的管理?我想从窗口这一点发散出去,探究WMS下与它相关的角色成员,然后把握WMS体系下的窗口组织形式。

1 窗口 —— WindowState类

/** A window in the window manager. */
public class WindowState extends WindowContainer<WindowState> implements
    WindowManagerPolicy.WindowState, InsetsControlTarget {

在WMS窗口体系中,一个WindowState对象就代表了一个窗口。

这里不去细看WindowState内部是用哪些成员变量和成员方法来描述一个窗口的属性、状态之类的,而是把WindowState看成一个WMS窗口体系下的最小单位,用宏观的角度去把握WMS是如何去管理这许多个窗口的。

这里看到,WindowState继承自WindowContainer。

2 窗口容器类 —— WindowContainer类

/**
 * Defines common functionality for classes that can hold windows directly or through their
 * children in a hierarchy form.
 * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
 * changes are made to this class.
 */
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable {

WindowContainer定义了能够直接或者间接以层级结构的形式持有窗口的类的通用功能。

从类的定义和名称,可以看到WindowContainer是一个容器类,可以容纳WindowContainer及其子类对象。如果另外一个容器类作为WindowState的容器,那么这个容器类需要继承WindowContainer或其子类。

继续看下,WindowContainer为了能够作为一个容器类,提供了哪些支持:

    /**
     * The parent of this window container.
     * For removing or setting new parent {@link #setParent} should be used, because it also
     * performs configuration updates based on new parent's settings.
     */
    private WindowContainer<WindowContainer> mParent = null;

	// ......

    // List of children for this window container. List is in z-order as the children appear on
    // screen with the top-most window container at the tail of the list.
    protected final WindowList<E> mChildren = new WindowList<E>();

1)、首先是一个同样为WindowContainer类型的mParent成员变量,保存的是当前WindowContainer的父容器的引用。

2)、其次是WindowList类型的mChildren成员变量,保存的则是当前WindowContainer持有的所有子容器。并且列表的顺序也就是子容器出现在屏幕上的顺序,最顶层的子容器位于队尾。

有了这两个成员变量,便为生成WindowContainer层级结构,WindowContainer树形结构提供了可能。

在这里插入图片描述

此时回看WindowState的定义:

public class WindowState extends WindowContainer<WindowState>

有了新的发现,即WindowState本身也是一个容器,不过这里指明了WindowState作为容器类可以持有的容器类型限定为WindowState类型,即WindowState可以作为其他WindowState的父容器。

事实上也的确如此,比如典型的PopupWindow,在PopupWindow弹出后查看PopupWindow的窗口信息,可以看到:

          #0 6b8748a com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
           #0 3c3dfc1 PopupWindow:a760a0c type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

在这里插入图片描述

查看PopupWindow的信息:

  Window #6 Window{193e28f u0 PopupWindow:b98815a}:
    // ......
    mParentWindow=Window{bfc77c9 u0 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity} mLayoutAttached=true

PopupWindow是一个子窗口,且其父窗口为:

Window{bfc77c9 u0 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity}

3 WindowState的容器 —— WindowToken、ActivityRecord和WallpaperWindowToken

WindowContainer是能够直接或者间接持有WindowState的容器类的通用基类,间接持有WindowState的容器类暂且不提,肯定很多,那么可以直接持有WindowState的类有哪些呢?

答案是WindowToken和ActivityRecord。

3.1 WindowToken类

/**
 * Container of a set of related windows in the window manager. Often this is an AppWindowToken,
 * which is the handle for an Activity that it uses to display windows. For nested windows, there is
 * a WindowToken created for the parent window to manage its children.
 */
class WindowToken extends WindowContainer<WindowState> {

WMS中存放一组相关联的窗口的容器。通常是ActivityRecord,它是Activity在WMS的表示,就如WindowState是Window在WMS的表示,用来显示窗口。

比如StatusBar:

       #0 WindowToken{ef15750 android.os.BinderProxy@4562c02} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 d3cbd49 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

在这里插入图片描述

3.2 ActivityRecord类

/**
 * An entry in the history task, representing an activity.
 */
public final class ActivityRecord extends WindowToken implements
    WindowManagerService.AppFreezeListener {

ActivityRecord是WindowToken的子类,如上面所说,在WMS中一个ActivityRecord对象就代表一个Activity对象。

这里有几点需要说明:

1)、可以根据窗口的添加方式将窗口分为App窗口和非App窗口,或者换个说法,Activity窗口和非Activity窗口,我个人习惯叫App窗口和非App窗口。

  • App窗口,这类窗口由系统自动创建,不需要App主动去调用ViewManager.addView去添加一个窗口,比如我写一个Activity或者Dialog,系统就会在合适的时机为Activity或者Dialog调用ViewManager.addView去向WindowManager添加一个窗口。这类窗口在创建的时候,其父容器为ActivityRecord。
  • 非App窗口,这类窗口需要App主动去调用ViewManager.addView来添加一个窗口,比如NavigationBar窗口的添加,需要SystemUI主动去调用ViewManager.addView来为NavigationBar创建一个新的窗口。这类窗口在创建的时候,其父容器为WindowToken。

2)、WindowToken既然是WindowState的直接父容器,那么每次添加窗口的时候,就需要创建一个WindowToken,或者一个ActivityRecord。

3)、上述情况也有例外,即存在WindowToken的复用,因为一个WindowToken中可能不只一个WindowState对象,说明第二个WindowState创建的时候,复用了第一个WindowState创建的时候生成的WindowToken。如果两个WindowState能被放进一个WindowToken中,那么这两个WindowState之间就必须有联系。那这联系是什么,查看WMS添加窗口的流程,在WMS.addWindow方法的部分片段:

            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);

可知,如果两个窗口的WindowManager.LayoutParams.token指向同一个对象,那么这两个WindowState就会被放入同一个WindowToken中。

更细节的逻辑我这边没有跟过,但是通常来说,只有由同一个Activity的两个窗口才有可能被放入到一个WindowToken中,比如典型的Launcher:

          #0 ActivityRecord{31ce9e6 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t191}
           #1 220afda com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity
           #0 4cf47c3 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity

在这里插入图片描述

一个ActivityRecord下有两个WindowState,且这两个窗口是同一层级的。

此外分别来自两个不同的Activity的两个窗口是不会被放入同一个WindowToken中的,因此更一般的情况是,一个WindowToken或ActivityRecord只包含一个WindowState。

3.3 WallpaperWindowToken

/**
 * A token that represents a set of wallpaper windows.
 */
class WallpaperWindowToken extends WindowToken {

其实WindowToken还有一个子类,WallpaperWindowToken,这类的WindowToken用来存放和Wallpaper相关的窗口。

         #0 WallpaperWindowToken{dc5772f token=android.os.Binder@985f410} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 1ea91d com.android.systemui.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

在这里插入图片描述

上面讨论ActivityRecord的时候,我们将窗口划分为App窗口和非App窗口。在引入了WallpaperWindowToken后,我们继续将非App窗口划分为两类,Wallpaper窗口和非Wallpaper窗口。

Wallpaper窗口的层级是比App窗口的层级低的,因此这里我们可以按照层级这一角度将窗口划分为:

  • App之上的窗口,父容器为WindowToken,如StatusBar和NavigationBar。
  • App窗口,父容器为ActivityRecord,如Launcher。
  • App之下的窗口,父容器为WallpaperWindowToken,如ImageWallpaper窗口。

4 WindowToken的容器 —— DisplayArea.Tokens

可以容纳WindowToken的容器,搜索之后,发现为定义在DisplayArea中的内部类Tokens:

    /**
     * DisplayArea that contains WindowTokens, and orders them according to their type.
     */
    public static class Tokens extends DisplayArea<WindowToken> {

如果要说明Tokens,就需要解释Tokens的父类DisplayArea,涉及DisplayArea的内容统一放在第7节中。

5 ActivityRecord的容器 —— Task

class Task extends WindowContainer<WindowContainer> {

Task,存放ActivityRecord的容器,再次温习一下google关于Task的说明:

任务(Task)是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。例如,电子邮件应用可能有一个 Activity 来显示新邮件列表。当用户选择一封邮件时,系统会打开一个新的 Activity 来显示该邮件。这个新的 Activity 会添加到返回堆栈中。如果用户按返回按钮,这个新的 Activity 即会完成并从堆栈中退出。

大多数Task都从Launcher上启动。当用户轻触Launcher中的图标(或主屏幕上的快捷方式)时,该应用的Task就会转到前台运行。如果该应用没有Task存在(应用最近没有使用过),则会创建一个新的Task,并且该应用的“主”Activity 将会作为堆栈的根 Activity 打开。

在当前 Activity 启动另一个 Activity 时,新的 Activity 将被推送到堆栈顶部并获得焦点。上一个 Activity 仍保留在堆栈中,但会停止。当 Activity 停止时,系统会保留其界面的当前状态。当用户按返回按 钮时,当前 Activity 会从堆栈顶部退出(该 Activity 销毁),上一个 Activity 会恢复(界面会恢复到上一个状态)。堆栈中的 Activity 永远不会重新排列,只会被送入和退出,在当前 Activity 启动时被送入堆栈,在用户使用返回按钮离开时从堆栈中退出。因此,返回堆栈按照“后进先出”的对象结构运作。图 1 借助一个时间轴直观地显示了这种行为。该时间轴显示了 Activity 之间的进展以及每个时间点的当前返回堆栈。

在这里插入图片描述

图 1. 有关Task中的每个新 Activity 如何添加到返回堆栈的图示。当用户按返回按钮时,当前 Activity 会销毁,上一个 Activity 将恢复。

如果用户继续按返回,则堆栈中的 Activity 会逐个退出,以显示前一个 Activity,直到用户返回到主屏幕(或Task开始时运行的 Activity)。移除堆栈中的所有 Activity 后,该Task将不复存在。

WMS中的Task类的作用和以上说明基本一致,用来管理ActivityRecord。

我在Launcher上启动Message,此时情况是:

        #6 Task=67 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
         #0 ActivityRecord{cb9b141 u0 com.google.android.apps.messaging/.ui.ConversationListActivity t67} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 d01e570 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

在这里插入图片描述

接着再启动Message的另外一个界面:

        #6 Task=67 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
         #1 ActivityRecord{77ab155 u0 com.google.android.apps.messaging/.conversation.screen.ConversationActivity t67} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 e50283c com.google.android.apps.messaging/com.google.android.apps.messaging.conversation.screen.ConversationActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
         #0 ActivityRecord{cb9b141 u0 com.google.android.apps.messaging/.ui.ConversationListActivity t67} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 d01e570 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

在这里插入图片描述

另外Task是支持嵌套的,从Task类的定义也可以看出来,Task的嵌套多用于多窗口模式,如分屏:

        #2 Task=5 type=standard mode=split-screen-primary override-mode=split-screen-primary requested-bounds=[0,0][720,770] bounds=[0,0][720,770]
         #0 Task=67 type=standard mode=split-screen-primary override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,770]
          #0 ActivityRecord{cb9b141 u0 com.google.android.apps.messaging/.ui.ConversationListActivity t67} type=standard mode=split-screen-primary override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,770]
           #0 d01e570 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=split-screen-primary override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,770]

在这里插入图片描述

以及部分非ACTIVITY_TYPE_STANDARD类型的ActivityRecord,如ACTIVITY_TYPE_HOME类型,即Launcher应用对应的ActivityRecord:

        #5 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 Task=191 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
          #0 ActivityRecord{31ce9e6 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t191} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
           #1 220afda com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
           #0 4cf47c3 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

在这里插入图片描述

但是对于同一个Task来说,它不能既包含Task,又包含ActivityRecord。

6 Task的容器 —— TaskDisplayArea

/**
 * {@link DisplayArea} that represents a section of a screen that contains app window containers.
 *
 * The children can be either {@link Task} or {@link TaskDisplayArea}.
 */
final class TaskDisplayArea extends DisplayArea<WindowContainer> {

TaskDisplayArea,代表了屏幕上一块专门用来存放App窗口的区域。

它的子容器可能是Task或者是TaskDisplayArea。

又涉及到了DisplayArea,那么接下来,就该介绍DisplayArea的相关内容。

7 DisplayArea类的继承关系

/**
 * Container for grouping WindowContainer below DisplayContent.
 *
 * DisplayAreas are managed by a {@link DisplayAreaPolicy}, and can override configurations and
 * can be leashed.
 *
 * DisplayAreas can contain nested DisplayAreas.
 *
 * DisplayAreas come in three flavors, to ensure that windows have the right Z-Order:
 * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks.
 * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks.
 * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
 *
 * @param <T> type of the children of the DisplayArea.
 */
public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {

DisplayArea,是DisplayContent之下的对WindowContainer进行分组的容器。

DisplayArea受DisplayAreaPolicy管理,而且能够复写Configuration和被绑定到leash上。

DisplayArea可以包含嵌套DisplayArea。

DisplayArea有三种风格,用来保证窗口能够拥有正确的Z轴顺序:

  • BELOW_TASKS,只能包含Task之下的的DisplayArea和WindowToken。
  • ABOVE_TASKS,只能包含Task之上的DisplayArea和WindowToken。
  • ANY,能包含任何种类的DisplayArea、WindowToken或是Task容器。

这里和我们之前分析WindowToken的时候逻辑是一样的,DisplayArea的子类有一个专门用来存放Task的容器类,TaskDisplayArea。层级高于TaskDisplayArea的DisplayArea,会被归类为ABOVE_TASKS,层级低于TaskDisplayArea则属于ABOVE_TASKS。

DisplayArea有三个直接子类,TaskDisplayArea,DisplayArea.Tokens和DisplayArea.Tokens。

7.1 TaskDisplayArea

/**
 * {@link DisplayArea} that represents a section of a screen that contains app window containers.
 *
 * The children can be either {@link Task} or {@link TaskDisplayArea}.
 */
final class TaskDisplayArea extends DisplayArea<WindowContainer> {

TaskDisplayArea代表了屏幕上的一个包含App类型的WindowContainer的区域。它的子节点可以是Task,或者是TaskDisplayArea。但是目前在代码中,我看到创建TaskDisplayArea的地方只有一处,该处创建了一个名为“DefaultTaskDisplayArea”的TaskDisplayArea对象,除此之外并没有发现其他地方有创建TaskDisplayArea对象的地方,自然也没有找到有关TaskDisplayArea嵌套的痕迹。

因此目前可以说,TaskDisplayArea存放Task的容器。

在手机的近期任务列表界面将所有App都清掉后,查看一下此时的TaskDisplayArea情况:

       #0 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #5 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 Task=191 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
          #0 ActivityRecord{31ce9e6 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t191} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
           #1 220afda com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
           #0 4cf47c3 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #4 Task=4 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #3 Task=3 type=undefined mode=split-screen-secondary override-mode=split-screen-secondary requested-bounds=[0,1498][1440,2960] bounds=[0,1498][1440,2960]
        #2 Task=2 type=undefined mode=split-screen-primary override-mode=split-screen-primary requested-bounds=[0,0][1440,1464] bounds=[0,0][1440,1464]
        #1 Task=5 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 Task=6 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

在这里插入图片描述

预期应该是DefaultTaskDisplayArea的子容器,只有Launcher应用对应的Task#1,但是却多Task#4、Task#3、Task#2、Task#5和Task#6这几个Task,并且这几个Task并没有持有任何一个ActivityRecord对象。正常来说,App端调用startActivity后,WMS会为新创建的ActivityRecord对象创建Task,用来存放这个ActivityRecord。当Task中的所有ActivityRecord都被移除后,这个Task就会被移除,就如Task#1,或者Task#191。

但是对于Task#4之类的Task来说并不是这样。这些Task是WMS启动后就由TaskOrganizerController创建的,这些Task并没有和某一具体的App联系起来,因此当它里面的子WindowContainer被移除后,这个Task也不会被销毁。比如分屏Task#5和Task#6:

        #3 Task=3 type=undefined mode=split-screen-secondary override-mode=split-screen-secondary requested-bounds=[0,1498][1440,2960] bounds=[0,1498][1440,2960]
        #2 Task=2 type=undefined mode=split-screen-primary override-mode=split-screen-primary requested-bounds=[0,0][1440,1464] bounds=[0,0][1440,1464]

这两个Task由TaskOrganizerController启动,用来管理系统进入分屏后,需要跟随系统进入分屏模式的那些App对应的Task,也就是说这些App对应的Task的父容器会从DefaultTaskDisplayArea变为Task#3和Task#2,例子就如第5节分析的分屏下Task的嵌套。

7.2 DisplayArea.Tokens

    /**
     * DisplayArea that contains WindowTokens, and orders them according to their type.
     */
    public static class Tokens extends DisplayArea<WindowToken> {

Tokens是DisplayArea的内部类,从其定义即可看出,它是一个只能包含WindowToken对象的DisplayArea类型的容器,那么其内部层级结构就相对简单,比如StatusBar:

      #0 Leaf:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 WindowToken{ef15750 android.os.BinderProxy@4562c02} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 d3cbd49 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

在这里插入图片描述

这里的Leaf:17:17是一个DisplayArea.Tokens对象的名字,之后分析DisplayArea层级结构的时候我们会知道它的由来。

他有一个子类DisplayContent.ImeContainer。

7.2.1 DisplayContent.ImeContainer

    /**
     * Container for IME windows.
     *
     * This has some special behaviors:
     * - layers assignment is ignored except if setNeedsLayer() has been called before (and no
     *   layer has been assigned since), to facilitate assigning the layer from the IME target, or
     *   fall back if there is no target.
     * - the container doesn't always participate in window traversal, according to
     *   {@link #skipImeWindowsDuringTraversal()}
     */
    private static class ImeContainer extends DisplayArea.Tokens {

DisplayContent的内部类,ImeContainer,是存放输入法窗口的容器。它继承的是DisplayArea.Tokens,说明它是一个只能存放WindowToken容器。

       #0 ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
        #0 WindowToken{5e01794 android.os.Binder@579cfe7} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
         #0 4435738 InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

在这里插入图片描述

7.3 DisplayArea.Dimmable

    /**
     * DisplayArea that can be dimmed.
     */
    static class Dimmable extends DisplayArea<DisplayArea> {

Dimmable也是DisplayArea的内部类,从名字可以看出,这类的DisplayArea可以添加模糊效果,并且Dimmable也是一个DisplayArea类型的DisplayArea容器。

它内部有一个Dimmer对象:

private final Dimmer mDimmer = new Dimmer(this);

可以通过Dimmer对象施加模糊效果,模糊图层可以插入到以该Dimmable对象为根节点的层级结构之下的任意两个图层之间。

它有一个直接子类,RootDisplayArea。

7.3.1 RootDisplayArea

/**
 * Root of a {@link DisplayArea} hierarchy. It can be either the {@link DisplayContent} as the root
 * of the whole logical display, or a {@link DisplayAreaGroup} as the root of a partition of the
 * logical display.
 */
class RootDisplayArea extends DisplayArea.Dimmable {

RootDisplayArea,是一个DisplayArea层级结构的根节点。

它可以是:

  • DisplayContent,作为整个屏幕的DisplayArea层级结构根节点。
  • DisplayAreaGroup,作为屏幕上部分区域对应的DisplayArea层级结构的根节点。

这又引申出了RootDisplayArea的两个子类,DisplayContent和DisplayAreaGroup。

7.3.1.1 DisplayContent
/**
 * Utility class for keeping track of the WindowStates and other pertinent contents of a
 * particular Display.
 */
@TctAccess(scope = TctScope.PUBLIC)
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {

DisplayContent,代表了一个实际的屏幕,那么它作为一个屏幕上的DisplayArea层级结构的根节点也没有毛病。

隶属于同一个DisplayContent的窗口将会被显示在同一个屏幕中。每一个DisplayContent都对应着唯一ID,比如默认屏幕是:

#1 Display 0 name="Built-in Screen" type=undefined mode=fullscreen override-mode=fullscreen

其他虚拟屏幕可能是:

#0 Display 4 name="Overlay #1" type=undefined mode=fullscreen override-mode=fullscreen

ID是递增的。

那么以DisplayContent为根节点的DisplayArea层级结构可能是:

在这里插入图片描述

7.3.1.2 DisplayAreaGroup
/** The root of a partition of the logical display. */
class DisplayAreaGroup extends RootDisplayArea {

DisplayAreaGroup,屏幕上的部分区域对应的DisplayArea层级结构的根节点,但是Android 12目前没有看到这个类在哪里使用了,因此不过多介绍。

8 RootWindowContainer

/** Root {@link WindowContainer} for the device. */
class RootWindowContainer extends WindowContainer<DisplayContent>
        implements DisplayManager.DisplayListener {

从名字也可以看出,这个类代表当前设备的根WindowContainer,就像View层级结构中的DecorView一样,它是DisplayContent的容器。由于DisplayContent代表了一个屏幕,且RootWindowContainer能够作为DisplayContent的父容器,这也说明了Android是支持多屏幕的,展开来说就是包括一个内部屏幕(内置于手机或平板电脑中的屏幕)、一个外部屏幕(如通过 HDMI 连接的电视)以及一个或多个虚拟屏幕。

如果我们在开发者选项里通过“Simulate secondary displays”开启另一个虚拟屏幕,此时的情况是:

ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
  #1 Display 0 name="Built-in screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,1612] bounds=[0,0][720,1612]
  #0 Display 2 name="Overlay #1" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,480] bounds=[0,0][720,480]

在这里插入图片描述

“Root”即RootWindowContainer,“Display 0”代表默认屏幕,“Display 2”是我们开启的虚拟屏幕。

9 总结

在代码搜索WindowContainer的继承关系,可以得到:

在这里插入图片描述

除了不参与WindowContainer层级结构的WindowingLayerContainer之外,其他类都在本文有提及,用类图总结为:

在这里插入图片描述

然后,将我们上面分析各个WindowContainer类时提供的说明图进行关联,可以得到:

在这里插入图片描述

得到了一个简略版的WindowContainer层级结构图,这个并不真实反映手机的情况,因为这是按照我们上面的分析拼凑出的一张图,但是可以作为参考。唯一和真实情况有出入的地方在于和DisplayArea相关的部分,DisplayArea本身也是有一个层级结构的,以后我们在分析DisplayArea层级结构的时候会了解。

除了WindowState可以显示图像以外,大部分的WindowContainer,如WindowToken、TaskDisplayArea是不会有内容显示的,都只是一个抽象的容器概念。极端点说,WMS如果只为了管理窗口,WMS也可以不创建这些个WindowContainer类,直接用一个类似列表的东西,将屏幕上显示的窗口全部添加到这个列表中,通过这一个列表来对所有的窗口进行管理。但是为了更有逻辑地管理屏幕上显示的窗口,还是需要创建各种各样的窗口容器类,即WindowContainer及其子类,来对WindowState进行分类,从而对窗口进行系统化的管理。

这样带来的好处也是显而易见的,如:

1)、这些WindowContainer类都有着鲜明的上下级关系,一般不能越级处理,比如DefaultTaskDisplayArea只用来管理调度Task,Task用来管理调度ActivityRecord,而DefaultTaskDisplayArea不能直接越过Task去调度Task中的ActivityRecord。这样TaskDisplayArea只需要关注它的子容器们,即Task的管理,ActivityRecord相关的事务让Task去操心就好,每一级与每一级之间的边界都很清晰,不会在管理逻辑上出现混乱,比如DefaultTaskDisplayArea强行去调整一个ActivityRecord的位置,导致这个ActivityRecord跳出它所在的Task,变成和Task一个层级。

2)、保证了每一个WiindowContainer不会越界,这个重要,比如我点击HOME键回到Launcher,此时DefaultTaskDisplayArea就会把Launcher对应的Task#1,移动到它内部栈的栈顶,而这仅限于DefaultTaskDisplayArea内部的调整,这一点保证了Launcher的窗口将永远不可能高于StatusBar窗口,也不会低于Wallpaper窗口。

  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值