在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。
对这一块的概念以及相关源码做了详细分析,整理出以下几篇。
【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树
【Android 13源码分析】WindowContainer窗口层级-2-构建流程
【Android 13源码分析】WindowContainer窗口层级-3-实例分析
【Android 13源码分析】WindowContainer窗口层级-4-Surface树
当前为第二篇,第一篇对窗口树有一个简单的认识后,本篇介绍窗口树的构建代码流程。
整个过程会相对无聊,但是不讲代码的技术文章就是耍流氓。
先看dump的数据在代码中是如何定义的
1 | typescript复制代码# WindowManagerService |
dumpChildrenNames的实现在WindowContainer的父类ConfigurationContainer中
1 | scss复制代码# ConfigurationContainer |
可以看到从RootWindowContainer开始递归打印。 这也就是dump到的窗口容器层级树的内容。比如最开始的RootWindowContainer::getName返回的内容就是 “ROOT”。
2.1 调用链
2.2 前期的一些调用链
调用链前面这段可以知道,在系统启动的时候就触发了这段逻辑,这也就是为什么刚进入launcher就可以dump出整个结构树的原因。
setWindowManager 方法传递的参数是WMS, WMS的启动是tartOtherServices中,而RootWindowContainer则是WMS的一个成员变量,RootWindowContainer是层级树中的跟容器,在WMS构建函数中创建。
1 | csharp复制代码# WindowManagerService |
继续看构建流程
1 | ini复制代码# RootWindowContainer |
重点解析:
- 这段代码也就能看出,为什么说一个DisplayContent就代表着1个屏幕了。
- 处理TaskDisplayArea相关(这里窗口层级树已经构建完成了)
上篇看层级树知道TaskDisplayArea就是放应用相关容器的,目前先不看这块,先跟踪DisplayContent下的逻辑, 现在需要看DisplayContent的构造方法,因为里面开始构造这个屏幕下层级树。(不考虑多屏幕的情况)
1 | scss复制代码# DisplayContent |
这一块我们目前可以忽略transaction的代码只需要关心中间“getDisplayAreaPolicyProvider”这一块就好了,这段是层级树的主流程。
tips:
- 方法最开始的 setName设置name在层级树是能找到对应名字的
- 注意instantiate倒数第3第4都个参数传递的都是this,也就是DisplayContent, 因为 DisplayContent这是的父类是RootDisplayArea
1 | csharp复制代码# DisplayContent |
mDisplayId 如果只有一个屏幕就是 0 ,所以dump到层级树中的这句信息
1 | shell复制代码 #0 Display 0 name="Built-in Screen" |
就是在这里设置的,后面的”Built-in Screen”对应的应该就是mDisplayInfo.name了。
接下来继续看主流程:
1 | scss复制代码# DisplayAreaPolicy.Provider |
这个方法是在DisplayContent构造函数掉进来的,注意最后2个参数,root表示跟容器,imeContainer则是输入法容器,在DisplayContent中传过来的,然后被通过setImeContainer设置给了HierarchyBuilder。
重点分析:
- “DefaultTaskDisplayArea” 终于出现了, 可以看到确实是TaskDisplayArea对象,然后FEATURE_DEFAULT_TASK_CONTAINER这个ID的值就是1, 那也就是在第二层,和层级树是对应的,然后构建了一个List,但是这个集合就这一个元素。
- 配置层级的支持的Feature
- 开始真正的构建
3.1 配置Feature
发现层级树中一共就出现了5个Feature就是在当前方法中配置的,分别如下:
WindowedMagnification
HideDisplayCutout
OneHanded
FullscreenMagnification
ImePlaceholder
1 | less复制代码# DisplayAreaPolicy.Provider |
这里执行了5次addFeature,每次对应一个Feature刚好是5个。Feature.Builder构造一个Feature对象,代码如下
1 | ini复制代码# DisplayAreaPolicy.Feature.Builder |
注意后面2个参数,第二个为name,就是名字,后面的是ID,根据使用的地方肯定是定义了对应ID的。
留意下mLayers,mPolicy.getMaxWindowLayer()返回36所以是定义了一个长度为37的boolean类型数组,如果为ture表示这个图层支持这个Feature,为false反之。
5个Feature对应的ID如下,并且有相应的注释:
1 | php复制代码# DisplayAreaOrganizer |
根据注释能知道这个Feature代表这个图层具体用于什么特征了。
然后还看到all(),and(),except()等方法。
3.1.1 all,and,except方法
1 | typescript复制代码# DisplayAreaPolicy.Feature.Builder |
mLayers前面说过是一个长度为37的数组,set方法就是将参数的这个图层,对应的boolean设置为true, 换句话说就是指定某个图层是否支持这个Feature。
all():将所有数组所有值都设为true,表示每个图层都支持这个Feature
and(): 将指定某个图层支持这个Feature
except():将指定某个图层不支持这个Feature
upTo(): 将支持Feature的图层设置为从0到typeInclusive
build():将数组的最后最后一个设置为false,剔除最后一层
这里的几个方法都会调用到layerFromType,根据layerFromType方法的调用知道具体逻辑在WindowManagerPolicy::getWindowLayerFromTypeLw方法控制的.
这段代码有点长是因为好多case,但是总体逻辑并不复杂,主要关注传入的WindowType和返回的Layertype,其实就是返回层级树中所在的图层。
3.1.2 重点:getWindowLayerFromTypeLw方法 (决定窗口挂载在那一层)
1 | kotlin复制代码# WindowManagerPolicy |
代码很长不用一个个看,直接根据参数找就好,比如当参数是TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,那对于的返回就是32。
后面看到的对应的TYPE,直接复制WindowManagerPolicy类下搜索即可。
现在再重新看看configureTrustedHierarchyBuilder方法里5个Feature到底是什么。
1 | less复制代码# DisplayAreaPolicy.Provider |
3.2 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.3 构建层级树 DisplayAreaPolicyBuilder::build
上面只是将5个Feature添加到了rootHierarchy的mFeatures这个集合中
1 | csharp复制代码# HierarchyBuilder |
DisplayAreaPolicyBuilder::setRootHierarchy方法很简单,就是把添加了ImeContainer和5个Feature的HierarchyBuilder设置给DisplayAreaPolicyBuilder
1 | ini复制代码# DisplayAreaPolicyBuilder |
然后开始执行DisplayAreaPolicyBuilder::build
1 | scss复制代码# DisplayAreaPolicyBuilder |
这个mRootHierarchyBuilder就是上一小节操作的RootHierarchyBuilder,然后执行其build方法,这个方法非常重要!!!
在构造层级树一共分为2步:
- 构建PendingArea树
- 构建Feature相关
- 构建Leaf相关
- 根据PendingArea树构建最终的DisplayAreas树,也就是层级树
通过2个类的名字也能感觉到一些关系,毕竟叫Pending。既然要先构造PendingPendingArea,那肯定需要先看看PendingArea这个数据结构
4.1 数据结构 PendingArea简介
1 | ini复制代码# DisplayAreaPolicyBuilder.PendingArea |
PendingArea后面还有一些方法,等后面会再次具体分析,当前只有PendingArea这个数据结构是什么样就好了。
4.2 构建PendingArea树
下面这段代码比较长我再代码里加了很多注释,其实这一块就是java的循环对数据结构的处理。就和刚学java的时候看2个for循环一样。
这个方法其实是构建整个树的方法,先看一眼,后面会再具体分析。
1 | ini复制代码# DisplayAreaPolicyBuilder.HierarchyBuilder |
4.2.1 构建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次全执行完后,层级树的图就是下面这个,不过做了下顺序的调整,从小到达排序
4.2.2 构建Leaf相关
在构建叶子节点的时候,又有一个新的东西,leafType,对应的就是叶子节点的类型,默认是LEAF_TYPE_TOKENS,一共也只定义了3个
1 | php复制代码# DisplayAreaPolicyBuilder.HierarchyBuilder |
然后是通过typeOfLayer方法根据当前层级返回type
1 | csharp复制代码# DisplayAreaPolicyBuilder.HierarchyBuilder |
逻辑还是比较简单的除了输入法(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,事实上也就是在那创建的,这个是和应用最相关的图层,
从代码上看也能证明:
1 | ini复制代码# DisplayAreaPolicyBuilder.HierarchyBuilder |
这段对应用图层的处理非常的重要了,特别最下面对parentPendingArea.mChildren再次添加DefaultTaskDisplayArea的操作
经过这个循环的处理,每个Feature下面都有了对应的叶子节点,如图:
第二层应用层目前是有2个孩子的,一个是Lead,另一个就是DefaultTaskDisplayArea。
到目前为止,层级树雏形是有了。但是比较还是一个PendingArea数组,另外 Leaf 0:1 这种目前在代码上也还没有得到体现。
4.3 真正DisplayAreas树 PendingArea::instantiateChildren
其实从PendingArea::instantiateChildren上面源码给的2个注释也知道,前面的2个循环,只是构建了一个PendingAreas树,接下来才是真正构建层级树(DisplayAreas)
并把这个树添加到root(DisplayContent)
1 | scss复制代码# DisplayAreaPolicyBuilder.PendingArea |
先解析一下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的成员变量和构造方法,现在看看
1 | typescript复制代码# DisplayAreaPolicyBuilder.PendingArea |
注意这里的参数areaForLayer这个是一个build方法创建的集合,也是最终层级树的体现。
- 方法前面mExisting.asTokens, 这个asTokens,方法定义在DisplayArea中默认返回null,只有DisplayArea.Tokens返回本身。 而ImeContainer是继承DisplayArea.Tokens的,所以有返回值。
而对于应用层mExisting是TaskDisplayArea,不是DisplayArea.Tokens的子类,所以这个不满足,也就是说只有IME的PendingArea才会执行下面fillAreaForLayers的逻辑
1 | ini复制代码# DisplayAreaPolicyBuilder.PendingArea |
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的定义了
1 | arduino复制代码# DisplayAreaPolicyBuilder |
mNewDisplayAreaSupplier这个对象的赋值是在Feature的构造方法,而根据代码分析,添加的5个Feature是通过Builder的方式,所以我们现在分析的
mNewDisplayAreaSupplier的值,就是定义在Feature.Builder下的默认值也就是DisplayArea对象
所以这一步就是返回了一个DisplayArea对象,然后name就是 “mFeature.mName + “:” + mMinLayer + “:” + mMaxLayer” 比如 “HideDisplayCutout:32:35”
到现在为止,层级树每个成员是如何构建,以及里面的字符串名字是怎么来的,就全都清楚了。
后面迭代也只是方法的递归而已,经过一层一层的迭代后,整个层级结构树就构建好了。
现在的层级树如下:
这个层级树和上一篇看 不太一样那是因为Leaf下没有内容了,应用层“DefaultTask
DisplayArea”和壁纸层也没有内容,那是因为Leaf后面的内容都是具体业务添加上去的。
所以其实对应Window的add流程,其实也就是真没添加到这个层级树的流程。后面具体分析业务的时候肯定是会有具体案例的。
窗口层级树这一块的代码有点抽象,代码虽然不多但是也挺绕。我写的也水平有限,学习这块最好是自己也能跟着画出一个层级树的图来。当然就算画不了,也问题不大,再怎么不济现在对层级树的概念肯定也是有了解的,也知道怎么命令看,以后实际业务经常会需要比对层级树的变化,看到多了,自如而且就清除了。虽然层级树打印的内容比较多,但是只要关注DefaultTaskDisplayArea下的内容,这一块的内容也就那么点。
本文转载自: 掘金