Android 窗口容器树(一)—— 窗口和窗口容器树

窗口容器树系列文章:

  1. Android 窗口容器树(一)------ 窗口和窗口容器树
  2. Android 窗口容器树(二)------ 窗口容器树的构建

1. 什么是窗口?

在 Android 中,窗口不是 View,也不是某个单独的界面控件,而是系统层面对一块可显示 UI 内容的抽象管理单元

它至少有 3 个核心特征:

  1. 它有独立的 WindowManager.LayoutParams,用来描述类型、位置、大小、标志位等。
  2. 它最终会对应到 SurfaceControl / Surface,并交给 SurfaceFlinger 做最终合成。
  3. 它受 WindowManagerServiceWMS)统一管理,参与布局、输入、焦点、可见性等。

常见窗口包括:

  • Activity 主窗口
  • Dialog
  • PopupWindow
  • 状态栏
  • 导航栏
  • 输入法窗口
  • 壁纸窗口

窗口之所以会互相遮挡,本质上是因为它们有前后顺序,也就是常说的 Z-Order。在 WMSWindowContainer 源码里,子节点列表本身就是按 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 下面,主窗口通常在前面,后弹出的 DialogPanel 往往会排到更靠后的位置,所以会盖在主窗口上面。

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 第一棵树:逻辑窗口容器树

第一棵树是 WMSWindowContainer 管理树。本文后面提到"窗口容器树""窗口管理树",默认都指这棵树。

WindowContainer 管理树上的节点大多是 WindowContainer 及其子类,例如:

  • RootWindowContainer
  • DisplayContent
  • DisplayArea
  • TaskDisplayArea
  • Task
  • TaskFragment
  • ActivityRecord
  • WindowToken
  • WindowState

WindowContainer 管理树管的是"窗口对象之间的管理关系",比如:

  • 谁是谁的父节点
  • 谁在谁上面
  • 配置怎么往下传
  • 可见性怎么往下传播
  • 动画和转场作用在哪一整组节点上

所以 WindowContainer 管理树的本质是:

它描述的是"谁管理谁",不是"屏幕怎么切",也不是"最终怎么显示"。

2.2 第二棵树:DisplayArea 切分树

第二棵树是 DisplayContent 内部的 DisplayArea 分区树。它做的事情很单纯,就是把一整块屏幕按层级语义和功能语义切成不同区域。

它主要解决的是:

  1. 按 layer 把屏幕切开
  2. 按功能把屏幕切开
  3. 给不同类型的窗口准备不同的挂载区域

比如在 Android 14 默认策略里,DisplayAreaPolicy 会构建出:

  • 应用任务所在的 TaskDisplayArea
  • 非应用窗口所在的 Tokens 区域
  • IME 相关区域
  • 放大镜、单手模式、刘海避让这类 feature 区域

所以 DisplayArea 树本质上描述的是:

屏幕被切成了哪些区,每一类窗口该落到哪个区。

它和第一棵树的本质区别是:

  • WindowContainer 树讲的是对象之间的管理关系
  • DisplayArea 树讲的是屏幕内部的区域划分关系

前者是在"管对象",后者是在"分地盘"。

2.3 第三棵树:SurfaceControl / SurfaceFlinger 合成树

第三棵树是 SurfaceControl / SurfaceFlinger 显示树。到了这一层,前面那些逻辑关系和区域划分,才会变成真正能显示到屏幕上的图层关系。

它关心的不是"谁管理谁",也不是"窗口该落哪个区",而是:

  • 最终画面怎么叠加
  • 图层前后顺序怎么生效
  • 位置、裁剪、透明度、缩放怎么作用到屏幕
  • 动画 leash 怎么临时接管某一组内容

WindowContainer 树上的节点,最终想显示出来,必须继续走下面这条路径:

  1. 创建或复用 SurfaceControl
  2. 通过 SurfaceControl.Transaction 提交 layer、位置、裁剪、透明度、变换矩阵等状态
  3. 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 个核心事实:

  1. 整个设备最上层是 RootWindowContainer
  2. 每一块实际屏幕在这棵管理树里对应一个 DisplayContent
  3. 到了 DisplayContent 内部以后,这棵管理树会分成两大主路径:
    • 应用窗口路径:TaskDisplayArea -> Task -> TaskFragment -> ActivityRecord -> WindowState
    • 非应用窗口路径:DisplayArea.Tokens -> WindowToken -> WindowState

3.2 分叉方式

源码里 DisplayArea.TypeDisplayArea / Task 这条管理链大致分成 3 类:

  • BELOW_TASKS
  • ANY
  • ABOVE_TASKS

它们在源码里的含义可以直接对应为:

  • BELOW_TASKS:应用任务层之下的系统窗口区域
  • ANY:既能继续包含其他 DisplayArea,也能包含 WindowTokenTask 容器
  • ABOVE_TASKS:应用任务层之上的系统窗口区域

这里最容易误解的是:ANY 不是图里额外长出来的一条单独分支,而是一种"这个区域可以装什么"的类型标记。

例如这张图里:

  • RootDisplayAreaANY
  • TaskDisplayArea 也是 ANY
  • Task 在源码的 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 个核心能力:

  1. mParent / mChildren 维护父子关系
  2. mChildren 维护兄弟节点顺序
  3. 向下传播配置、可见性和动画状态
  4. 作为逻辑容器去绑定 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
  • 维护焦点屏幕、睡眠状态、旋转等全局状态
  • 作为 ATMWMS 协调显示行为的顶层入口

一句话说,RootWindowContainer 管的是"整台设备有多少块屏,每块屏的窗口体系怎么组织"。

4.3 DisplayContent:单块屏幕的根节点

源码定义:

java 复制代码
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
}

DisplayContent 表示一块实际屏幕。到了这一层,分析对象就从"整台设备"变成了"某一块具体屏幕"。

它在这棵管理树里主要做两件事:

  1. 作为单屏窗口体系的根节点
  2. 管理单屏显示相关状态,例如焦点窗口、输入法目标、壁纸目标、转场和显示参数

同时它还维护 mTokenMap,把 IBinderWindowToken 关联起来,并持有 mWindowingLayermOverlayLayermInputOverlayLayer 等合成层入口。

所以 DisplayContent 既是屏幕根节点,也是单屏逻辑树和显示树的连接点。

4.4 RootDisplayAreaDisplayArea:先分区,再决定挂载路径

源码定义:

java 复制代码
public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
}

到了 DisplayContent 下面,不会立刻挂具体窗口,而是先由 RootDisplayArea / DisplayArea 这一层决定"这个窗口应该落到哪一块区域"。

这里说的作用,指的是 RootDisplayArea / DisplayArea 这一层在整棵管理树里的作用:

  1. 按 layer 分区
  2. 按 feature 分区
  3. 为不同窗口类型准备稳定挂载点

也就是说,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

应用窗口的主窗口、启动窗口、DialogPanel、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 件事:

  1. 标识这组窗口属于谁
  2. 决定这组窗口落在哪个区域和层级
  3. 作为权限校验和生命周期管理的单位
  4. 作为 WindowState 的直接父容器

对非应用窗口来说,WindowToken 直接承载这一组窗口;对应用窗口来说,这套能力被 ActivityRecord 继承过去了。

所以正确理解应该是:

WindowToken 是窗口分组机制;应用窗口用 ActivityRecord 继承这套机制,非应用窗口直接使用这套机制。

4.8 WindowState:树的最底层窗口实例

源码定义:

java 复制代码
class WindowState extends WindowContainer<WindowState>
        implements WindowManagerPolicy.WindowState, InsetsControlTarget, InputTarget {
}

WindowState 才是真正具体的窗口实例。比如一个 Activity 的主窗口、一个启动窗口、一个状态栏窗口,落到 WMS 里最终都会对应一个 WindowState。它持有窗口运行时最关键的信息,例如:

  • mAttrs
  • mToken
  • mActivityRecord
  • mBaseLayer
  • mSubLayer
  • mFrame
  • mSurfaceControl

它通常是容器树最底部的节点,但不一定是绝对叶子节点。比如 Activity 主窗口通常已经在路径末端了,但如果这个主窗口上又弹出了 DialogPanel,这个主窗口下面还会继续挂子窗口。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 显示到屏幕上时,最终会落到所属 TaskActivityRecord 下面,主窗口类型通常是 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

  1. 启动窗口:先显示,目的是尽快给用户一个可见界面
  2. 主窗口:应用真正绘制完成后接管显示

可以把它理解成:启动窗口是"占位过渡窗口",主窗口才是"应用真正的内容窗口"。

5.3 Dialog

如果是应用内部弹出的 Dialog,一般仍然属于同一个 ActivityRecord

text 复制代码
Task
`-- ActivityRecord
    |-- WindowState (TYPE_BASE_APPLICATION)
    `-- WindowState (TYPE_APPLICATION / TYPE_APPLICATION_ATTACHED_DIALOG)

这里的两个 WindowState 在这张图里可以先按"同属一个 ActivityRecord 的两个子窗口"来理解,也就是它们同属于一个应用窗口分支。

要注意两点:

  1. 这行 TYPE_APPLICATION / TYPE_APPLICATION_ATTACHED_DIALOG 说明 Dialog 常见有两种类型:
    • TYPE_APPLICATION
    • TYPE_APPLICATION_ATTACHED_DIALOG
  2. 如果是 attached dialog,它在更细的实际结构里还可能继续挂到某个父 WindowState 下面,而不只是简单地和主窗口并列

这说明 Dialog 并没有脱离当前 Activity 单独成树,而是仍然挂在同一个应用窗口分支里,只是窗口类型和挂载细节可能不同。

这类 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 高低不同。

相关推荐
HUGu RGIN2 小时前
MySQL--》如何在MySQL中打造高效优化索引
android·mysql·adb
Joseph Cooper4 小时前
Linux/Android 跟踪技术:ftrace、TRACE_EVENT、atrace、systrace 与 perfetto 入门
android·linux·运维
空中海5 小时前
安卓逆向03. 动态调试、抓包分析与 Frida Hook
android
一起搞IT吧6 小时前
相机Camera日志实例分析之二十:相机Camx【照片后置4800/5000/6400万拍照】单帧流程日志详解
android·嵌入式硬件·数码相机·智能手机
jinanwuhuaguo7 小时前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
千码君20169 小时前
Trae:一些关于flutter和 go前后端开发构建的分享
android·flutter·gradle·android-studio·trae·vibe code
重生之我是Java开发战士12 小时前
【MySQL】事务 & 用户与权限管理
android·数据库·mysql
怣疯knight14 小时前
Windows不安装 Android Studio如何打包安卓软件
android·windows·android studio
ke_csdn14 小时前
从Java演变到Kotlin下的jet pack
android