0%

Android 12 - WMS 层级结构 && DisplayAreaGroup 引入

1. 简介

在 Android 窗口管理中,所有的窗口都是以树形数据结构进行组织管理的,认知这棵 WMS 的树有助于我们理解窗口的管理和显示,同时,WMS 的层级也决定了其在 SurfaceFlinger 的层级结构,这恰恰决定了它的显示规则。

2. WMS 顶层层级构建

在 Android 12 中,所有窗口树形管理都继基于 WindowContainer, 每个 WindowContainer 都有一个父节点和若干个子节点,我们先看看框架中 WindowContainer 都有哪些类型:

2021-11-01-10-50-30

  • RootWindowContainer: 最顶层的管理者,直接管理 DisplayContent
  • DisplayContent: 代表着一个真实或者虚拟的显示设备,在普遍场景中,系统中只存在一个 DisplayContent
  • TaskDisplayArea: 是系统中所有应用任务的父节点,用于管理 Task
  • Task: 代表着一个任务
  • ActivityRecord: 代表一个 Activity 节点
  • WallpaperWindowToken: 代表壁纸节点

在开始之前大概整理了一下系统中各个节点之间的关系:

2021-11-01-11-35-41

从上图可以看到,节点之间的嵌套关系还是比较复杂的( 而且这还是不包括下面章节中提到的引入 Feature 之后的层级关系),层级的最顶端就是 RootWindowContainer, 而它的子节点只能是: DisplayContent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// RootWindowContainer.java
void setWindowManager(WindowManagerService wm) {
...

final Display[] displays = mDisplayManager.getDisplays();
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
// 为每一个 Display 挂载一个 DisplayContent 节点
final DisplayContent displayContent = new DisplayContent(display, this);
addChild(displayContent, POSITION_BOTTOM);
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
mDefaultDisplay = displayContent;
}
}
...
}

再来看看 DisplayContent 的构造方法,核心逻辑就只有一句,依靠 DisplayAreaPolicy 进行层级初始化

1
2
3
4
5
6
7
8
9
10
11
// DisplayContent.java
DisplayContent(Display display, RootWindowContainer root) {
super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
...

// 构造子节点层级,默认策略是使用 DisplayAreaPolicy.DefaultProvider
mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
mWmService, this /* content */, this /* root */, mImeWindowsContainer);

...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// DisplayAreaPolicy.java
static final class DefaultProvider implements DisplayAreaPolicy.Provider {
@Override
public DisplayAreaPolicy instantiate(WindowManagerService wmService,
DisplayContent content, RootDisplayArea root,
DisplayArea.Tokens imeContainer) {
// 创建 TaskDisplayArea 节点,注意,这里是允许创建多个 TaskDisplayArea 并添加的
final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
"DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
final List<TaskDisplayArea> tdaList = new ArrayList<>();
tdaList.add(defaultTaskDisplayArea);

final HierarchyBuilder rootHierarchy = new HierarchyBuilder(root);
rootHierarchy.setImeContainer(imeContainer).setTaskDisplayAreas(tdaList);
if (content.isTrusted()) {
// 配置 Feature 及它所能影响的层级
configureTrustedHierarchyBuilder(rootHierarchy, wmService, content);
}

// 根据配置的 Feature 生成并挂载各个节点,建造层级
return new DisplayAreaPolicyBuilder().setRootHierarchy(rootHierarchy).build(wmService);
}
}

在 Android 12 上,Feature 正式派上用场了,原生添加了以下 Feature:

  • WindowedMagnification: 屏幕放大功能,通過 SystemUI mirrorSurface 该节点实现内容拷贝,详见 WindowMagnificationGestureHandler#toggleMagnification
  • HideDisplayCutout: 隐藏刘海屏功能,开启后,该节点将不会延伸到刘海屏区域(除了状态栏导航栏等窗口,因为不在节点控制范围之内)
  • OneHandedBackgroundPanel: 单手模式下此节点会挂载一个纯色图层(在壁纸图层之下),防止深色模式下分辨不出单手模式
  • OneHanded: 单手模式下相关节点都会做一个向下的位移
  • FullscreenMagnification: 屏幕放大功能,通过无障碍服务 FullScreenMagnificationController.SpecAnimationBridge#setMagnificationSpecLocked 最后调用 DisplayContent#applyMagnificationSpec 方法实现节点放大。不过源码中并不是通过这个 Feature 来实现相关层级放大的,改造得还不彻底
  • ImePlaceholder: 特殊情况下用来放置输入法的节点

我们知道,Android 系统是有 Z 轴概念的,不同的窗口有不同的高度,所有的窗口类型对应到 WMS 都会有一个 layer 值,layer 越大,显示在越上面,WMS 规定 1~36 层级,每一个 Feature 都指定了它所能影响到的 layer 层。这里用颜色对不同 Feature 能影响 layer 图层进行颜色标记:

2021-11-01-14-35-46

标记完之后,就需要根据图表生成窗口层级了,首先对标记好的图表进行上移,上移规则: 如果色块上方是空白的,则可以上移,直至上方是颜色块(不知道大家有没有玩过 2048 这款游戏,上移逻辑是一样的~)

2021-11-01-14-45-24

上移之后,我们得到了最终的图表,接下来用以下规则进行层级构建:

  • 同一行相邻的同色块变成一个 Feature 节点,从左到右根据颜色不断生成节点,同一行所有节点挂在同一个父节点下
  • 父节点就是垂直正上方一行的色块对应的节点
  • 为最末端所有 Feature 节点再添加一个节点,根据子节点代表的 layer 不一样,最后添加的节点也不一样
    • 除了 layer 是 2、15 和 16 外,挂载 DisplayArea.Tokens(这类节点后续只能添加 WindowToken 节点)
    • layer = 2 (也就是 APPLICATION_LAYER)的节点,挂载 TaskDisplayArea
    • layer 等于 15 和 16 的节点,挂载 ImeContainer

通过上述构建规则后,我们可以获得一个树形的层级,并且这棵树有以下特点:

  • 树的最末端节点对应一个 layer 范围,同一个 layer 值只有一个末端节点与之对应
  • 为所有 Feature 都生成了对应的父节点,用以控制其所能影响的 layer

生成了这棵树后,我们会保存两样东西:

  • 所有 layer 值对应的最末端节点,方便我们后续根据窗口类型添加节点
  • Map<Feature, List<DisplayArea>> 形式保存的所有 Feature 节点,方便我们后取出某 Feature 对应的所有节点

现在,虽说我们的 WMS 层级是构建好了,但对于这些 Feature 有何作用还完全没有涉及,这块打算放在 WM Shell 专题里进行说明~~

3. DisplayAreaGroup

通过上面 Feature 的说明可以知道,不同的 Feature 是父子节点的关系,那如果我想划分一个逻辑显示区域,对这块区域配置不同的 Feature 该如何呢? 这时候就可以使用 DisplayAreaGroup 了,框架允许我们添加多个 DisplayAreaGroup, 并为其配置不同的 Feature

就像原生提供的 demo 一样,我们可以创建两个 DisplayAreaGroup 并将屏幕一分为二分别放置这两个,这两个区域都是可以作为应用容器的,和分屏不一样的是,这两块区域可以有不同的 Feature 规则以及其他特性,比如设置不同的 DisplayArea#setIgnoreOrientationRequest

2021-11-01-15-35-28

DisplayAreaGroupDisplayContent 都是 RootDisplayArea 的直接子类,DisplayAreaGroup 可以认为是一个 Display 划分出的多个逻辑 Display 吧。当然,AOSP 虽然引入了这个概念和代码,但其实并未使用,我们只能从测试代码 DualDisplayAreaGroupPolicyTest 中略窥一二了~

4. 小结

WMS 相关的内容体系实在太多,本文也仅仅是分析 WMS 窗口层级最顶层的结构,对于具体的窗口添加移除管理这些尚未涉及,同样,原生新增的 Feature 节点使用也没有涉及(这大部分都被打包进 WM Shell 中去了)

chenhang wechat