【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树

在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。
对这一块的概念以及相关源码做了详细分析,整理出以下几篇。

【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树

【Android 13源码分析】WindowContainer窗口层级-2-构建流程

【Android 13源码分析】WindowContainer窗口层级-3-实例分析

【Android 13源码分析】WindowContainer窗口层级-4-Surface树

当前为第一篇,主要是基础知识介绍。

打开“电话应用”,然后按音量键出现一下界面:
电话应用按音量截图
提出2个问题:

  1. 为什么音量窗口会挡住应用窗口?
  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

在这里插入图片描述

  1. 控件B下新增控件D,D下面放了一个文本控件G

  2. 控件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,对比一下差异。
现在可以有以下信息:

  1. 开完机层级结构树就存在
  2. 界面上有相关的Window的操作,都会在层级结构树上体现
  3. 不同的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个应用进行分屏操作的前后对比:

在这里插入图片描述
通过对比可以发现

  1. Task=5,6 这2个Task是分屏用到的Task,它们有共同的父亲-- Task =4。 这3个Task 在开机的时候就创建好了,默认在DefaultTaskDisplayArea孩子里是最后面。
  2. 启动分屏后其实对应窗口容器这边做了2件事
    1. 将Task=4 移到栈顶
    2. 将2个分屏应用的Task分别移到到Task5,6 下

这样分屏就完成了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/424269.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智能家居环境监测系统设计(论文+源码)

1. 系统方案 系统由9个部分构成,分别是电源模块、烟雾传感器模块、GSM发送短信模块、报警模块、温度传感器模块、人体红外感应模块、按键设置模块、显示模块、MCU模块。各模块的作用如下:电源模块为系统提供电力;烟雾传感器模块检测烟雾浓度&…

[项目实战]EOS多节点部署

文章总览:YuanDaiMa2048博客文章总览 EOS多节点部署 (一)环境设计(二)节点配置(三)区块信息同步(四)启动节点并验证同步EOS单节点的环境如何配置 (一&#xf…

开放系统,面向各类业务需求可提供定制化服务的智慧物流开源了

智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。构建基于Ai技术的…

计算机毕业设计 智慧物业服务系统的设计与实现 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点…

Python数据分析 Pandas库-初步认识

Python数据分析 Pandas库-初步认识 认识Pandas ​ pandas是一个非常实用的Python工具,我们可以把它想象成一个超级强大的表格处理工具,它比Excel更智能,操作更为简单。pands可以从各种文件格式(CSV、JSON、SQL、Excel&#xff0…

ModbusTCP/RTU转Ethernet/IP(CIP)-Modbus设备与罗克韦尔AB的PLC之间通讯

IGT-DSER智能网关模块支持西门子、三菱、欧姆龙、罗克韦尔AB等各种品牌的PLC之间通讯,同时也支持PLC与Modbus协议的工业机器人、智能仪表、变频器等设备通讯。网关有多个网口、串口,也可选择WIFI无线通讯。无需PLC内编程开发,只要在IGT-DSER智…

AI大模型与产品经理:替代与合作的深度剖析

在创业的征途中,产品经理常常被外界以一种半开玩笑的口吻提及:“就差一个程序员了。”这句话背后,既蕴含着对产品经理创意与策略能力的认可,也揭示了技术实现环节对于产品成功不可或缺的重要性。然而,随着AI技术的飞速…

2024年微电子与纳米技术国际研讨会(ICMN 2024) Microelectronics and Nanotechnology

文章目录 一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询 一、会议详情 二、重要信息 大会官网:https://ais.cn/u/vEbMBz提交检索:EI Compendex、IEEE Xplore、Scopus大会时间:2024年9月20-22日地点:成都…

Golang数据流处理:掌握Reader和Writer接口的技巧

Golang数据流处理:掌握Reader和Writer接口的技巧 引言理解Reader和Writer接口Reader接口的定义和基本方法Writer接口的定义和基本方法 Reader接口的深入探讨Reader接口的实现示例使用io.Reader读取文件内容从网络连接中读取数据 常用Reader类型及其应用场景strings.…

Selenium打开浏览器后闪退问题解决

笔者这两天在做一个自动化方案,用来优化数据统计。其中一部分数据需要通过云上堡垒机跳转访问,而这个堡垒机在笔者日常使用的火狐浏览器上运行不是很正常(表现在有些复制粘贴按钮显示不太灵敏)。 但在Edge浏览器上基本正常&#…

Unity3d 以鼠标位置点为中心缩放视角(正交模式下)

思路整理: 缩放前: 缩放后: 记录缩放前鼠标的屏幕坐标 A,计算鼠标位置对应的世界坐标 A_world 缩放完成后,根据当前屏幕下A所对应的世界坐标A1_world 计算A1_world 和 A_world 的偏移量 移动摄像机 代码&#xff…

数据集 wider person 户外密集行人检测 >> DataBall

数据集 wider person 用于野外密集行人检测的多样化数据集 行人检测 目标检测 户外密集行人检测的多样化数据集 WiderPerson: A Diverse Dataset for Dense Pedestrian Detection in the Wild article{zhang2019widerperson, Author {Zhang, Shifeng and Xie, Yiliang and Wa…

TiDB 数据库核心原理与架构_Lesson 01 TiDB 数据库架构概述课程整理

作者: 尚雷5580 原文来源: https://tidb.net/blog/beeb9eaf 注:本文基于 TiDB 官网 董菲老师 《TiDB 数据库核心原理与架构(101) 》系列教程之 《Lesson 01 TiDB 数据库架构概述》内容进行整理和补充。 课程链接:…

跟《经济学人》学英文:2024年09月14日这期 Demand for high-end cameras is soaring

Demand for high-end cameras is soaring The ubiquity of smartphones has helped ubiquity: 美 [juːˈbɪkwəti] 到处存在;遍在 注意发音 原文: Buying a Leica feels like buying a piece of art. Made in Germany, the cameras are sold in th…

电容的充放电时间的计算(容性负载的输出建立时间)

本文重点叙述开关电源带容性负载时,电源的输出建立时间(对应电容的充电时间)和掉电时间(对应电容的放电时间)。 无论是恒压源还是恒流源,其输出的电流都是可以确定的,因此,电容的充…

高职院校人工智能技术和无人机技术实训室建设方案

一、方案背景与需求分析 1.1 人工智能与无人机技术发展概况 人工智能(AI)和无人机技术作为当今科技领域的两大热点,正以前所未有的速度发展和渗透到各行各业中。根据国际数据公司(IDC)的报告,全球人工智能市场规模预计将在2024年…

function uuid_generate_v4()不存在

说明:记录一次使用postgresql函数错误,如下: 项目中的一个SQL用到了uuid_generate_v4()函数生成uuid作为记录的主键,结果报上面这个错误; 分析&排查 首先,我连接上了数据库,在数据库里敲下…

【JavaScript】数据结构之字典 哈希表

字典 键值对存储的,类似于js的对象,但在js对象中键[key]都是字符串类型或者会转换成字符串类型,因此后声明的键值会覆盖之前声明的值。字典以map表示,map的键不会转换类型。 let map new Map() map.set(a, 1) map.set(b, 2) ma…

electron-vite vue3离线使用monaco-editor

目录 1.搭建一个 electron-vite 项目 2.安装monaco-editor和vite-plugin-monaco-editor 3.electron.vite.config.mjs配置 4.创建 worker.js并在main.js 引入 5.创建组件 MonacoVite.vue 组件 6. App.vue中引入组件 7.运行测试 1.搭建一个 electron-vite 项目 pnpm creat…

PCL 点云基于高程渲染颜色

目录 一、概述 1.1原理 1.2实现步骤 1.3 应用场景 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接: PCL点云算法与项目实战案例汇总(长期更新) 一、概述 本文将介绍如何使用PCL库…