Android14 WMS/AMS 窗口层级结构解析

0. 理解图层

(图片来自 www.jianshu.com/p/b0ef7c044...

在很多的图形相关的软件中都有图层的概念,那什么是图层呢?

简单的说,可以将每个图层理解为一张透明的纸,将图像的各部分绘制在不同的透明纸(图层)上。透过这层纸,可以看到纸后面的东西,而且每层纸都是独立的,无论在这层纸上如何涂画,都不会影响到其他图层中的图像。也就是说,每个图层可以独立编辑或修改,最后将透明纸叠加起来,从上向下俯瞰,即可得到并实时改变最终的合成效果。

1. 显示界面的组成与描述

在 Android 中,一个显示界面由多个窗口(Window)组成。

从应用侧看

  • statusbar 占据一个窗口
  • navigationbar 占据一个窗口
  • Activity 占据一个窗口
  • Wallpaper,也就是壁纸占据一个窗口
  • ......

窗口都有一个 Z 轴高度的属性,高的窗口会盖在低的窗口之上,WallPaper 的高度最低,Activity 其次,statusbar 和 navigationbar 最高。

实际的窗口会多一些,我们可以通过 adb shell dumpsys window w 命令来查看窗口信息。

bash 复制代码
Window #0 Window{c5e43 u0 ScreenDecorOverlayBottom}:
# ......
Window #1 Window{bc07d71 u0 ScreenDecorOverlay}:
# ......
Window #2 Window{7162bcd u0 NavigationBar0}:
# ......
Window #3 Window{166a760 u0 NotificationShade}:
# ......
Window #4 Window{59378d5 u0 StatusBar}:
# ......
Window #5 Window{3ade79d u0 ShellDropTarget}:
# ......
Window #6 Window{b3af08a u0 InputMethod}:
# ......
Window #7 Window{70a1ff6 u0 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher}:
# ......
Window #8 Window{3dc41e3 u0 com.android.systemui.wallpapers.ImageWallpaper}:
# ......

从系统侧看

Android11 以后,WMS/AMS 中使用 WindowContainer 对象来描述一块显示区域,使用 WindowContainer 组成的树来描述整个显示界面。为方便叙述,本文称这棵树为窗口容器树

仅从便于理解的角度来看,树的结构大致如下:

(注意,这是根据代码分析分析拼凑出的一张图,不准确,仅用于学习理解)

图片看着有点抽象,接下来我们一步步分析每个节点的类型即可看懂这张图了。

2. 窗口容器树节点分析

窗口容器树的每一个节点都是窗口容器(WindowContainor)的子类。

我们可以通过 adb shell dumpsys activity containers 命令查看整个窗口容器树的描述。(太长了,就不贴出来了)

2.1 WindowContainor ------ 树节点的公共父类

我们先看看 WindowContainor 类的定义:

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
/**
 * 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 中定义了这些节点通用的成员变量和成员方法。

我们需要重点关注的是 WindowContainer 的以下两个成员:

java 复制代码
    /**
     * 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>();
  • mParent 成员变量的类型是 WindowContainer,保存的是当前 WindowContainer 的父节点的引用
  • WindowList 是 ArrayList 的子类, mChildren 成员变量保存的是当前 WindowContainer 持有的所有子节点,列表的顺序同时也是子节点出现在屏幕上的顺序,最顶层的子容器位于队尾

2.2 WindowState ------ 窗口类

在 WMS/AMS 中,一个 WindowState 对象就代表了一个窗口,通常位于树的最底层。可以将屏幕理解为一张画布,WindowState 就是其中的一个图层。

java 复制代码
/** A window in the window manager. */
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
        InsetsControlTarget, InputTarget {
            // ......
}

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 WindowState 的描述:

less 复制代码
# NavigationBar 的描述
#0 7162bcd NavigationBar0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

# StatusBar 的描述
#0 59378d5 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

# Launcher 页面的描述
#0 70a1ff6 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

# ......

2.3 WindowToken、ActivityRecord 和 WallpaperWindowToken ------ WindowState 的父节点

WindowState 的父节点有三类 WindowToken、ActivityRecord 和 WallpaperWindowToken。

WindowToken

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
/**
 * 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> {
    // ......
}

WindowToken 是 WMS 中用于保存一组相关窗口(WindowState)的容器类。

在 WMS/AMS 中,WindowToken 继承自 WindowContainer<WindowState>, 是 WindowState 的容器,其父类内部成员 WindowList<E> mChildren 保存了 WindowToken 的 WindowState 子节点们。

我们称 WindowToken 是 WindowState 的父容器或者父节点。

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 WindowToken 的描述:

less 复制代码
       #0 WindowToken{77ed98c type=2000 android.os.BinderProxy@2667cde} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
        #0 59378d5 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
        
        # ......

ActivityRecord

java 复制代码
/**
 * An entry in the history task, representing an activity.
 */
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    // ......
}

在 WMS/AMS 中一个 ActivityRecord 对象用于描述 App 端的一个 Activity 对象。同时 ActivityRecord 继承自 WindowToken,和 WindowToken 一样, ActivityRecord 是 WindowState 的容器,其内部成员 WindowList<E> mChildren 保存了 WindowToken 的 WindowState 子节点们。

ActivityRecord 和 WindowToken 的子节点都是 WindowState,那它们的区别是什么呢?

在了解两者的区别之前,我们先了解一下窗口的类型。根据窗口的添加方式可以将窗口分为 Activity 窗口和非 Activity 窗口:

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

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 ActivityRecord 的描述:

less 复制代码
          #0 ActivityRecord{8076c53 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t7} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
           #0 70a1ff6 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

WallpaperWindowToken

java 复制代码
/**
 * A token that represents a set of wallpaper windows.
 */
class WallpaperWindowToken extends WindowToken {
    // ......
}

WallpaperWindowToke 继承自 WindowToken,是 Wallpaper 相关的窗口的父节点。

上面讨论 ActivityRecord 的时候,我们将窗口划分为 Activity 窗口和非 Activity 窗口。在引入了 WallpaperWindowToken 后,我们继续将非 Activity 窗口划分为两类,Wallpaper 窗口和非 Wallpaper 窗口。Wallpaper 窗口的父节点都是 WallpaperWindowToken 类型的。

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

Activity 之上的窗口,父节点为 WindowToken,如 StatusBar 和 NavigationBar。 Activity 窗口,父节点为 ActivityRecord,如 Launcher。 Activity 之下的窗口,父节点为 WallpaperWindowToken,如 ImageWallpaper 窗口。

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 WallpaperWindowToken 的描述:

less 复制代码
        #0 WallpaperWindowToken{b8d832d token=android.os.Binder@43bb644} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
         #0 3dc41e3 com.android.systemui.wallpapers.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

2.4 Task ------ ActivityRecord 的父节点

java 复制代码
class Task extends TaskFragment {

}

class TaskFragment extends WindowContainer<WindowContainer> {

}

一个 Task 对象就代表了一个任务栈,内部保存了一组相同 affinities 属性的相关 Activity,这些 Activity 用于执行一个特定的功能。比如发送短信,拍摄照片等。

关于任务栈的内容可以参考官方文档任务和返回堆栈

Taks 继承自 TaskFragment,TaskFragment 继承自 WindowContainer。除了上述的功能,在我们描述的窗口容器树中, Task 是 ActivityRecord 的父节点,内部管理有多个 ActivityRecord 对象。

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 Task 的描述:

less 复制代码
         #0 Task=7 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
          #0 ActivityRecord{8076c53 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t7} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
           #0 70a1ff6 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

2.5 DisplayArea ------ 一块显示区域

java 复制代码
/**
 * 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> {
    // ......
    private final String mName;
    // ......
}

DisplayArea 代表了屏幕上的一块区域。

DisplayArea 中有一个字符串成员 mName,表示 DisplayArea 对象的名字,其内容由三部分组成 name + ":" + mMinLayer + ":" + mMaxLayer。其中:

  • name:用于指定 DisplayArea 的特殊功能,如:name 的值为 "WindowedMagnification" 表示 DisplayArea 代表的屏幕区域支持窗口放大。如果没有特殊功能且是叶子节点,name 的值为 "leaf"
  • mMinLayer 和 mMaxLayer,指定当前 DisplayArea 的图层高度范围,WMS 将 Z 轴上的纵向空间分成了 0 到 36 一共 37 个区间,值越大代表图层高度越高,这里两个值,指定了图层高度的范围区间。

实际上我们可以把屏幕显示的内容看做一个三维空间:

注意这里的 x y z 轴的位置,不同于一般的三维图。

这里的每一个矩形就是一个窗口(WindowState),我们可以将其理解为一个图层。

我们将 Z 轴分为 0 到 36, 一共 37 个连续的区间。

DisplayArea 指定了一块显示区域,这里的显示区域包含了 x y z 三个方向上的区域。Z 轴的区域通过内部 mName 成员中保存的两个整形变量 mMinLayer 和 mMaxLayer 指定。比如 mMinLayer = 1, mMaxLayer =3,那么当前 DisplayArea 对象在 Z 轴方向上占据了 1 到 3,3 个区间的纵向空间。

DisplayArea 有三个子类 TaskDisplayArea,DisplayArea.Tokens 和 DisplayArea.Dimmable。DisplayArea.Tokens 有一个子类 DisplayContent.ImeContainer。DisplayArea.Dimmable 有一个子类 RootDisplayArea

更清晰的继承关系可以参考以下的类图:

接下来,我们来看看 DisplayArea 的每一个子类:

TaskDisplayArea

java 复制代码
/**
 * {@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,继承自 DisplayArea,代表了屏幕上一块专门用来存放 App 窗口的区域。它的子容器通常是 Task。

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 TaskDisplayArea 的描述:

less 复制代码
       #1 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
        #1 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
         #0 Task=7 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
          #0 ActivityRecord{8076c53 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t7} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
           #0 70a1ff6 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

DisplayArea.Tokens

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

    }

Tokens 是 DisplayArea 的内部类,继承自 DisplayArea,代表了屏幕上一块专门用来存放非 App 窗口的区域。它的子容器通常是 WindowToken。

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 DisplayArea.Tokens 的描述:

less 复制代码
      #0 Leaf:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
       #0 WindowToken{77ed98c type=2000 android.os.BinderProxy@2667cde} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
        #0 59378d5 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

这里的 Leaf 的类型就是 DisplayArea.Tokens

DisplayContent.ImeContainer

java 复制代码
    /**
     * 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 {
    }

ImeContainer 是 DisplayContent 类的内部类,继承自 DisplayArea.Tokens,是存放输入法窗口的容器。

我们可以从 adb shell dumpsys window w 命令的输出结构中找到 ImeContainer 的描述:

less 复制代码
       #0 ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
        #0 WindowToken{82b005b type=2011 android.os.Binder@a85536a} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]
         #0 b3af08a InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1280]

DisplayArea.Dimmable

java 复制代码
    /**
     * DisplayArea that can be dimmed.
     */
    static class Dimmable extends DisplayArea<DisplayArea> {
        // ......
    }

Dimmable 也是 DisplayArea 的内部类,从名字可以看出,这类的 DisplayArea 可以添加模糊效果。

它内部有一个Dimmer对象:

java 复制代码
private final Dimmer mDimmer = new Dimmer(this);

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

RootDisplayArea

java 复制代码
/**
 * 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。

DisplayContent

java 复制代码
/**
 * 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 层级结构的根节点。

DisplayAreaGroup

java 复制代码
/** The root of a partition of the logical display. */
class DisplayAreaGroup extends RootDisplayArea {
}

DisplayAreaGroup,屏幕上的部分区域对应的 DisplayArea 层级结构的根节点。什么意思呢?

(图片来自 blog.csdn.net/shensky711/...

我们可以创建两个 DisplayAreaGroup 并将屏幕一分为二分别放置这两个,这两个区域都是可以作为应用容器的,和分屏不一样的是,这两块区域可以有不同的 Feature 规则以及其他特性。这个用得比较少,了解一下即可

3. 总结

在了解了这些类以后,在看文章开头给出的窗口层级图,就容易不少了:

参考资料

相关推荐
Crossoads2 小时前
【汇编语言】外中断(一)—— 外中断的魔法:PC机键盘如何触发计算机响应
android·开发语言·数据库·深度学习·机器学习·计算机外设·汇编语言
sunphp开发者3 小时前
黑客攻击网站,篡改首页问题排查修复
android·js
我又来搬代码了3 小时前
【Android Studio】创建新项目遇到的一些问题
android·ide·android studio
ggs_and_ddu7 小时前
Android--java实现手机亮度控制
android·java·智能手机
zhangphil13 小时前
Android绘图Path基于LinearGradient线性动画渐变,Kotlin(2)
android·kotlin
watl013 小时前
【Android】unzip aar删除冲突classes再zip
android·linux·运维
键盘上的蚂蚁-13 小时前
PHP爬虫类的并发与多线程处理技巧
android
喜欢猪猪14 小时前
Java技术专家视角解读:SQL优化与批处理在大数据处理中的应用及原理
android·python·adb
JasonYin~16 小时前
HarmonyOS NEXT 实战之元服务:静态案例效果---手机查看电量
android·华为·harmonyos
zhangphil16 小时前
Android adb查看某个进程的总线程数
android·adb