窗口层级基础知识
打开应用屏幕显示:
抽象出来的结构是:
左边是将第一张截图的每一个窗口提出来画了的模拟图, 想象一下每个窗口其实都是全屏的,那么他的前后顺序若右图(注意颜色是对应的)。 如果说是按右边的这种层级排序,那么音量键的窗口和状态栏的窗口就会挡住Activity的窗口。
在安卓中窗口是有先后顺序的,越靠近用户的就越靠前,就能挡住底下的窗口。 目前说这么一个结论可能为时过早,后面的内容将详细介绍。
三维坐标体系
我们玩的王者荣耀,原神等游戏的开发,都是类似在这么一个3D场景下进行的,开发者将使用3D建模工具来创建地形、建筑、植被等地图元素,并通过材质和贴图来增强地图的视觉效果。
比如这张图片里放了3个颜色的柱子。 除了这些物体外,可以看到还有一个摄像机(Camera),它是用来捕捉画面的, 毕竟手机屏幕是2D的,简单来说这个摄像机能捕捉到的画面就是我们手机屏幕上显示的内容,比如这张图片捕捉到的画面是右下角的内容。像我们玩游戏移动角色,其实就是通过转动这个摄像机(Camera)来完成的。
Android坐标系其实就是一个三维坐标,Z轴向上,X轴向右,Y轴向下。
经常听到的 Z-Order 也就是指窗口在Z轴的排序,离用户越近,Z值越大。并且能够遮挡住后面的窗口。
为了再加深一下安卓窗口的层级关系,下面看一张应用开发的View层级的3D视角。
在布局了写了37层约束布局,其实第3层加了一个按钮。 右上角我们手机屏幕上只是一个按钮的界面,但是右下角通过Android Studio自带的Layout Inspector工具可以发现,整个View树是有很多层的(代码写了37层)。
View Tree
XML里如图写下2个简单的红绿布局,根布局为约束布局A,下属约束布局B(红色)和约束布局C(绿色),显示的UI效果绿色的会挡住一部分红色的。但是用Layout Inspector工具其实可以发现红色控件其实也是完成绘制的。
也就是说在同一层级下,在ViewGropu孩子View数组这个集合中,下标越大的View会挡住后面的View。也可以理解为层级越靠前,就会覆盖挡住后面的View。
继续增加几个View,约束布局B下增加控件D和E,控件D下新增文本控件G。
约束布局C下面新增文本控件F。
Layout Inspector工具左边可以看到对View进行了层级划分,这个就是ViewTree:
这个就是View层的View树,能构建出View树的原因是因为在代码中定义了ViewGroup这个类,下边的代码可以看到,每个View都有定义自己的父类容器,而ViewGroup继承自View所以它也有自己的父类容器,此外它拥有一个view数组成员,表示它的孩子子,这就是代码层面构建View的基础。
# View
// 父容器
protected ViewParent mParent;
# ViewGroup
// 所有子View
private View[] mChildren;
public void addView(View child, int index) {
......
// 内部实现是通过addInArray 将View添加到数组mChildren中
addView(child, index, params);
}
@Override
public void removeView(View view) {
......// 本质还是从mChildren移除
}
在View体系中,五大布局都有共同的父类ViewGroup。
ViewGroup继承了View,所以有父亲(mParent),自身内部又维护了一个View数组(mChildren)表示它的孩子们。 上有父亲下有一群孩子,所以能构建出一个ViewTree。
在窗口这一级别的开发中,也有窗口树及其对应的容器结构。
Window Tree
- WindowContainer:
类似ViewGroup的存在,是窗口容器的基类,后面介绍的其他窗口容器类都是它的子类。
有泛型限制,说明容器的内容是有限制的
mParent: 保存当前容器的父窗口引用。
mChildren :保存当前窗口的所有孩子窗口容器集合(有泛型)。根据注释,列表后面的子容器,z-order 越大,离屏幕越近。
父类为ConfigurationContainer,封装了配置的处理,当前类封装了容器的操作。 - RootWindowContainer:根窗口容器,也是窗口层级树的根。管理DisplayContent。
- DisplayContent:代表一块屏幕,Android是支持多屏幕的。继承关系为:DisplayContent->RootDisplayArea->DisplayArea.Dimmable->DisplayArea->WindowContainer
孩子为DisplayArea(这个规则定义在DisplayArea的子类Dimmable中) - DisplayArea: 注释:DisplayContent下的窗口容器集合,表示一块显示区域,是DisplayContent的子容器。DisplayContent下安卓目前设计为分了37层,每一层都是一个DisplayArea。
- DisplayArea.Dimmable:DisplayArea的子类,并且是其内部类,DisplayContent的父类,带模糊效果。孩子是DisplayArea。
- DisplayArea.Token:DisplayArea的子类,并且是其内部类,表示WindowToken的容器。WindowToken的子类是WindowState
- WindowState:代表一个窗口,本身也是一个容器,比如子窗口(Popupwindow场景)。
- WindowToken:一组相关窗口的容器,是窗口的Token,是窗口分类的依据,而窗口的定义WindowState。一般在窗口层级树中WindowToken下面就会挂载一个WindowState。壁纸用到的WallpaperWindowToken也是其子类。
- ImeContainer:输入法专用的容器,父类是DisplayArea.Token,那么孩子也是WindowToken。
- ActivityRecord:对应着一个Activity。是WindowToken的子类,所以孩子也是WindowState。 从应用开发角度,一个Activity下也有一个Window。并且一般作为是Task的孩子。
Feature
AOSP既然将屏幕分了37层,那说明图层之间是有区别的,有不一样的特性,这个就是Feature,比如这一层是不是支持单手操作。 这里列举5个常见的Feature,已经它们所在的层级。
- WindowedMagnification:拥有特征的层级0-31,特征描述:支持窗口缩放的一块区域,一般是通过辅助服务进行缩小或放大
- HideDisplayCutout:拥有特征的层级: 0-14 16 18-23 26-35,特征描述:隐藏剪切区域,即在默认显示设备上隐藏不规则形状的屏幕区域,比如在代码中打开这个功能后,有这个功能的图层就不会延伸到刘海屏区域。。
- OneHanded:拥有特征的层级:0-23 26-32 34-35,特征描述:表示支持单手操作的图层,这个功能在手机上还是挺常见的。
- FullscreenMagnification:拥有特征的层级:0-12 15-23 26-27 29-31 33-35 特征描述:支持全屏幕缩放的图层,和上面的不同,这个是全屏缩放,前面那个可以局部。
- ImePlaceholder:拥有特征的层级: 13-14 特征描述:输入法相关。
WMS层级结构树
可以通过以下命令来看获取到设备当前的层级结构树:
adb shell dumpsys activity containers
在开完机后的launcher就执行了dump命令,查看dump日志,可以获取层级树的输出。
ACTIVITY MANAGER CONTAINERS (dumpsys activity containers)
ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Display 0 name="Built-in Screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,1600] bounds=[0,0][720,1600]
#2 Leaf:36:36 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 WindowToken{451b2bd type=2024 android.os.BinderProxy@4526826} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 47e1803 ScreenDecorOverlayBottom type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WindowToken{69b9325 type=2024 android.os.BinderProxy@3a8ab1c} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 799b2ab ScreenDecorOverlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 HideDisplayCutout:32:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#2 OneHanded:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 FullscreenMagnification:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 FullscreenMagnification:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 OneHanded:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WindowedMagnification:0:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#6 HideDisplayCutout:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 OneHanded:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#2 FullscreenMagnification:29:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:29:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 Leaf:28:28 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 FullscreenMagnification:26:27 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:26:27 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#5 Leaf:24:25 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 WindowToken{922c2bc type=2024 android.os.BinderProxy@d50168e} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 48a6245 pip-dismiss-overlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WindowToken{1a3a19a type=2019 android.os.BinderProxy@1ec36bc} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 50a3d66 NavigationBar0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#4 HideDisplayCutout:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 OneHanded:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 FullscreenMagnification:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#3 OneHanded:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 FullscreenMagnification:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WindowToken{7472fe2 type=2040 android.os.BinderProxy@1bfb9c4} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 4b26f73 NotificationShade type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#2 HideDisplayCutout:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 OneHanded:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 FullscreenMagnification:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 OneHanded:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 FullscreenMagnification:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WindowToken{3da7d5c type=2000 android.os.BinderProxy@e2c682e} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 7619865 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 HideDisplayCutout:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 OneHanded:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 ImePlaceholder:13:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WindowToken{1397896 type=2011 android.os.Binder@23bebb1} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 d0c3c51 InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 FullscreenMagnification:0:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#2 Leaf:3:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WindowToken{82fa61a type=2038 android.os.BinderProxy@2f86adc} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 33a873c ShellDropTarget type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#2 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Task=7 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 ActivityRecord{bd2b1d4 u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t7} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 ae1df9b 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,1600]
#0 b9fa2f0 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,1600]
#1 Task=2 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Task=3 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#1 Task=6 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Task=5 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Leaf:0:1 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 WallpaperWindowToken{4b4c99a token=android.os.Binder@9258e45} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 f3495ce com.android.systemui.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
- 1、开完机层级结构树就存在
- 2、界面上有相关的Window的操作,都会在层级结构树上体现
- 3、不同的window会被挂在到对应的位置,这个其实就是层级结构树的关键。 当然哪个Window应该挂在到那一层,怎么个先后顺序,这个我们其实无需过于在意,这个是产品设计。
现在来分析上面那一团输出信息怎么看。(从上到下,从左往右)
ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
#0 Display 0 name="Built-in Screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,1600] bounds=[0,0][720,1600]
上面的ROOT 表示根节点,暂时可以忽略。下面的 #0 Display 0 name="Built-in Screen"表示当前的手机的第0个屏幕,目前也可忽略,主要是下面那一部分内容。
下面的内容虽然很长,但是我们不要看全部,抓住几个点就够了,以前面这段为例
#2 Leaf:36:36 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1600]
- 1、看所在位置#x:每一行最前面都是 “#+数字”的形式打头,比如现在看的是 “#2 Leaf:36:36”这里的数字表示这个图层在当前父容器的位置,从0开始。其实和ViewGrop或许childView的一样的。
当前这个为#2 所以他的父容器一共有3个子容器,当前这个处于第三个,也就是最上面。那么和他同级的另外2个怎么找呢?
需要往下看,找到和当前 #2 前面空格一样多的#1 和#0 就是他同级的2个容器了,按照规则能能找下面这2个与他同级的。
需要注意这里说的同级并不是在同一图层,而是在层级树这个树的结构是同级关系。
#1 HideDisplayCutout:32:35
#0 WindowedMagnification:0:31
-
- 层级name 名字+起始层级:结束层级
指的是 “Leaf:36:36” 这一段信息,这个的格式为“容器名 起始层级:结束层级”
体现在当前就是这个 #2 的容器叫 “Leaf”,表示一个叶子节点,比较特殊,主要看后面的频繁出现的HideDisplayCutout,ImePlaceholder,OneHanded等这些,都有具体的意义,我们知道所有的东西在源码中都能找到对应的代码, 像提到的HideDisplayCutout,ImePlaceholder,OneHanded在源码中称之为Feature(特征),即表示当前这个容器有具有这个特征,暂时知道就可以,后面会详细介绍源码对这些Feature的具体定义。
然后就是后面的起始层级:结束层级,因为虽然一共是分为37层,但是并不是说有37个Feature,比如“#1 ImePlaceholder:13:14 ” ImePlaceholder看着就是和输入法相关,那就代表着13,14都是和输入法相关的window。
android 13目前一共也只有5个Feature。
另外提一下这里的 “Leaf”代表的不是Feature,而且说当前是某个叶子节点,下面是要挂着具体Window的。
- 层级name 名字+起始层级:结束层级
-
3、看其他属性,比如type,mode:知道上面这4点基本上就能看到层级结构树的信息了,内容虽然很多,但是我们其实主要关心的还是下面“#1 DefaultTaskDisplayArea”的部分,因为这里放的才是应用相关的窗口,其他的一般都是系统窗口。像应用操作,分屏,小窗,自由窗口操作导致层级改变都体现在这一层,
另外可以留意一下如果是 WindowToken +WindowState的都是系统窗口,比如下面这种形式:
#0 WindowToken{1397896
#0 d0c3c51 InputMethod
d0c3c51 这个应该是WindowState的对象名,后面的InputMethod是具体的窗口名。
而 ActivityRecord+WindowState就是应用窗口类型了比如:
#0 ActivityRecord{91c971c
#0 9c20028 com.google.android.dialer
窗口层级可视化
单看层级树可能过于枯燥,在刚开始学习的时候一般都会根据信息画出一个层级结构图。 首先根据前面看dump内容的方式,先画出一部分内容如下:
这里是DisplayContent下的3个孩子,可以看到已经覆盖了 0-36层。 然后按照这种方式将所有的内容都画出来就可以得到下面这完整的树图:
android版本相同,画出来的图基本上都是一样的,只会根据出现不同的Window在响应的Leaf,也就是叶子节点会有不同。
这里将叶子节点的颜色涂上了,发现叶子节点下要么为空,要么就是 WindowToken,除了最底层的壁纸,其实壁纸叶子节点下的WallpaperWindowToken这个类,也是继承的WindowToken。
可能之前通过文本的形式还有点陌生,但是转换成图片后,一些常见的东西就都清楚了,比如launcher,StatusBar,NavigationBar,Wallpaper
窗口树和View树还是有差距的,View树上都是View,而窗口树上只有叶子节点上挂着窗口,其他大都都是一些容器。
窗口树是固定37层的, 然后各个图层都有自己支持的Feature这些都是代码中固定好的。 实际开发中,开发中再将自己的窗口根据需求挂到对应的叶子节点上,这个和View树是有区别的。
窗口层级树设计的目的
1、方便管理 在写应用的时候也会这样定义,这样如果说某个业务的View无论怎么写,他就只能在自己所在的“层级”上显示,不会影响到其他。各个类型的窗口在自己的层级上,不会影响到其他。
2、方便功能开发,这种设计为小窗,分屏这种功能提供了开发的便利,因为只需要将对应的窗口移动到所在的Task就可以了。像现在的Activity启动,在system_service进程的处理的很多逻辑都是围绕着这个层级树来做的。 比如看一眼“电话”和“短信”2个应用进行分屏操作的前后对比:
通过对比可以发现
Task=5,6 这2个Task是分屏用到的Task,它们有共同的父亲-- Task =4。 这3个Task 在开机的时候就创建好了,默认在DefaultTaskDisplayArea孩子里是最后面。
启动分屏后其实对应窗口容器这边做了2件事
- 将Task=4 移到栈顶
- 将2个分屏应用的Task分别移到到Task5,6 下
这样分屏就完成了。
窗口层级树构建流程
dump内容数据定义
# WindowManagerService
RootWindowContainer mRoot;
private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
......
// containers
} else if ("containers".equals(cmd)) {
synchronized (mGlobalLock) {
mRoot.dumpChildrenNames(pw, " ");
pw.println(" ");
mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
}
return;
} else if ("trace".equals(cmd)) {
......
}
// 打印的name
@Override
String getName() {
return "ROOT";
}
dumpChildrenNames的实现在WindowContainer的父类ConfigurationContainer中
# ConfigurationContainer
public void dumpChildrenNames(PrintWriter pw, String prefix) {
final String childPrefix = prefix + " ";
pw.println(getName()
+ " type=" + activityTypeToString(getActivityType())
+ " mode=" + windowingModeToString(getWindowingMode())
+ " override-mode=" + windowingModeToString(getRequestedOverrideWindowingMode())
+ " requested-bounds=" + getRequestedOverrideBounds().toShortString()
+ " bounds=" + getBounds().toShortString());
for (int i = getChildCount() - 1; i >= 0; --i) {
final E cc = getChildAt(i);
// 打印 # 加角标
pw.print(childPrefix + "#" + i + " ");
cc.dumpChildrenNames(pw, childPrefix);
}
}
可以看到从RootWindowContainer开始递归打印。 这也就是dump到的窗口容器层级树的内容。比如最开始的RootWindowContainer::getName返回的内容就是 “ROOT”。
层级树构建
调用链
调用链前面这段可以知道,在系统启动的时候就触发了这段逻辑,这也就是为什么刚进入launcher就可以dump出整个结构树的原因。
setWindowManager 方法传递的参数是WMS, WMS的启动是tartOtherServices中,而RootWindowContainer则是WMS的一个成员变量,RootWindowContainer是层级树中的跟容器,在WMS构建函数中创建。
# WindowManagerService
// The root of the device window hierarchy.
RootWindowContainer mRoot;
// WMS构造函数
private WindowManagerService(......) {
......
mRoot = new RootWindowContainer(this);
......
}
继续看构建流程:
# RootWindowContainer
void setWindowManager(WindowManagerService wm) {
mWindowManager = wm;
mDisplayManager = mService.mContext.getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(this, mService.mUiHandler);
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
final Display[] displays = mDisplayManager.getDisplays();
// 遍历每个屏幕
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
// 重点*1:为每一个 Display 挂载一个 DisplayContent 节点
final DisplayContent displayContent = new DisplayContent(display, this);
addChild(displayContent, POSITION_BOTTOM);
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
mDefaultDisplay = displayContent;
}
}
// 重点*2 TaskDisplayArea相关
final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
false /* includingParents */);
}
1、这段代码也就能看出,为什么说一个DisplayContent就代表着1个屏幕了,每个display都挂载了一个displaycontent。
2、处理TaskDisplayArea相关(这里窗口层级树已经构建完成了)
上篇看层级树知道TaskDisplayArea就是放应用相关容器的,目前先不看这块,先跟踪DisplayContent下的逻辑, 现在需要看DisplayContent的构造方法,因为里面开始构造这个屏幕下层级树。(不考虑多屏幕的情况)
DisplayContent开始构造当前屏幕的层级树
# DisplayContent
// 2个参数为Display和跟容器
DisplayContent(Display display, RootWindowContainer root) {
......
// 创建事务
final Transaction pendingTransaction = getPendingTransaction();
// 重点开始构建层级树
configureSurfaces(pendingTransaction);
// 执行事务
pendingTransaction.apply();
......
}
private void configureSurfaces(Transaction transaction) {
// 构建一个SurfaceControl
final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
.setOpaque(true)
.setContainerLayer()
.setCallsite("DisplayContent");
// 设置名字后构建 (Display 0 name="XXX")
mSurfaceControl = b.setName(getName()).setContainerLayer().build();
// 重点* 设置策略并构建显示区域层次结构
if (mDisplayAreaPolicy == null) {
// WMS的getDisplayAreaPolicyProvider方法按返回 DisplayAreaPolicy.Provider
// 然后其 instantiate的实现 目前只有DisplayAreaPolicy的内部类DefaultProvider
mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
mWmService, this /* content */, this /* root */,
mImeWindowsContainer);
}
// 事务相关设置
transaction
.setLayer(mSurfaceControl, 0)
.setLayerStack(mSurfaceControl, mDisplayId)
.show(mSurfaceControl)
.setLayer(mOverlayLayer, Integer.MAX_VALUE)
.show(mOverlayLayer);
}
这一块我们目前可以忽略transaction的代码只需要关心中间“getDisplayAreaPolicyProvider”这一块就好了,这段是层级树的主流程。
tips:
1、方法最开始的 setName设置name在层级树是能找到对应名字的
2、注意instantiate倒数第3第4都个参数传递的都是this,也就是
DisplayContent, 因为 DisplayContent这是的父类是RootDisplayArea。
# DisplayContent
String getName() {
return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
}
接下来继续看主流程:
# DisplayAreaPolicy.Provider
@Override
public DisplayAreaPolicy instantiate(WindowManagerService wmService,
DisplayContent content, RootDisplayArea root,
DisplayArea.Tokens imeContainer) {
// 重点*1. 创建一个名为 "DefaultTaskDisplayArea" 的对象作为应用窗口的默认容器(第三个参数Feature为FEATURE_DEFAULT_TASK_CONTAINER)
final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
"DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
final List<TaskDisplayArea> tdaList = new ArrayList<>();
// 实际上只有1个元素
tdaList.add(defaultTaskDisplayArea);
// Define the features that will be supported under the root of the whole logical
// display. The policy will build the DisplayArea hierarchy based on this.
// 传递RootDisplayArea(DisplayContent)构建出一个层级树的数据结构
final HierarchyBuilder rootHierarchy = new HierarchyBuilder(root);
// Set the essential containers (even if the display doesn't support IME).
// 设置输入法容器
rootHierarchy.setImeContainer(imeContainer).setTaskDisplayAreas(tdaList);
// 这个条件满足,肯定是被信任的
if (content.isTrusted()) {
// 重点* 2 配置层级的支持的Feature
configureTrustedHierarchyBuilder(rootHierarchy, wmService, content);
}
// Instantiate the policy with the hierarchy defined above. This will create and attach
// all the necessary DisplayAreas to the root.
// 重点* 3 真正开始构建层级树
return new DisplayAreaPolicyBuilder().setRootHierarchy(rootHierarchy).build(wmService);
}
这个方法是在DisplayContent构造函数掉进来的,注意最后2个参数,root表示跟容器,imeContainer则是输入法容器,在DisplayContent中传过来的,然后被通过setImeContainer设置给了HierarchyBuilder。
重点分析:
1、“DefaultTaskDisplayArea” 终于出现了, 可以看到确实是TaskDisplayArea对象,然后FEATURE_DEFAULT_TASK_CONTAINER这个ID的值就是1, 那也就是在第二层,和层级树是对应的,然后构建了一个List,但是这个集合就这一个元素。
2、配置层级的支持的Feature
3、开始真正的构建
配置层级支持的Feature
发现层级树中一共就出现了5个Feature就是在当前方法中配置的,分别如下:
- WindowedMagnification:支持窗口缩放
- HideDisplayCutout:多余裁剪
- OneHanded :单手
- FullscreenMagnification:全屏显示
- ImePlaceholder:输入法
# DisplayAreaPolicy.Provider
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, "OneHanded",
FEATURE_ONE_HANDED)
.all()
.except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
TYPE_SECURE_SYSTEM_OVERLAY)
.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());
}
这里执行了5次addFeature,每次对应一个Feature刚好是5个。Feature.Builder构造一个Feature对象,代码如下:
# DisplayAreaPolicy.Feature.Builder
Builder(WindowManagerPolicy policy, String name, int id) {
mPolicy = policy;
mName = name;
mId = id;
mLayers = new boolean[mPolicy.getMaxWindowLayer() + 1];
}
注意后面2个参数,第二个为name,就是名字,后面的是ID,根据使用的地方肯定是定义了对应ID的。
留意下mLayers,mPolicy.getMaxWindowLayer()返回36所以是定义了一个长度为37的boolean类型数组,如果为ture表示这个图层支持这个Feature,为false反之。
5个Feature对应的ID如下,并且有相应的注释:
# DisplayAreaOrganizer
/**
* Display area that can be magnified in
* ......
*/
public static final int FEATURE_WINDOWED_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 4;
/**
* Display area for hiding display cutout feature
* @hide
*/
public static final int FEATURE_HIDE_DISPLAY_CUTOUT = FEATURE_SYSTEM_FIRST + 6;
/**
* Display area for one handed feature
*/
public static final int FEATURE_ONE_HANDED = FEATURE_SYSTEM_FIRST + 3;
/**
* Display area that can be magnified in
* ......
*/
public static final int FEATURE_FULLSCREEN_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 5;
/**
* 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;
根据注释能知道这个Feature代表这个图层具体用于什么特征了。
然后还看到all(),and(),except()等方法。
all,and,except方法
# DisplayAreaPolicy.Feature.Builder
Builder all() {
Arrays.fill(mLayers, true);
return this;
}
Builder and(int... types) {
for (int i = 0; i < types.length; i++) {
int type = types[i];
set(type, true);
}
return this;
}
Builder except(int... types) {
for (int i = 0; i < types.length; i++) {
int type = types[i];
set(type, false);
}
return this;
}
Builder upTo(int typeInclusive) {
// 根据传入的type计算到图层
final int max = layerFromType(typeInclusive, false);
for (int i = 0; i < max; i++) {
mLayers[i] = true;
}
set(typeInclusive, true);
return this;
}
private void set(int type, boolean value) {
mLayers[layerFromType(type, true)] = value;
......
}
private int layerFromType(int type, boolean internalWindows) {
return mPolicy.getWindowLayerFromTypeLw(type, internalWindows);
}
Feature build() {
// 默认为true
if (mExcludeRoundedCorner) {
// Always put the rounded corner layer to the top most layer.
mLayers[mPolicy.getMaxWindowLayer()] = false;
}
return new Feature(mName, mId, mLayers.clone(), mNewDisplayAreaSupplier);
}
mLayers前面说过是一个长度为37的数组,set方法就是将参数的这个图层,对应的boolean设置为true, 换句话说就是指定某个图层是否支持这个Feature。
all():将所有数组所有值都设为true,表示每个图层都支持这个Feature
and(): 将指定某个图层支持这个Feature
except():将指定某个图层不支持这个Feature
upTo(): 将支持Feature的图层设置为从0到typeInclusive
build():将数组的最后最后一个设置为false,剔除最后一层
getWindowLayerFromTypeLw方法(决定窗口挂载在哪一层)
# WindowManagerPolicy
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
boolean roundedCornerOverlay) {
// Always put the rounded corner layer to the top most.
// 第二个参数为false,这里忽略
if (roundedCornerOverlay && canAddInternalSystemWindow) {
return getMaxWindowLayer();
}
// 根据这2个type名字也知道表示 APP图层,对应的值是1-99,如果处于这直接就返回APPLICATION_LAYER =2
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return APPLICATION_LAYER;
}
// 然后开始根据各个WindowType,去返回其在层级树中所在的图层
switch (type) {
case TYPE_WALLPAPER:
// wallpaper is at the bottom, though the window manager may move it.
return 1;
case TYPE_PRESENTATION:
case TYPE_PRIVATE_PRESENTATION:
case TYPE_DOCK_DIVIDER:
case TYPE_QS_DIALOG:
case TYPE_PHONE:
return 3;
case TYPE_SEARCH_BAR:
return 4;
case TYPE_INPUT_CONSUMER:
return 5;
case TYPE_SYSTEM_DIALOG:
return 6;
case TYPE_TOAST:
// toasts and the plugged-in battery thing
return 7;
case TYPE_PRIORITY_PHONE:
// SIM errors and unlock. Not sure if this really should be in a high layer.
return 8;
case TYPE_SYSTEM_ALERT:
// like the ANR / app crashed dialogs
// Type is deprecated for non-system apps. For system apps, this type should be
// in a higher layer than TYPE_APPLICATION_OVERLAY.
return canAddInternalSystemWindow ? 12 : 9;
case TYPE_APPLICATION_OVERLAY:
return 11;
case TYPE_INPUT_METHOD:
// on-screen keyboards and other such input method user interfaces go here.
return 13;
case TYPE_INPUT_METHOD_DIALOG:
// on-screen keyboards and other such input method user interfaces go here.
return 14;
case TYPE_STATUS_BAR:
return 15;
case TYPE_STATUS_BAR_ADDITIONAL:
return 16;
case TYPE_NOTIFICATION_SHADE:
return 17;
case TYPE_STATUS_BAR_SUB_PANEL:
return 18;
case TYPE_KEYGUARD_DIALOG:
return 19;
case TYPE_VOICE_INTERACTION_STARTING:
return 20;
case TYPE_VOICE_INTERACTION:
// voice interaction layer should show above the lock screen.
return 21;
case TYPE_VOLUME_OVERLAY:
// the on-screen volume indicator and controller shown when the user
// changes the device volume
return 22;
case TYPE_SYSTEM_OVERLAY:
// the on-screen volume indicator and controller shown when the user
// changes the device volume
return canAddInternalSystemWindow ? 23 : 10;
case TYPE_NAVIGATION_BAR:
// the navigation bar, if available, shows atop most things
return 24;
case TYPE_NAVIGATION_BAR_PANEL:
// some panels (e.g. search) need to show on top of the navigation bar
return 25;
case TYPE_SCREENSHOT:
// screenshot selection layer shouldn't go above system error, but it should cover
// navigation bars at the very least.
return 26;
case TYPE_SYSTEM_ERROR:
// system-level error dialogs
return canAddInternalSystemWindow ? 27 : 9;
case TYPE_MAGNIFICATION_OVERLAY:
// used to highlight the magnified portion of a display
return 28;
case TYPE_DISPLAY_OVERLAY:
// used to simulate secondary display devices
return 29;
case TYPE_DRAG:
// the drag layer: input for drag-and-drop is associated with this window,
// which sits above all other focusable windows
return 30;
case TYPE_ACCESSIBILITY_OVERLAY:
// overlay put by accessibility services to intercept user interaction
return 31;
case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
return 32;
case TYPE_SECURE_SYSTEM_OVERLAY:
return 33;
case TYPE_BOOT_PROGRESS:
return 34;
case TYPE_POINTER:
// the (mouse) pointer layer
return 35;
default:
Slog.e("WindowManager", "Unknown window type: " + type);
return 3;
}
}
构建层级树DisplayAreaPolicyBuilder::build
上面只是将5个Feature添加到了rootHierarchy的mFeatures这个集合中。
# HierarchyBuilder
private final ArrayList<DisplayAreaPolicyBuilder.Feature> mFeatures = new ArrayList<>();
HierarchyBuilder addFeature(DisplayAreaPolicyBuilder.Feature feature) {
mFeatures.add(feature);
return this;
}
DisplayAreaPolicyBuilder::setRootHierarchy方法很简单,就是把添加了ImeContainer和5个Feature的HierarchyBuilder设置给DisplayAreaPolicyBuilder
# DisplayAreaPolicyBuilder
DisplayAreaPolicyBuilder setRootHierarchy(HierarchyBuilder rootHierarchyBuilder) {
mRootHierarchyBuilder = rootHierarchyBuilder;
return this;
}
然后开始执行DisplayAreaPolicyBuilder::build
# DisplayAreaPolicyBuilder
// 这个可以忽略,没有值
private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders =
new ArrayList<>();
Result build(WindowManagerService wmService) {
// 对输入参数进行验证,确保它们是有效的
validate();
// Attach DA group roots to screen hierarchy before adding windows to group hierarchies.
// 重点:构建层级树
mRootHierarchyBuilder.build(mDisplayAreaGroupHierarchyBuilders);
// 因为mDisplayAreaGroupHierarchyBuilders没有值,后面的都可以忽略
......
return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots,
mSelectRootForWindowFunc);
}
层级树结构构造
构造层级树分为2步:
- 构建PendingArea树(a、构建Feature相关,b、构建Leaf相关)
- 根据PendingArea树构建最终的DisplayAreas树,也就是层级树
PendingArea数据结构
# DisplayAreaPolicyBuilder.PendingArea
static class PendingArea {
final int mMinLayer; // 最小层级
final ArrayList<PendingArea> mChildren = new ArrayList<>();// 有Children说明也是一个容器
final Feature mFeature; // 当前支持的Feature
final PendingArea mParent; // 有父亲
int mMaxLayer; // 最大层级
// 从这几个成员变量其实能感觉到和上一篇画的层级树的图有点那味了
@Nullable DisplayArea mExisting; // 当前存在的容器
boolean mSkipTokens = false; // 只有输入法和应用会为true
PendingArea(Feature feature, int minLayer, PendingArea parent) {
mMinLayer = minLayer;
mFeature = feature;
mParent = parent;
}
......
}
下面这段代码比较长我再代码里加了很多注释,其实这一块就是java的循环对数据结构的处理。就和刚学java的时候看2个for循环一样。 这个方法其实是构建整个树的方法,先看一眼,后面会再具体分析。
# DisplayAreaPolicyBuilder.HierarchyBuilder
private final RootDisplayArea mRoot;
private void build(@Nullable List<HierarchyBuilder> displayAreaGroupHierarchyBuilders) {
final WindowManagerPolicy policy = mRoot.mWmService.mPolicy;
// 定义最大层级数 37 = 36+1
final int maxWindowLayerCount = policy.getMaxWindowLayer() + 1;
// 存储每个窗口层级对应的 DisplayArea.Tokens,一共37个,后续窗口挂载也是在这个数据结构上找
// 在方法底部执行instantiateChildren的时候调用
final DisplayArea.Tokens[] displayAreaForLayer =
new DisplayArea.Tokens[maxWindowLayerCount];
// 存储每个特性对应的 DisplayArea 列表
// mFeatures就是在configureTrustedHierarchyBuilder配置的Feature大小,很明显一共是5个
final Map<Feature, List<DisplayArea<WindowContainer>>> featureAreas =
new ArrayMap<>(mFeatures.size());
for (int i = 0; i < mFeatures.size(); i++) {
// 为每个feature,创建其对应的DisplayArea列表
featureAreas.put(mFeatures.get(i), new ArrayList<>());
}
// 到这里featureAreas里一共是5个值,key就是上节提到的5个Feature,value目前就是个空的List
// -------构建PendingArea树----
// *1 创建 PendingArea 数组,用于临时存储每个窗口层级对应的 PendingArea,也是37个
// 后面需要关注这个areaForLayer成员的变化
PendingArea[] areaForLayer = new PendingArea[maxWindowLayerCount];
// 先创建个PendingArea 再让areaForLayer的37个数据都默认为root的PendingArea
// 注意第一个参数feature为null
final PendingArea root = new PendingArea(null, 0, null);
Arrays.fill(areaForLayer, root);
// 2. 构建Features的树
// mFeatures.size为5,所以有5个大循环
final int size = mFeatures.size();
for (int i = 0; i < size; i++) {
// 拿到当前需要处理的Feature
final Feature feature = mFeatures.get(i);
PendingArea featureArea = null;
// 内部循环,37次
for (int layer = 0; layer < maxWindowLayerCount; layer++) {
// 如果这个层级,支持当前Feature
if (feature.mWindowLayers[layer]) {
//判断是否复用 PendingArea (同一个feature才复用,否则创建新的)
if (featureArea == null || featureArea.mParent != areaForLayer[layer]) {
// 创建新的 PendingArea,作为上一层级的子节点,用于当前层级,并且双向奔赴,设置为各自的孩子或者父亲
// 注意第一个参数feature
featureArea = new PendingArea(feature, layer, areaForLayer[layer]);
areaForLayer[layer].mChildren.add(featureArea);
}
areaForLayer[layer] = featureArea;
} else {
// 如果该特性不应用于当前窗口层级,则featureArea置为空。用于上面if的判断
featureArea = null;
}
}
}
// 到这里,areaForLayer这个37层就按照feature分类,有自己对应的PendingArea了。
// 3. 构建叶子节点相关的PendingArea,注意还是操作areaForLayer数组,但是操作的是内部元素的mChildren的值
// 定义一个叶子节点用的 PendingArea
PendingArea leafArea = null;
int leafType = LEAF_TYPE_TOKENS;// 定义leafType
for (int layer = 0; layer < maxWindowLayerCount; layer++) {
// 获取每层的type,从这看type是和所在层级有关系的
int type = typeOfLayer(policy, layer);
// // 检查是否可以复用前一个层级的 Tokens,和前面的循环类似
if (leafArea == null || leafArea.mParent != areaForLayer[layer]
|| type != leafType) {
// 创建PendingArea,注意参数,featur为null
leafArea = new PendingArea(null /* feature */, layer, areaForLayer[layer]);
// 注意是添加到孩子,而不是跟上一次循环直接修改areaForLayer
areaForLayer[layer].mChildren.add(leafArea);
leafType = type;
// 应用类型处理
if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
// 添加 TaskDisplayArea 到应用程序层级
addTaskDisplayAreasToApplicationLayer(areaForLayer[layer]);
// 添加 DisplayAreaGroup 到应用程序层级
addDisplayAreaGroupsToApplicationLayer(areaForLayer[layer],
displayAreaGroupHierarchyBuilders);
// 跳过创建 Tokens,即不创建 Tokens,即使没有 Task
leafArea.mSkipTokens = true;
} else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
// 输入法处理
leafArea.mExisting = mImeContainer;
// 跳过
leafArea.mSkipTokens = true;
}
}
leafArea.mMaxLayer = layer;
}
// 计算根节点的最大层级
root.computeMaxLayer();
// -------构建DisplayAreas树----
// 4. 根据之前定义的PendingArea生成最后的 DisplayAreas 树
// 注意参数
// We built a tree of PendingAreas above with all the necessary info to represent the
// hierarchy, now create and attach real DisplayAreas to the root.
root.instantiateChildren(mRoot, displayAreaForLayer, 0, featureAreas);
// 通知根节点已经完成了所有DisplayArea的添加 (将displayAreaForLayer保存在RootDisplayArea成员变量roomAreaForLayer中,供后面逻辑使用)
mRoot.onHierarchyBuilt(mFeatures, displayAreaForLayer, featureAreas);
}
构建Feature相关
这边根据具体的执行画了几张图,先看上面Features的循环逻辑,在执行循环前数组areaForLayer和执行第一次大循环后集合如下
第一个Feature是WindowedMagnification拥有特征的层级 0-31,也就是其 前面32个为true。
在层级树,如果某一块都是支持同一Feature的话,可以写成 “name 起始层:结束层 ”的形式,转换后如下 转换成层级树的方式就是:
然后第二个大循环
第二Feature是HideDisplayCutout拥有特征的层级 0-14 16 18-23 26-35
太长了所以第24开始换到了下一排, 规律就是结合上一次的循环,0-31以内,HideDisplayCutout的父亲都是上一次循环的WindowedMagnification,然后32之后的父亲就是默认的root了。 再转成层级树的表示形式如下:
按照这个规则Feature 5次全执行完后,层级树的图就是下面这个,不过做了下顺序的调整,从小到大排序:
构建Leaf相关
在构建叶子节点的时候,又有一个新的东西,leafType,对应的就是叶子节点的类型,默认是LEAF_TYPE_TOKENS,一共也只定义了3个:
# DisplayAreaPolicyBuilder.HierarchyBuilder
private static final int LEAF_TYPE_TASK_CONTAINERS = 1; // APP
private static final int LEAF_TYPE_IME_CONTAINERS = 2; // 输入法
private static final int LEAF_TYPE_TOKENS = 0; // 默认
然后是通过typeOfLayer方法根据当前层级返回type
# DisplayAreaPolicyBuilder.HierarchyBuilder
private static int typeOfLayer(WindowManagerPolicy policy, int layer) {
if (layer == APPLICATION_LAYER) {
return LEAF_TYPE_TASK_CONTAINERS;
} else if (layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)
|| layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD_DIALOG)) {
return LEAF_TYPE_IME_CONTAINERS;
} else {
return LEAF_TYPE_TOKENS;
}
}
逻辑还是比较简单的除了输入法(13-14)和应用(2)所在的层级,均返回LEAF_TYPE_TOKENS。
经过第二个for循环后,相当于给每个Feature 都加上了一个Leaf , 然后对输入法和应用图做了单独的处理。
先看输入法的, 是把mExisting设置为了最开始从DisplayConten传进来的mImeContainer,然后mSkipTokens设置为false,表示后续的操作可以跳过。
然后看对应用图层的处理,除了也将mSkipTokens设置为false外还执行了2个方法其中第二個方法。addDisplayAreaGroupsToApplicationLayer因为内部依赖displayAreaGroupHierarchyBuilders,而目前也没看到对这个对象操作的地方,所以长度为0,可以忽略,所以只看
addTaskDisplayAreasToApplicationLayer方法即可
addTaskDisplayAreasToApplicationLayer
mTaskDisplayAreas看到应该联想到前面创建的name为“DefaultTaskDisplayArea”的那一个TaskDisplayArea,事实上也就是在那创建的,这个是和应用最相关的图层,
从代码上看也能证明:
# DisplayAreaPolicyBuilder.HierarchyBuilder
private final ArrayList<TaskDisplayArea> mTaskDisplayAreas = new ArrayList<>();
HierarchyBuilder setTaskDisplayAreas(List<TaskDisplayArea> taskDisplayAreas) {
mTaskDisplayAreas.clear();
mTaskDisplayAreas.addAll(taskDisplayAreas);
return this;
}
private void addTaskDisplayAreasToApplicationLayer(PendingArea parentPendingArea) {
// 已知长度为1,
final int count = mTaskDisplayAreas.size();
for (int i = 0; i < count; i++) {
PendingArea leafArea =
new PendingArea(null /* feature */, APPLICATION_LAYER, parentPendingArea);
// 所以就是把“DefaultTaskDisplayArea”这个设置为mExisting
leafArea.mExisting = mTaskDisplayAreas.get(i);
leafArea.mMaxLayer = APPLICATION_LAYER;
// parentPendingArea.mChildren本来为大家都一样的Leaf,又添加了一个DefaultTaskDisplayArea
parentPendingArea.mChildren.add(leafArea);
}
}
这段对应用图层的处理非常的重要了,特别最下面对parentPendingArea.mChildren再次添加DefaultTaskDisplayArea的操作
经过这个循环的处理,每个Feature下面都有了对应的叶子节点,如图:
第二层应用层目前是有2个孩子的,一个是Leaf,另一个就是DefaultTaskDisplayArea。 到目前为止,层级树雏形是有了。但是比较还是一个PendingArea数组,另外 Leaf 0:1 这种目前在代码上也还没有得到体现。
真正DisplayAreas树PendingArea::instantiateChildren
其实从PendingArea::instantiateChildren上面源码给的2个注释也知道,前面的2个循环,只是构建了一个PendingAreas树,接下来才是真正构建层级树(DisplayAreas)
并把这个树添加到root(DisplayContent)
# DisplayAreaPolicyBuilder.PendingArea
void instantiateChildren(DisplayArea<DisplayArea> parent, DisplayArea.Tokens[] areaForLayer,
int level, Map<Feature, List<DisplayArea<WindowContainer>>> areas) {
// 1. 子区域按照它们的最小层级进行升序排列
mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer));
// 2. 遍历孩子将PendingArea转换成DisplayArea
for (int i = 0; i < mChildren.size(); i++) {
final PendingArea child = mChildren.get(i);
final DisplayArea area = child.createArea(parent, areaForLayer);
if (area == null) {
// TaskDisplayArea and ImeContainer can be set at different hierarchy, so it can
// be null.
continue;
}
// 将返回的area设置为孩子,第一次执行的时候root就是DisplayContent
parent.addChild(area, WindowContainer.POSITION_TOP);
if (child.mFeature != null) {
// 让Feature对应的容器里添加创建的DisplayArea
areas.get(child.mFeature).add(area);
}
// 开始迭代构建
child.instantiateChildren(area, areaForLayer, level + 1, areas);
}
}
先解析一下3个参数
parent:根据上面代码的代码逻辑,root就是DisplayContent
areaForLayer: 这个是build方法开始创建的displayAreaForLayer
level:从哪级开始
areas: 这个也是build方法创建的map集合,key是Feature。
- 上来就执行了个排序,这个mChildren是啥呢?咋一看好像一点印象都没有,但是根据这个方法调用处看,他是root.instantiateChildren,
而这个root是构建PendingAreas树时最开始创建的root,也就是我们上面图片PendingAreas树里的 root 0:0。所以他的孩子就是2次循环处理后,父亲是他的PendingArea,也就是那些feature或者leaf - 这一步就是将那些PendingArea的数据结构转换为DisplayArea
之前看过PendingArea的成员变量和构造方法,现在看看
# DisplayAreaPolicyBuilder.PendingArea
@Nullable
private DisplayArea createArea(DisplayArea<DisplayArea> parent,
DisplayArea.Tokens[] areaForLayer) {
// 只有输入法和应用层mExisting有值
if (mExisting != null) {
if (mExisting.asTokens() != null) {
// 只有输入法满足
// Store the WindowToken container for layers
fillAreaForLayers(mExisting.asTokens(), areaForLayer);
}
// 然后将mExisting作为结果返回
return mExisting;
}
// mSkipTokens为true则返回,应用和IME创建的PendingArea
if (mSkipTokens) {
return null;
}
// 2. 定义DisplayArea的type
DisplayArea.Type type;
if (mMinLayer > APPLICATION_LAYER) {
type = DisplayArea.Type.ABOVE_TASKS;
} else if (mMaxLayer < APPLICATION_LAYER) {
type = DisplayArea.Type.BELOW_TASKS;
} else {
type = DisplayArea.Type.ANY;
}
if (mFeature == null) {
// // 3. 构建返回的leaf 注意第三个参数格式
final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type,
"Leaf:" + mMinLayer + ":" + mMaxLayer);
fillAreaForLayers(leaf, areaForLayer); // 给对应覆盖的层级都需要赋值
return leaf;
} else {
// 对有Feature的PendingArea返回构建
return mFeature.mNewDisplayAreaSupplier.create(parent.mWmService, type,
mFeature.mName + ":" + mMinLayer + ":" + mMaxLayer, mFeature.mId);
}
}
注意这里的参数areaForLayer这个是一个build方法创建的集合,也是最终层级树的体现。
方法前面mExisting.asTokens, 这个asTokens,方法定义在DisplayArea中默认返回null,只有DisplayArea.Tokens返回本身。 而ImeContainer是继承DisplayArea.Tokens的,所以有返回值。
而对于应用层mExisting是TaskDisplayArea,不是DisplayArea.Tokens的子类,所以这个不满足,也就是说只有IME的PendingArea才会执行下面fillAreaForLayers的逻辑
# DisplayAreaPolicyBuilder.PendingArea
private void fillAreaForLayers(DisplayArea.Tokens leaf, DisplayArea.Tokens[] areaForLayer) {
for (int i = mMinLayer; i <= mMaxLayer; i++) {
areaForLayer[i] = leaf;
}
}
fillAreaForLayers方法也比较简单,就是将这个PendingArea的所有图层都设置传进来的leaf。那当前逻辑只处理IME的话,就是把13,14层都设置这个mExisting.
另外应用层不执行到fillAreaForLayers,执行后面的return mExisting, 这里也有个很重要的点,因为前面知道应用层的Feature有2个孩子,但是mExisting却是为DefaultTaskDisplayArea,
这也就是为什么最终层级树的第二层只有DefaultTaskDisplayArea的原因
-
定义了个DisplayArea的type, 也不复杂, 如果当前区域最小的图层都大于应用图层(2),那type就是ABOVE_TASKS,如果最大图层还小于应用图层(2)就是BELOW_TASKS(这个只有壁纸了),
其他的就是ANY。目前还不知道具体用处,我认为了解即可 -
mFeature == null的条件,在上面build方法里有2个for循环都创建了PendingArea对象,第二个创建叶子节点的时候是没有传递mFeature的。
直接创建DisplayArea.Tokens,最重要的是第三个参数,是一个字符串,就是构建这个对象的name,看格式也是非常的清楚。其实就是层级树的Leaf节点,比如“Leaf:0:1 ”。(舒服了) -
这里处理的是第一次循环对Feature构建出来的PendingArea,
这里比较好奇的是这个mNewDisplayAreaSupplier是什么,那么就需要看Feature的定义了
# DisplayAreaPolicyBuilder
static class Feature {
private final String mName;
private final int mId;
private final boolean[] mWindowLayers;
private final NewDisplayAreaSupplier mNewDisplayAreaSupplier;
// 构造函数
private Feature(String name, int id, boolean[] windowLayers,
NewDisplayAreaSupplier newDisplayAreaSupplier) {
mName = name;
mId = id;
mWindowLayers = windowLayers;
mNewDisplayAreaSupplier = newDisplayAreaSupplier;
}
static class Builder {
......
// 默认为DisplayArea对象
private NewDisplayAreaSupplier mNewDisplayAreaSupplier = DisplayArea::new;
private boolean mExcludeRoundedCorner = true;
Feature build() {
......
return new Feature(mName, mId, mLayers.clone(), mNewDisplayAreaSupplier);
}
}
}
/** Supplier interface to provide a new created {@link DisplayArea}. */
interface NewDisplayAreaSupplier {
DisplayArea create(WindowManagerService wms, DisplayArea.Type type, String name,
int featureId);
}
mNewDisplayAreaSupplier这个对象的赋值是在Feature的构造方法,而根据代码分析,添加的5个Feature是通过Builder的方式,所以我们现在分析的
mNewDisplayAreaSupplier的值,就是定义在Feature.Builder下的默认值也就是DisplayArea对象
所以这一步就是返回了一个DisplayArea对象,然后name就是 “mFeature.mName + “:” + mMinLayer + “:” + mMaxLayer” 比如 “HideDisplayCutout:32:35”
到现在为止,层级树每个成员是如何构建,以及里面的字符串名字是怎么来的,就全都清楚了。
后面迭代也只是方法的递归而已,经过一层一层的迭代后,整个层级结构树就构建好了。
现在的层级树如下:
这个层级树和上一篇看 不太一样那是因为Leaf下没有内容了,应用层“DefaultTask
DisplayArea”和壁纸层也没有内容,那是因为Leaf后面的内容都是具体业务添加上去的。
所以其实对应Window的add流程,其实也就是真没添加到这个层级树的流程。后面具体分析业务的时候肯定是会有具体案例的。