窗口容器树系列文章:
- Android 窗口容器树(一)------ 窗口和窗口容器树
- Android 窗口容器树(二)------ 窗口容器树的构建
1. 什么是窗口?
在 Android 中,窗口不是 View,也不是某个单独的界面控件,而是系统层面对一块可显示 UI 内容的抽象管理单元。
它至少有 3 个核心特征:
- 它有独立的
WindowManager.LayoutParams,用来描述类型、位置、大小、标志位等。 - 它最终会对应到
SurfaceControl/Surface,并交给SurfaceFlinger做最终合成。 - 它受
WindowManagerService(WMS)统一管理,参与布局、输入、焦点、可见性等。
常见窗口包括:
Activity主窗口DialogPopupWindow- 状态栏
- 导航栏
- 输入法窗口
- 壁纸窗口
窗口之所以会互相遮挡,本质上是因为它们有前后顺序,也就是常说的 Z-Order。在 WMS 的 WindowContainer 源码里,子节点列表本身就是按 Z-Order 保存的:
java
// List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
这句的意思是:同一个父容器下,mChildren 越靠后,视觉上越靠前。比如同一个 ActivityRecord 下面,主窗口通常在前面,后弹出的 Dialog、Panel 往往会排到更靠后的位置,所以会盖在主窗口上面。
37 层窗口
37 层 指的是 WindowManagerPolicy 定义的窗口策略 layer 编号范围。
源码中 getMaxWindowLayer() 返回 36,所以 layer 取值是 0 ~ 36,一共 37 个槽位。
系统会通过 getWindowLayerFromTypeLw() 先把不同 window type 映射到这 37 个策略 layer 中的某一个,再在 Android 14 中继续用它做 DisplayArea 分区,并作为 WindowState 计算基础显示层的输入。因此,37 层 说的是窗口类型的策略分层,不是最终 SurfaceFlinger 看到的真实显示图层。

2. 不同维度的 3 棵树
先把下面 3 个层次的树区分开:
2.1 第一棵树:逻辑窗口容器树
第一棵树是 WMS 的 WindowContainer 管理树。本文后面提到"窗口容器树""窗口管理树",默认都指这棵树。
WindowContainer 管理树上的节点大多是 WindowContainer 及其子类,例如:
RootWindowContainerDisplayContentDisplayAreaTaskDisplayAreaTaskTaskFragmentActivityRecordWindowTokenWindowState
WindowContainer 管理树管的是"窗口对象之间的管理关系",比如:
- 谁是谁的父节点
- 谁在谁上面
- 配置怎么往下传
- 可见性怎么往下传播
- 动画和转场作用在哪一整组节点上
所以 WindowContainer 管理树的本质是:
它描述的是"谁管理谁",不是"屏幕怎么切",也不是"最终怎么显示"。
2.2 第二棵树:DisplayArea 切分树
第二棵树是 DisplayContent 内部的 DisplayArea 分区树。它做的事情很单纯,就是把一整块屏幕按层级语义和功能语义切成不同区域。
它主要解决的是:
- 按 layer 把屏幕切开
- 按功能把屏幕切开
- 给不同类型的窗口准备不同的挂载区域
比如在 Android 14 默认策略里,DisplayAreaPolicy 会构建出:
- 应用任务所在的
TaskDisplayArea - 非应用窗口所在的
Tokens区域 IME相关区域- 放大镜、单手模式、刘海避让这类 feature 区域
所以 DisplayArea 树本质上描述的是:
屏幕被切成了哪些区,每一类窗口该落到哪个区。
它和第一棵树的本质区别是:
WindowContainer树讲的是对象之间的管理关系DisplayArea树讲的是屏幕内部的区域划分关系
前者是在"管对象",后者是在"分地盘"。
2.3 第三棵树:SurfaceControl / SurfaceFlinger 合成树
第三棵树是 SurfaceControl / SurfaceFlinger 显示树。到了这一层,前面那些逻辑关系和区域划分,才会变成真正能显示到屏幕上的图层关系。
它关心的不是"谁管理谁",也不是"窗口该落哪个区",而是:
- 最终画面怎么叠加
- 图层前后顺序怎么生效
- 位置、裁剪、透明度、缩放怎么作用到屏幕
- 动画 leash 怎么临时接管某一组内容
WindowContainer 树上的节点,最终想显示出来,必须继续走下面这条路径:
- 创建或复用
SurfaceControl - 通过
SurfaceControl.Transaction提交 layer、位置、裁剪、透明度、变换矩阵等状态 - 由
SurfaceFlinger在合成阶段把所有Surface叠加到一起
所以 SurfaceControl / SurfaceFlinger 显示树的本质是:
它描述的是"最终屏幕上怎么显示"。
它和前两棵树的本质区别是:
- 第一棵树
WindowContainer树:讲管理关系 - 第二棵树
DisplayArea树:讲分区关系 - 第三棵树
SurfaceControl/SurfaceFlinger树:讲显示关系
把三棵树连起来看,就是:
先在
WindowContainer树里确定管理关系,再在DisplayArea树里确定落区,最后在SurfaceControl/SurfaceFlinger这一层确定真正的显示结果。
2.4 三棵树之间到底是什么关系?
可以把它们串成一条链:
text
window type
-> policy layer
-> DisplayArea 分区
-> WindowContainer 树中的挂载位置
-> SurfaceControl 树中的实际 layer 和父子关系
-> SurfaceFlinger 最终合成
因此,最容易记住的结论是:
WindowContainer树负责"管理",DisplayArea树负责"分区",Surface树负责"显示"。
三者强相关,但绝对不是同一棵树。
3. 窗口容器树
在讲每一个类之前,先从全局看一遍 Android 14 中最常见的单屏 WindowContainer 管理树结构。下面这张图画的是 WMS 用来管理窗口对象的那棵树,不是 DisplayArea 分区树,也不是 SurfaceFlinger 的最终显示树。
3.1 简化总图
text
RootWindowContainer
`-- DisplayContent
`-- RootDisplayArea (ANY)
|-- DisplayArea (BELOW_TASKS)
| `-- DisplayArea.Tokens
| `-- WindowToken
| `-- WindowState
|
|-- TaskDisplayArea (ANY)
| `-- Task (RootTask,类型仍按 ANY 容器链处理)
| `-- Task / TaskFragment
| `-- ActivityRecord
| |-- WindowState (TYPE_BASE_APPLICATION)
| |-- WindowState (TYPE_APPLICATION_STARTING)
| `-- WindowState (Dialog / Panel / attached window)
|
`-- DisplayArea (ABOVE_TASKS)
|-- DisplayArea.Tokens
| `-- WindowToken
| `-- WindowState
`-- ImeContainer / IME 对应区域
`-- WindowToken
`-- WindowState
这张 WindowContainer 管理树总图想表达 3 个核心事实:
- 整个设备最上层是
RootWindowContainer - 每一块实际屏幕在这棵管理树里对应一个
DisplayContent - 到了
DisplayContent内部以后,这棵管理树会分成两大主路径:- 应用窗口路径:
TaskDisplayArea -> Task -> TaskFragment -> ActivityRecord -> WindowState - 非应用窗口路径:
DisplayArea.Tokens -> WindowToken -> WindowState
- 应用窗口路径:
3.2 分叉方式
源码里 DisplayArea.Type 把 DisplayArea / Task 这条管理链大致分成 3 类:
BELOW_TASKSANYABOVE_TASKS
它们在源码里的含义可以直接对应为:
BELOW_TASKS:应用任务层之下的系统窗口区域ANY:既能继续包含其他DisplayArea,也能包含WindowToken或Task容器ABOVE_TASKS:应用任务层之上的系统窗口区域
这里最容易误解的是:ANY 不是图里额外长出来的一条单独分支,而是一种"这个区域可以装什么"的类型标记。
例如这张图里:
RootDisplayArea是ANYTaskDisplayArea也是ANYTask在源码的DisplayArea.Type.typeOf()里也按ANY处理
所以 TaskDisplayArea -> RootTask -> Task / TaskFragment 这条应用任务链,本身就是落在 ANY 这类容器能力里的。
而 TaskDisplayArea 本身就是专门放应用任务的区域,所以在 WindowContainer 管理树里,应用窗口不会直接掉进普通 Tokens 区,而是单独走应用任务容器链。
3.3 易错点
ActivityRecord 在 Android 14 中直接继承 WindowToken
这意味着应用窗口的 token 和 ActivityRecord 是同一个对象,不是"ActivityRecord 下再套一个独立 WindowToken"。
应用窗口不直接挂在 DisplayArea.Tokens
DisplayArea.Tokens 主要承载的是非应用类 WindowToken,应用窗口要先进入任务体系,再进入 ActivityRecord,最后才到 WindowState。
4. 从根到叶,节点详解
先抓住主线:
text
WindowContainer
-> RootWindowContainer
-> DisplayContent
-> RootDisplayArea / DisplayArea
-> 应用分支或非应用分支
-> WindowState
这一节只讲每一层"在树里负责什么",不再展开无关旁支。
4.1 WindowContainer:所有节点的公共基类
源码定义:
java
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
InsetsControlTarget {
private WindowContainer<WindowContainer> mParent = null;
protected final WindowList<E> mChildren = new WindowList<E>();
}
WindowContainer 是整棵管理树的基础设施,所有后续节点都共享它的 4 个核心能力:
- 用
mParent/mChildren维护父子关系 - 用
mChildren维护兄弟节点顺序 - 向下传播配置、可见性和动画状态
- 作为逻辑容器去绑定
SurfaceControl
其中最关键的是 mChildren。源码注释写得很明确:
java
// List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
也就是说,mChildren 本身就是按 Z-Order 排好的,列表尾部的子节点在视觉上最靠前。WindowContainer 维护层级顺序,主要靠 addChild(...) 和 positionChildAt(...) 这类方法完成。
4.2 RootWindowContainer:设备级根节点
源码定义:
java
class RootWindowContainer extends WindowContainer<DisplayContent>
implements DisplayManager.DisplayListener {
}
RootWindowContainer 是整台设备的根节点,它的直接子节点不是窗口,而是一块块屏幕对应的 DisplayContent。
所以它负责的是设备级问题:
- 管理所有
DisplayContent - 维护焦点屏幕、睡眠状态、旋转等全局状态
- 作为
ATM和WMS协调显示行为的顶层入口
一句话说,RootWindowContainer 管的是"整台设备有多少块屏,每块屏的窗口体系怎么组织"。
4.3 DisplayContent:单块屏幕的根节点
源码定义:
java
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
}
DisplayContent 表示一块实际屏幕。到了这一层,分析对象就从"整台设备"变成了"某一块具体屏幕"。
它在这棵管理树里主要做两件事:
- 作为单屏窗口体系的根节点
- 管理单屏显示相关状态,例如焦点窗口、输入法目标、壁纸目标、转场和显示参数
同时它还维护 mTokenMap,把 IBinder 和 WindowToken 关联起来,并持有 mWindowingLayer、mOverlayLayer、mInputOverlayLayer 等合成层入口。
所以 DisplayContent 既是屏幕根节点,也是单屏逻辑树和显示树的连接点。
4.4 RootDisplayArea 和 DisplayArea:先分区,再决定挂载路径
源码定义:
java
public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
}
到了 DisplayContent 下面,不会立刻挂具体窗口,而是先由 RootDisplayArea / DisplayArea 这一层决定"这个窗口应该落到哪一块区域"。
这里说的作用,指的是 RootDisplayArea / DisplayArea 这一层在整棵管理树里的作用:
- 按 layer 分区
- 按 feature 分区
- 为不同窗口类型准备稳定挂载点
也就是说,DisplayContent 先把屏幕交给 RootDisplayArea 管,RootDisplayArea 再根据窗口类型和 layer,把窗口分流到不同的 DisplayArea / Tokens / TaskDisplayArea 区域。
这里最关键的源码出自 RootDisplayArea 查找窗口落点的方法,也就是 findAreaForTokenInLayer() 最终调用的按 windowType 找区域逻辑:
java
if (windowLayerFromType == APPLICATION_LAYER) {
throw new IllegalArgumentException(
"There shouldn't be WindowToken on APPLICATION_LAYER");
}
这句的结论就是:普通 WindowToken 不能落在 APPLICATION_LAYER。所以从 DisplayArea 往下会分两条路:
- 应用窗口:走任务体系
- 非应用窗口:走
Tokens体系
4.5 应用窗口路径:TaskDisplayArea -> Task / TaskFragment -> ActivityRecord
应用窗口路径可以先记成:
text
TaskDisplayArea
`-- Task
`-- TaskFragment
`-- ActivityRecord
`-- WindowState
这条路径里每一层负责的事情分别是:
TaskDisplayArea- 应用任务进入这块屏幕后的入口区域
- 负责根任务顺序、焦点任务、启动落点
Task- 真正承载任务实体
- 负责这一组
Activity属于哪个任务
TaskFragment- 任务内部的再切分容器
- 大屏、分栏、嵌入式布局都依赖它
ActivityRecord- 应用窗口分支的关键节点
- 负责把任务容器切换到具体窗口容器
其中最重要的是 ActivityRecord。Android 14 源码定义:
java
final class ActivityRecord extends WindowToken
implements WindowManagerService.AppFreezeListener {
}
这意味着:
ActivityRecord本身就是应用窗口对应的WindowToken。
应用窗口的主窗口、启动窗口、Dialog、Panel、attached window,都会挂到这个 ActivityRecord 下面。
4.6 非应用窗口路径:DisplayArea.Tokens -> WindowToken
非应用窗口路径更短:
text
DisplayArea.Tokens
`-- WindowToken
`-- WindowState
入口节点是 DisplayArea.Tokens:
java
public static class Tokens extends DisplayArea<WindowToken> {
}
它承载的是非应用类窗口,例如:
- 状态栏
- 壁纸
- 输入法
- 系统弹窗
- 辅助功能覆盖层
这些窗口不会进入 Task / TaskFragment / ActivityRecord,而是直接进入 WindowToken 分组。
4.7 WindowToken:窗口分组机制
源码定义:
java
class WindowToken extends WindowContainer<WindowState> {
final IBinder token;
final int windowType;
}
WindowToken 的核心作用就是"把一组窗口组织起来"。
它负责 4 件事:
- 标识这组窗口属于谁
- 决定这组窗口落在哪个区域和层级
- 作为权限校验和生命周期管理的单位
- 作为
WindowState的直接父容器
对非应用窗口来说,WindowToken 直接承载这一组窗口;对应用窗口来说,这套能力被 ActivityRecord 继承过去了。
所以正确理解应该是:
WindowToken是窗口分组机制;应用窗口用ActivityRecord继承这套机制,非应用窗口直接使用这套机制。
4.8 WindowState:树的最底层窗口实例
源码定义:
java
class WindowState extends WindowContainer<WindowState>
implements WindowManagerPolicy.WindowState, InsetsControlTarget, InputTarget {
}
WindowState 才是真正具体的窗口实例。比如一个 Activity 的主窗口、一个启动窗口、一个状态栏窗口,落到 WMS 里最终都会对应一个 WindowState。它持有窗口运行时最关键的信息,例如:
mAttrsmTokenmActivityRecordmBaseLayermSubLayermFramemSurfaceControl
它通常是容器树最底部的节点,但不一定是绝对叶子节点。比如 Activity 主窗口通常已经在路径末端了,但如果这个主窗口上又弹出了 Dialog 或 Panel,这个主窗口下面还会继续挂子窗口。attached window 场景下,源码会执行:
java
parentWindow.addChild(this, sWindowSubLayerComparator);
这说明 WindowState 下面还可以继续挂子 WindowState。例如一个 Activity 主窗口下面,可以继续挂 TYPE_APPLICATION_PANEL 的面板窗口,或者 TYPE_APPLICATION_ATTACHED_DIALOG 的附着对话框。
因此,WindowState 最准确的定义是:
WMS管理的最小窗口实例,也是最接近真实显示和输入分发的节点。比如点击屏幕时,输入系统最终要找到具体的WindowState;窗口真正显示到屏幕上时,WMS也是通过它去关联SurfaceControl和窗口属性。
5. 实际场景
5.1 普通 Activity 主窗口
text
RootWindowContainer
`-- DisplayContent
`-- TaskDisplayArea
`-- Task
`-- ActivityRecord
`-- WindowState (TYPE_BASE_APPLICATION)
这是最基础的应用路径。一个普通 Activity 显示到屏幕上时,最终会落到所属 Task 的 ActivityRecord 下面,主窗口类型通常是 TYPE_BASE_APPLICATION。
5.2 应用启动窗口
text
Task
`-- ActivityRecord
|-- WindowState (TYPE_APPLICATION_STARTING)
`-- WindowState (TYPE_BASE_APPLICATION)
这里最容易误解的是:启动窗口和主窗口不是同一个窗口。
5.1里的TYPE_BASE_APPLICATION是应用真正的主窗口- 这里的
TYPE_APPLICATION_STARTING是系统为了冷启动过渡临时加上的启动窗口
所以在冷启动早期,同一个 ActivityRecord 下面可能短时间同时存在两个 WindowState:
- 启动窗口:先显示,目的是尽快给用户一个可见界面
- 主窗口:应用真正绘制完成后接管显示
可以把它理解成:启动窗口是"占位过渡窗口",主窗口才是"应用真正的内容窗口"。
5.3 Dialog
如果是应用内部弹出的 Dialog,一般仍然属于同一个 ActivityRecord:
text
Task
`-- ActivityRecord
|-- WindowState (TYPE_BASE_APPLICATION)
`-- WindowState (TYPE_APPLICATION / TYPE_APPLICATION_ATTACHED_DIALOG)
这里的两个 WindowState 在这张图里可以先按"同属一个 ActivityRecord 的两个子窗口"来理解,也就是它们同属于一个应用窗口分支。
要注意两点:
- 这行
TYPE_APPLICATION / TYPE_APPLICATION_ATTACHED_DIALOG说明Dialog常见有两种类型:TYPE_APPLICATIONTYPE_APPLICATION_ATTACHED_DIALOG
- 如果是 attached dialog,它在更细的实际结构里还可能继续挂到某个父
WindowState下面,而不只是简单地和主窗口并列
这说明 Dialog 并没有脱离当前 Activity 单独成树,而是仍然挂在同一个应用窗口分支里,只是窗口类型和挂载细节可能不同。
5.4 PopupWindow / Panel
这类 attached window 往往继续挂在某个父 WindowState 下:
text
ActivityRecord
`-- WindowState (主窗口)
`-- WindowState (TYPE_APPLICATION_PANEL)
它和上一节 Dialog 的区别,重点不在"是不是弹出来",而在"是不是附着在父 WindowState 上"。
Dialog:常见理解是仍然属于同一个ActivityRecord,有些场景下可以近似看成和主窗口同属一个应用窗口分支PopupWindow/Panel:更典型的是 attached window,会直接挂在某个父WindowState下面
所以这类窗口最典型的特点不是"和主窗口平级",而是"附着在某个父窗口下面"。因此它们除了受 ActivityRecord 管,还会继续受父 WindowState 的位置和 sub-layer 影响。
5.5 输入法窗口
text
DisplayContent
`-- DisplayArea.Tokens / IME 所在区域
`-- WindowToken (TYPE_INPUT_METHOD)
`-- WindowState
输入法不是应用任务的一部分,而是非应用窗口。它走的是 DisplayArea.Tokens -> WindowToken -> WindowState 这条路径,只是它会被放进专门的 IME 区域。
5.6 状态栏窗口
text
DisplayContent
`-- DisplayArea.Tokens
`-- WindowToken (TYPE_STATUS_BAR)
`-- WindowState
状态栏和输入法一样,也不属于任何 Task。它直接挂在非应用窗口分支,是一个典型的系统窗口场景。
5.7 壁纸窗口
text
DisplayContent
`-- DisplayArea.Tokens
`-- WindowToken (TYPE_WALLPAPER)
`-- WindowState
壁纸窗口也是非应用窗口,但它的层级通常更低,所以经常处在应用窗口下面,作为整个界面的背景层。
5.8 分屏场景下的两个应用任务
text
RootWindowContainer
`-- DisplayContent
`-- TaskDisplayArea
|-- Task
| `-- ActivityRecord
| `-- WindowState (左侧或上侧应用)
`-- Task
`-- ActivityRecord
`-- WindowState (右侧或下侧应用)
分屏场景不是多出一套新的窗口体系,而是在同一个 TaskDisplayArea 下同时存在多个 Task。这时要重点看的是 TaskDisplayArea 内部的任务顺序、焦点任务和分屏边界。
5.9 系统对话框或系统覆盖层
text
DisplayContent
`-- DisplayArea.Tokens
`-- WindowToken (TYPE_SYSTEM_DIALOG / TYPE_APPLICATION_OVERLAY ...)
`-- WindowState
像系统对话框、悬浮窗、某些辅助功能覆盖层,也都走非应用路径。区别不在于树形结构,而在于 window type、权限校验和最终 layer 高低不同。