【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树
在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。
对这一块的概念以及相关源码做了详细分析,整理出以下几篇。
【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树
【Android 13源码分析】WindowContainer窗口层级-2-构建流程
【Android 13源码分析】WindowContainer窗口层级-3-实例分析
【Android 13源码分析】WindowContainer窗口层级-4-Surface树
当前为第一篇,主要是基础知识介绍。
打开“电话应用”,然后按音量键出现一下界面:

提出2个问题:
- 为什么音量窗口会挡住应用窗口?
- 为什么不管打开哪个应用都能看到导航栏和状态栏?
再看下面这个图:

左边是将第一张截图的每一个窗口提出来画了的模拟图, 想象一下每个窗口其实都是全屏的,那么他的前后顺序若右图(注意颜色是对应的)。 如果说是按右边的这种层级排序,那么音量键的窗口和状态栏的窗口就会挡住Activity的窗口。
在安卓中窗口是有先后顺序的,越靠近用户的就越靠前,就能挡住底下的窗口。 目前说这么一个结论可能为时过早,后面的内容将详细介绍。
1. 基础知识介绍
1.1 三维空间概念
1.1.1 三维空间概念–游戏3D世界
下面2个图来自Unity3d官网开发文档:

们玩的王者荣耀,原神等游戏的开发,都是类似在这么一个3D场景下进行的,开发者将使用3D建模工具来创建地形、建筑、植被等地图元素,并通过材质和贴图来增强地图的视觉效果。
比如这张图片里放了3个颜色的柱子。 除了这些物体外,可以看到还有一个摄像机(Camera),它是用来捕捉画面的, 毕竟手机屏幕是2D的,简单来说这个摄像机能捕捉到的画面就是我们手机屏幕上显示的内容,比如这张图片捕捉到的画面是右下角的内容。
像我们玩游戏移动角色,其实就是通过转动这个摄像机(Camera)来完成的。
下面这种图会更加像一个游戏的画面一些。

通过看游戏地图的3D开发场景,就是希望能狗更加生动的理解到在2D的手机屏幕下,其实有一个3D的空间,虽然我们的安卓开发不会像游戏开发那么复杂,但是原理也是一样的。
1.1.2 三维空间概念–Android的三维坐标系

Android坐标系其实就是一个三维坐标,Z轴向上,X轴向右,Y轴向下。
经常听到的 Z-Order 也就是指窗口在Z轴的排序,离用户越近,Z值越大。并且能够遮挡住后面的窗口。
为了再加深一下安卓窗口的层级关系,下面看一张应用开发的View层级的3D视角。
注意,下面的图片是View,不是本次要讲的窗口,只是为了加深一下层级印象

可以看到在布局了写了37层约束布局,其实第3层加了一个按钮。 右上角我们手机屏幕上只是一个按钮的界面,但是右下角通过Android Studio自带的工具可以发现,整个View树是有很多层的(代码写了37层)。
这里看到的View层级其实和后面要讲就窗口分层,是有相似之处的。 将这里是37层想象层安卓在窗口的分层也是可以的。
1.2 ViewTree
为了后续方便理解窗口树, 先介绍一下安卓开发都知道的View树

XML里如图写下2个简单的红绿布局,显示的UI效果绿色的会挡住一部分红色的。但是用工具其实可以发送红色控件其实也是完成绘制的。

也就是说在同一层级下,在ViewGropu孩子View数组这个集合中,下标越大的View会挡住后面的View。也可以理解层 层级越靠前,就会当初后面的View。
继续增加几个View

-
控件B下新增控件D,D下面放了一个文本控件G
-
控件C下面新增了一个文本控件F

左边看到对View做了层级处理,这个就是ViewTree,可以将其画成树图

这个就是View层的View树,能构建出View树的原因是因为在代码中定义了ViewGroup这个类
# View// 父容器protected ViewParent mParent;# ViewGroup// 所有子Viewprivate View[] mChildren;public void addView(View child, int index) {......// 内部实现是通过addInArray 将View添加到数组mChildren中addView(child, index, params);}@Overridepublic void removeView(View view) {......// 本质还是从mChildren移除}

应用布局用的场景的几个类,他们都有一个共同的父类–ViewGroup。
ViewGroup继承了View,所以有父亲(mParent),自身内部又维护了一个View数组(mChildren)表示它的孩子们。 上有父亲下有一群孩子,所以能构建出一个ViewTree。
如果只有一个孩子,那是线性结构,因此孩子必须是多个,所以ViewGroup也是一个View的容器类。 有这么一个类的存在,开发者就可以通过嵌套来行程一个非常复杂的ViewTree结构
这也是应用开发者能写出各种丰富UI的基础。
在窗口这一级别的开发中,也有窗口树,也有对应的容器结构。 后面会详细解释窗口容器类,在介绍之前先了解一下什么是窗口。
ViewTree在代码中也是真实存在的,在Activity中通过以下代码对应ViewTree的变化做监听
View.getViewTreeObserver().addOnGlobalLayoutListener(......)
1.3 什么是窗口
从视觉上,用户在手机屏幕上看到的“一块区域”就是一个窗口,比如前面看到的这张图,每一块都是一个窗口

从代码上来说,应用端的窗口指的是Window, framework层的窗口指的是WindowState

为什么会用不一样的类来表示“窗口”呢?
比如说有一个人,他是唯一的,身份证号是他的唯一表示,但是他在不同的系统中,保存的他的数据是不一样的,比如在公司,公司系统对这个人保存的个人基本信息,在交警系统,保存的是这个人的车辆信息和违章信息。

在生活中,不同系统对一个人关注保存的数据是不一样,在代码中也是一样的。应用开发者为了降低模块的依赖,也会有这种设计。
2. 初识窗口层级树
2.1 窗口容器类介绍
前面看到了一些View树构建的类,也就是我们说的常见布局,现在列举构建窗口树用的的几个容器类。

WindowContainer:
类似ViewGroup的存在,是窗口容器的基类,后面介绍的其他窗口容器类都是它的子类。
有泛型限制,说明容器的内容是有限制的
mParent: 保存当前容器的父窗口引用。
mChildren :保存当前窗口的所有孩子窗口容器集合(有泛型)。根据注释,列表后面的子容器,z-order 越大,离屏幕越近。
父类为ConfigurationContainer,封装了配置的处理,当前类封装了容器的操作。

RootWindowContainer:*
根窗口容器,也是窗口层级树的根,管理DisplayContent。

DisplayContent:
代表一个屏幕,Android是支持多屏幕的。
继承关系为:
DisplayContent->RootDisplayArea->DisplayArea.Dimmable->DisplayArea->WindowContainer
孩子为DisplayArea(这个规则定义在DisplayArea的子类Dimmable中)

WindowState:
代表一个窗口,本身也是一个容器,比如子窗口就是它的孩子(Popupwindow场景)

DisplayArea:
注释:DisplayContent下的窗口容器集合。
说人话:表示一块显示区域,是DisplayContent的子容器。DisplayContent下安卓目前设计为分了37层,每一层都是一个DisplayArea。
有三个直接子类,TaskDisplayArea,DisplayArea.Tokens和DisplayArea.Tokens。

TaskDisplayArea:
注释:孩子可以是Task,或者是TaskDisplayArea。(不过目前看到的孩子都是Task类型)。
对应层级树的第二层,专门用来存放应用的窗口图层,非常重要,APP的窗口都在这。也是窗口层级树看的重点区域。

DisplayArea.Token:
DisplayArea的子类,并且是其内部类,表示WindowToken的容器。WindowToken的子类是WindowState

DisplayArea.Dimmable:
DisplayArea的子类,并且是其内部类,DisplayContent的父类,带模糊效果。孩子是DisplayArea。

ImeContainer:
输入法容器,父类是DisplayArea.Token,那么孩子也是WindowToken。输入法专用

Task:
父类TaskFragment继承WindowContainer,泛型没有限制孩子的类型。但是实际情况下孩子是Task和ActivityRecord类型。
开发过程中经常见到的类,也是应用开发,多窗口开发经常会遇到的。

WindowToken:
理器中一组相关窗口的容器,是窗口的Token,而窗口的定义WindowState。一般在窗口层级树中WindowToken下面就会挂载一个WindowState。
壁纸用到的WallpaperWindowToken也是其子类。

ActivityRecord:
对应着一个Activity。
是WindowToken的子类,所以孩子也是WindowState。 从应用开发角度,一个Activity下也有一个Window。并且一般作为是Task的孩子。
2.2 Feature介绍
为什么有这个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
特征描述:输入法相关

3 WMS的层级结构树
可以通过以下命令来看获取到设备当前的层级结构树
adb shell dumpsys activity containers
在开完机后的launcher就执行了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]
先看看点击桌面的“电话“进入电话界面后,再执行这个dump命令有什么区别

tips: com.google.android.dialer 这个包名是"电话"这个应用
这个区别就比较好看出来了,在#1 DefaultTaskDisplayArea下多了一些东西。看到里面也知道这个是增加一些与“电话”这个应用的Activity相关的东西。
然后再按一下音量键盘,看一下区别

在其他的场景比如按power出现的弹窗, 出现toast的时候,或者进入分屏都可以进行dump,对比一下差异。
现在可以有以下信息:
- 开完机层级结构树就存在
- 界面上有相关的Window的操作,都会在层级结构树上体现
- 不同的window会被挂在到对应的位置,这个其实就是层级结构树的关键。
当然哪个Window应该挂在到那一层,怎么个先后顺序,这个我们其实无需过于在意,这个是产品设计。
tips: 可以试试不同Activity启动模式后的区别
3.1 简单分析层级结构树
现在来分析上面那一团输出信息怎么看。(从上到下,从左往右)
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
2. 层级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的。
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
这些有个印象就行,不需要硬背,以后看的多了自然就有感觉了。
3.2 层级结构树可视化
单看层级树可能过于枯燥,在刚开始学习的时候一般都会根据信息画出一个层级结构图。
首先根据前面看dump内容的方式,先画出一部分内容如下:

这里是DisplayContent下的3个孩子,可以看到已经覆盖了 0-36层。
然后按照这种方式将所有的内容都画出来就可以得到下面这完整的树图:

强烈建议想学这一块的同学一定要手动画出这么一个图
android版本相同,画出来的图基本上都是一样的,只会根据出现不同的Window在响应的Leaf,也就是叶子节点会有不同。
这里将叶子节点的颜色涂上了,发现叶子节点下要么为空,要么就是 WindowToken,除了最底层的壁纸,其实壁纸叶子节点下的WallpaperWindowToken这个类,也是继承的WindowToken。
可能之前通过文本的形式还有点陌生,但是转换成图片后,一些常见的东西就都清楚了,比如launcher,StatusBar,NavigationBar,Wallpaper
窗口树和View树还是有差距的,View树上都是View,而窗口树上只有叶子节点上挂着窗口,其他大都都是一些容器和一些。
窗口树是固定37层的, 然后各个图层都有自己支持的Feature这些都是代码中固定好的。 实际开发中,开发中再将自己的窗口根据需求挂到对应的叶子节点上,这个和View树是有区别的。
3.3 为什么这么设计
方便管理
在写应用的时候也会这样定义,这样如果说某个业务的View无论怎么写,他就只能在自己所在的“层级”上显示,不会影响到其他。
窗口这样设计的原因可能还有其他的考虑,在远古时期窗口的顺序好像是经过规则计算的,那么这种计算很麻烦出了问题也不好定位。
现在的这种设计就很合理,符合“单一原则”,各个类型的窗口在自己的层级上,不会影响到其他。
方便功能开发
另外一个优点我认为是这种设计为小窗,分屏这种功能提供了开发的便利,因为只需要将对应的窗口移动到所在的Task就可以了。
像现在的Activity启动,在system_service进程的处理的很多逻辑都是围绕着这个层级树来做的。
比如看一眼“电话”和“短信”2个应用进行分屏操作的前后对比:

通过对比可以发现
- Task=5,6 这2个Task是分屏用到的Task,它们有共同的父亲-- Task =4。 这3个Task 在开机的时候就创建好了,默认在DefaultTaskDisplayArea孩子里是最后面。
- 启动分屏后其实对应窗口容器这边做了2件事
- 将Task=4 移到栈顶
- 将2个分屏应用的Task分别移到到Task5,6 下
这样分屏就完成了。
相关文章:
【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树
在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。 对这一块的概念以及相关源码做了详细分析,整理出以下几篇。 【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树 【Android 13源码分析】WindowCon…...
Node.js的学习2——内置模块(一)
Node.js的内置模块 module模块global全局变量Console控制台Errors错误模块捕获异常异步方法通过回调函数传递异常事件触发器对象异常捕获 module模块 使用module模块可以查看Node.js所有的内置模块、在所有模块中都可以使用的全局变量、程序在运行过程中可能会出现的四类错误。…...
信息安全工程师(5)域名与域名解析
一、域名 1. 定义与功能 域名(Domain Name)是互联网上用于标识网站或服务器地址的名称,由一串由点分隔的字符组成,如“example.com”。域名的主要功能是提供一种便于记忆和输入的地址形式,以代替难以记忆的IP地址。域名…...
idear导入他人项目如何快速运行
最近idear经常导入别人的项目,结果永远在加载依赖项。网上查了一堆资料,什么jdk问题,环境变量问题,maven仓库路径问题,总之就是没啥用。那有没有什么简单粗暴的办法,能够导入项目后快速运行呢。 解决方法&a…...
直流无刷电机霍尔线序自学习解释
直流无刷电机霍尔线序自学习 步骤详解 1. 初始连接 连接电机的三相线:A、B、C。连接霍尔传感器线:HA、HB、HC。 2. 输入电压组合与霍尔信号记录 电机的电压输入组合和霍尔信号记录是电机控制系统中至关重要的一部分,它们决定了电机的运转…...
C++学习笔记(26)
七 、显示字符串中的字符 从界面上输入一个字符串(C 风格),把字符串中的每个字符显示出来,如果输入的是"abc",要求: 1)正序显示:a b c 2)逆序显示:…...
安卓14剖析SystemUI的ShadeLogger/LogBuffer日志动态控制输出dumpsy机制
背景: 看SystemUI的锁屏相关代码时候发现SystemUI有一个日志打印相关的方法调用,相比于常规的Log.i直接可以logcat查看方式还是比较新颖。 具体日志打印代码如下: 下面就来介绍一下这个ShadeLogger到底是如何打印的。 分析源码࿱…...
华为CNA VRM搭建(使用vmware worfstartion搭建)
创建虚拟机: 自定义→高级 选择硬件兼容性:默认安装版本,如果未来想要将此虚拟机安装到其他电脑,其他电脑版本过低,此时可以向下兼容,这里我们默认版本 稍后安装操作系统: CNA采用Euler OS系统…...
【WRF工具】WRF Domain Wizard第二期:使用教程
【WRF工具】WRF Domain Wizard第二期:使用教程 WRF Domain Wizard使用教程1)Wizard Option:新建区域/打开已有区域2)New Domain:新建区域3)Horizontal Editor:水平编辑器4)Namelist.…...
智能摄像头MP4格式化恢复方法
如果说生孩子扎堆,那很显然最近智能摄像头多碎片的恢复也扎堆了,这次恢复的是一个不知名的小品牌。其采用了mp4视频文件方案,不过这个案例的特殊之处在于其感染了病毒且不只一次,我们来看看这个小品牌的智能恢复头格式化的恢复方法…...
【C++】unordered系列
前言: 在C11及以后的标准中,unordered容器是标准模板库(STL)的一部分,提供了高效的数据结构选项,适用于需要快速查找和插入操作的场景。 unordered通常与关联容器一起使用,特别是unordered_map和…...
Cobbler 搭建方法
统信服务器操作系统行业版V20-1000c【Cobbler 搭建】手册 统信服务器操作系统行业版 V20版本上Cobbler 搭建方法 文章目录 功能概述一、使用范围二、cobbler工作流程1. Server 端2. Client 端三、 环境准备1. 测试环境告知,以提供配置时参考:2. 关闭防火墙、selinux:3. 注意…...
从边缘到云端,合宙DTURTU打造无缝物联网解决方案
随着物联网(IoT)技术的飞速发展,万物互联的时代已经到来, 如何高效、稳定地连接边缘设备与云端平台,实现数据的实时采集、传输与处理,成为了推动物联网应用落地的关键。 DTU(数据传输单元&…...
【Android Studio】API 29(即Android 10)或更高版本,在程序启动时检查相机权限,并在未获取该权限时请求它
文章目录 1. 在AndroidManifest.xml文件中,声明相机权限:2. 在你的Activity中(例如MainActivity)测试 1. 在AndroidManifest.xml文件中,声明相机权限: <uses-feature android:name"android.hardwar…...
【裸机装机系列】3.kali(ubuntu)-更新sources.list并重启
当装机并重启计算机后,暂时还不能使用,需要更新源并下载软件 1、更新软件源 1> 切换root使用命令 sudo su root 进入界面后,是你自己的账户,不是root账户,这里的操作是需要进入root账户进行操作的,否…...
text2sql(NL2Sql)综述《The Dawn of Natural Language to SQL: Are We Fully Ready?》
《The Dawn of Natural Language to SQL: Are We Fully Ready?》(github)出自2024年6月的NL2SQL(Natural language to SQL )综述论文。这篇论文尝试回答如下三个问题: 问题1:NL2SQL的现状是什么?(Q1:Where Are we Now?) 论文图1总结了近20年NL2SQL方法…...
【滑动窗口】一题讲透滑动窗口!
🚀个人主页:一颗小谷粒 🚀所属专栏:力扣刷题 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 1.1 题目要求 1.2 算法图解分析 1.3 代码实现 1.4 时间复杂度分析 1.5 算法思想总结 1.1 题目要…...
嵌入式通信原理—SPI总线通信原理与应用
文章目录 SPI 简介基本原理工作模式特点 SPI寻址方式1. 片选(Chip Select, CS)2. 多从设备通信3. 菊花链(Daisy-Chain)模式4. 地址寄存器(应用层) SPI通信过程时钟信号生成(SCLK)数据…...
基于web的 BBS论坛管理系统设计与实现
博主介绍:专注于Java .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的可以…...
【Scala入门学习】Scala的方法和函数
1. 方法 在scala中的操作符都被当成方法存在,比如说、-、*、/ 12就是1.(2)的调用, 2.0 是doule类型,强调用Int类型的写法为1.(2:Int) 1.1 方法的声明和使用 定义方法的语法: def 方法名([变量:变量类型ÿ…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
