Android 12的新特性解析:深入探讨DisplayArea层级架构(第三部分) - 内容概要与总结
4 总结
4.1 DisplayArea类型
4.1.1 根据类的继承关系分类
一种是从DisplayArea类的继承关系出发,有以下关系:
4.1.2 根据DisplayArea.Type类的定义分类
根据DisplayArea.Type类的定义:
enum Type {
/** Can only contain WindowTokens above the APPLICATION_LAYER. */
ABOVE_TASKS,
/** Can only contain WindowTokens below the APPLICATION_LAYER. */
BELOW_TASKS,
/** Can contain anything. */
ANY;
// ......
}
可以将DisplayArea分为三类,分类的依据当前DisplayArea和APPLICATION_LAYER的关系:
if (mMinLayer > APPLICATION_LAYER) {
type = DisplayArea.Type.ABOVE_TASKS;
} else if (mMaxLayer < APPLICATION_LAYER) {
type = DisplayArea.Type.BELOW_TASKS;
} else {
type = DisplayArea.Type.ANY;
}
这种分类,首先把窗口划分为了App窗口和非App窗口。
其次,对于非App窗口,再根据其层级与App窗口层级的高低关系,分为位于App窗口之上的非App窗口(即ABOVE_TASKS),和位于App窗口之下的非App窗口(即BELOW_TASKS)。
4.1.3 根据Feature分类
在上面分析DisplayArea层级结构的创建流程中,我们在DisplayAreaPolicy.DefaultProvider.configureTrustedHierarchyBuilder方法中看到了6种Feature的添加:
private void configureTrustedHierarchyBuilder(HierarchyBuilder rootHierarchy,
WindowManagerService wmService, DisplayContent content) {
// WindowedMagnification should be on the top so that there is only one surface
// to be magnified.
rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "WindowedMagnification",
FEATURE_WINDOWED_MAGNIFICATION)
.upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
.except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
// Make the DA dimmable so that the magnify window also mirrors the dim layer.
.setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
.build());
if (content.isDefaultDisplay) {
// Only default display can have cutout.
// See LocalDisplayAdapter.LocalDisplayDevice#getDisplayDeviceInfoLocked.
rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "HideDisplayCutout",
FEATURE_HIDE_DISPLAY_CUTOUT)
.all()
.except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
TYPE_NOTIFICATION_SHADE)
.build())
.addFeature(new Feature.Builder(wmService.mPolicy,
"OneHandedBackgroundPanel",
FEATURE_ONE_HANDED_BACKGROUND_PANEL)
.upTo(TYPE_WALLPAPER)
.build())
.addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
FEATURE_ONE_HANDED)
.all()
.except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)
.build());
}
rootHierarchy
.addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification",
FEATURE_FULLSCREEN_MAGNIFICATION)
.all()
.except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD,
TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY,
TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)
.build())
.addFeature(new Feature.Builder(wmService.mPolicy, "ImePlaceholder",
FEATURE_IME_PLACEHOLDER)
.and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
.build());
}
}
这些Feature都对应一个特定的的DisplayArea。
但是再看DisplayArea的层级结构图:
还有一些节点的对应Feature却没有看到,即根节点DisplayContent,叶节点TaskDisplayArea和叶节点Leaf对应的DisplayArea.Tokens。
但是每次创建DisplayArea的时候都会传入一个对应的FeatureId的,之前分析的时候可能没有注意:
1)、DisplayContent:
DisplayContent(Display display, RootWindowContainer root) {
super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
// ......
}
2)、TaskDisplayArea:
final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
"DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
3)、DisplayArea.Tokens:
Tokens(WindowManagerService wms, Type type, String name) {
super(wms, type, name, FEATURE_WINDOW_TOKENS);
}
这样,所有的FeatureID,都已经找到了使用的地方了:
/**
* The value in display area indicating that no value has been set.
*/
public static final int FEATURE_UNDEFINED = -1;
/**
* The Root display area on a display
*/
public static final int FEATURE_SYSTEM_FIRST = 0;
/**
* The Root display area on a display
*/
public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST;
/**
* Display area hosting the default task container.
*/
public static final int FEATURE_DEFAULT_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
/**
* Display area hosting non-activity window tokens.
*/
public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2;
/**
* Display area for one handed feature
*/
public static final int FEATURE_ONE_HANDED = FEATURE_SYSTEM_FIRST + 3;
/**
* Display area that can be magnified in
* {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW}. It contains all windows
* below {@link WindowManager.LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY}.
*/
public static final int FEATURE_WINDOWED_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 4;
/**
* Display area that can be magnified in
* {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN}. This is different from
* {@link #FEATURE_WINDOWED_MAGNIFICATION} that the whole display will be magnified.
* @hide
*/
public static final int FEATURE_FULLSCREEN_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 5;
/**
* Display area for hiding display cutout feature
* @hide
*/
public static final int FEATURE_HIDE_DISPLAY_CUTOUT = FEATURE_SYSTEM_FIRST + 6;
/**
* Display area that the IME container can be placed in. Should be enabled on every root
* hierarchy if IME container may be reparented to that hierarchy when the IME target changed.
* @hide
*/
public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7;
/**
* Display area for one handed background layer, which preventing when user
* turning the Dark theme on, they can not clearly identify the screen has entered
* one handed mode.
* @hide
*/
public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8;
/**
* The last boundary of display area for system features
*/
public static final int FEATURE_SYSTEM_LAST = 10_000;
看一下这些Feature的含义都是什么:
-
FEATURE_ROOT,一个屏幕上的根DisplayArea,对应DisplayContent。
-
FEATURE_DEFAULT_TASK_CONTAINER,容纳默认Task容器的DisplayArea,对应TaskDisplayArea。
-
FEATURE_WINDOW_TOKENS,容纳非activity窗口的DisplayArea,对应DisplayArea.Tokens。
-
FEATURE_ONE_HANDED,用于单手模式的DisplayArea,对应名为“OneHanded”的DisplayArea。
-
FEATURE_WINDOWED_MAGNIFICATION,在ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW模式下可以对窗口的某些区域进行放大的DisplayArea,对应名为“WindowedMagnification”的DisplayArea。
-
FEATURE_FULLSCREEN_MAGNIFICATION,在ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN模式下可以对整个屏幕进行放大的DisplayArea,对应名为“FullscreenMagnification”的DisplayArea。
-
FEATURE_HIDE_DISPLAY_CUTOUT,隐藏DisplayCutout的DisplayArea,对应名为“HideDisplayCutout”的DisplayArea。
-
FEATURE_IME_PLACEHOLDER,存放输入法窗口的DisplayArea,对应名为“ImePlaceholder”的DisplayArea。
-
FEATURE_ONE_HANDED_BACKGROUND_PANEL,容纳单手背景图层的DisplayArea,避免用户开启暗黑模式后,无法分辨屏幕是否已经进入了单手模式,对应名为“OneHandedBackgroundPanel”的DisplayArea。
4.2 DisplayArea层级结构生成规则
这里想要讨论一下为什么DisplayArea层级结构呈现为这样的形式:
在2.3节中分析了生成DisplayArea树的流程,但是感觉不够直观,这里借鉴了:
blog.****.net/shensky711/…
的分析方式,用颜色对各个DisplayArea进行标记。
分析前我们需要知道几点前提:
- 属于同一层级值的窗口,统一由一个Leaf管理,这个Leaf可以是DisplayArea.Tokens,也可以是TaskDisplayArea或者ImeContainer,这里暂且认为一个Leaf代表的就是同一类型的窗口。
- 为DisplayArea定义的各种Feature,代表了这个DisplayArea属下的窗口所具有的特征。Leaf虽然本身拥有的Feature,如FEATURE_WINDOW_TOKENS,没有对应的一个具体的功能,但是Leaf又是被层级结构中的父节点所管理的,所以它也会拥有父节点DisplayArea对应的Feature代表的特征。比如一个Leaf的父节点是WindowedMagnification,那么这个Leaf管理的所有窗口都具有窗口放大功能。
- 另外虽然一个DisplayArea只有一个Feature,但是由于DisplayArea的互相嵌套,那么一个Leaf可能会处于多级DisplayArea之下,所以一个Leaf可能具备多个Feature,比如Leaf:33:35,它的父节点从下往上依次是FullscreenMagnification,OneHanded,HideDisplayCutout,那么这个Leaf下的所有窗口,都具备这些Feature带来的特征。
以此为基础,来分析一下DisplayArea层级结构的生成过程。
1)、由于定义的层级值是0~36,所以最初我们可能为每一个层级值都创建了一个Leaf对象。如果想要某一个Leaf拥有某一个Feature代表的特征,那么就为这个Leaf添加对应的父节点,那么最初的设计可能是这样的:
因为有37个Leaf,所以总共有37列。这里的每一个有颜色的格子都代表一个DisplayArea对象。空白格子说明该列的Leaf管理的窗口不希望有该Feature代表的功能,因此没有针对该Feature创建一个DisplayArea对象。
所以这里有37棵独立的DisplayArea树,每一个树的根节点都是一个DisplayContent对象,叶节点都是一个Leaf,然后中间节点则是有多有少,这取决于这棵树的leaf希望拥有哪些Feature。
2)、对于每一棵DisplayArea树,都是父节点连接子节点,中间不能有空白节点。但是上面的表格,我们能看到是有格子是空白的。我们这里是希望表格同样能够反映DisplayArea层级结构,所以我们需要去掉空白格子。
举个例子,看一下36列,该列下的Leaf不需要任何额外Feature,因此不需要再为该Leaf创建任何父DisplayArea,直接将该Leaf添加到DisplayContent的子节点数组中,在表格中就是将该Leaf对应的格子上移,直接移动到DisplayContent所代表的格子之下。
这也就意味着每一个有颜色的格子如果上方有空白格子,那么就将其上移,最终得到:
3)、现在每一棵树都是父节点连接子节点,且中间没有空白节点了,但是此时并不够成一个层级结构,而仍然是37棵独立的树,需要进一步优化。首先我们看到,每一个屏幕只对应一个DisplayContent对象,那么这37棵树,它们的根节点其实都是同一个DisplayContent。此外,对于表格中左右相邻的DisplayArea,如果它们的父DisplayArea是同类型的(拥有的Feature相同),那么这种情况下,就可以复用父DisplayArea,即没有必要创建多个DisplayArea,而是只创建一个父DisplayArea,然后将这些左右相邻的DisplayArea全部添加到该父DisplayArea的子节点数组之中,也能达到同样的效果,即这些DisplayArea都具有了父DisplayArea的Feature。此时,这些表格中左右相邻的DisplayArea,由同一个父节点管理,因此表格上看它们左右相邻,在实际的层级结构中,它们也是处于同一层级。
那么表格中如果一个相邻格子的颜色相同,就把这两个格子合并,即DisplayArea的复用。最终得到:
再对比之前的树状图:
也是符合的。
4.3 向DisplayArea层级结构添加窗口
根据DisplyArea树状图可知,对于0~36的每一个层级值,在DisplayArea层级结构中都有相应的Leaf对应。因此每次添加新窗口的时候,只需要将该窗口的窗口类型换算为相应的层级值,然后将该新窗口添加到该层级值对应的Leaf下即可。
层级值反映了一个Leaf在DisplayArea层级结构中的层级高低,层级值越大,该Leaf在DisplayArea层级结构中的层级也越高。而Leaf是窗口的容器,Leaf层级值越大,其管理的窗口在Z轴的顺序也就越高。这也说明了窗口类型值越大的窗口,其在Z轴上的顺序不一定越高,因为窗口类型值和层级值并不是一个正相关的关系。