Task、Root Task 与 WindowContainer 如何描述多窗口层级
Task、Root Task 与 WindowContainer 如何描述多窗口层级
分屏不是把两个 Activity 的 View 合并到一个 View 树。system_server 会重组 WindowContainer 的父子关系、bounds 和层级,再让对应 Surface 按视觉事务变化。本文以 AOSP Android 14 android-14.0.0_r75 为基准,只跟踪显示区域中的容器归属及其 Surface 映射。
问题场景
“一个 Task 是不是一个 Activity?”“Root Task 是不是屏幕根节点?”这两个误解会把分屏看成两个应用互相缩放。实际的根节点是显示器对应的容器,Root Task 是某个 TaskDisplayArea 下的顶层 Task;一个 Task 可以再包含子 Task 或 Activity。分屏时,系统改变的是这棵树中相关 Task 的父子关系、窗口模式和边界。
核心结论
WindowContainer是 WMS/ATMS 在system_server维护的通用层级节点;DisplayContent、TaskDisplayArea、Task、ActivityRecord和WindowState都在这条树上承担不同职责。DisplayContent表示一块显示器,TaskDisplayArea是其中容纳应用窗口容器的区域;其直接子节点可以是Task或嵌套的TaskDisplayArea。- Root Task 是
TaskDisplayArea下的顶层 Task,不等于 Display 根节点,也不等于某个 Activity。分屏会组织出用于分屏的 Task 层次及两侧的子树,而不是合并应用 View。 - 逻辑容器与合成对象相关但不等同:
WindowContainer创建/重挂自己的SurfaceControl;交给TaskOrganizer的 leash 是以 Task 的SurfaceControl构造的可操作句柄。
核心对象
| 对象 | 所在进程/层 | 职责 | 关系 |
|---|---|---|---|
DisplayContent | system_server,显示层 | 表示一个显示器的窗口层级 | 取得默认 TaskDisplayArea |
TaskDisplayArea | system_server,应用区域 | 排列 Root Task | 直接容纳 Task/嵌套区域 |
Task | system_server,任务层 | 保存任务配置、边界和子层级 | 可为 Root Task,也可包含 Activity/子 Task |
ActivityRecord | system_server,Activity 记录 | 关联 Activity 到 Task | 子窗口归入该记录 |
WindowState | system_server,窗口层 | 对应一个实际 Window | 经 ActivityRecord 回到所属 Task |
TaskOrganizer | Shell/特权组织者进程 | 收到 Task 信息与 leash,提交 Surface 事务 | 通过 Binder 与 TaskOrganizerController 协作 |
先观察“父子归属”和“跨进程控制”两条线:前者在 system_server 的全局锁保护下维护;后者仅在组织者注册/回调时跨 Binder,客户端回调由其 Executor 执行。
图是分屏组织形态的概念图,不表示每台设备都打印相同中间节点。关键点是两侧仍是不同的 Task 子树;Binder 传出的 leash 让组织者控制 Surface,不能倒推为“逻辑树被复制到 Shell”。
主流程
全屏与分屏:同一棵树的不同组织
| 场景 | 应用窗口的典型向上路径 | 主要差异 |
|---|---|---|
| 全屏 | WindowState → ActivityRecord → Task → TaskDisplayArea → DisplayContent | Task 的配置/边界覆盖可用显示区域。 |
| 分屏 | WindowState → ActivityRecord → 侧边 Task 子树 → 分屏相关 Root Task → TaskDisplayArea → DisplayContent | 两侧 Task 子树在同一显示区域内按各自边界布局;具体中间 Task 和名称取决于 Shell 与设备实现。 |
不要把表中的箭头理解为 Java 调用栈;它们是 WindowContainer 的逻辑归属。Surface 侧通常也有父子关系,但动画时可能增加 leash,且控制权可以被交给组织者。
关键源码
以下节点均已对照 AOSP android-14.0.0_r75。除特别说明外,容器读写发生在 system_server 持有 WindowManagerGlobalLock 的代码路径;这不是“固定某一个 Handler 线程”的承诺。TaskOrganizer 回调进入客户端 Binder 线程后,再由其 Executor 调度。
源码节点:容器父子关系映射到 Surface 父子关系
- 类:
WindowContainer<E extends WindowContainer> - 方法:
makeChildSurface(WindowContainer) - 路径:
frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java - 进程:
system_server - 线程:持有 WMS 全局锁的调用路径
- 作用:向父容器递归请求 Builder,并把当前容器的
mSurfaceControl设为 Surface 父节点。
阅读问题:逻辑父容器如何影响新建 Surface 的父节点?
Builder makeSurface() {
final WindowContainer p = getParent();
return p.makeChildSurface(this);
}
Builder makeChildSurface(WindowContainer child) {
final WindowContainer p = getParent();
return p.makeChildSurface(child)
.setParent(mSurfaceControl);
}结论:这段代码支撑“容器重组会影响合成层级”,但它没有把 Task 等同于某个 Activity View;每个容器是否有自己的 Surface、是否处于动画 leash 下,需要继续看当前对象状态。
源码节点:显示器找到应用窗口区域
- 类:
DisplayContent - 方法:
getDefaultTaskDisplayArea() - 路径:
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java - 进程:
system_server - 线程:持有 WMS 全局锁的调用路径
- 作用:从显示区域策略取得默认应用任务区域。
阅读问题:一块显示器如何进入可放置应用 Task 的区域?
/**
* Get the default display area on the display dedicated to app windows.
*/
TaskDisplayArea getDefaultTaskDisplayArea() {
return mDisplayAreaPolicy.getDefaultTaskDisplayArea();
}结论:DisplayContent 是显示层入口,默认 TaskDisplayArea 是后续创建/查找应用 Root Task 的落点;多显示器或特性区域不能简单假定都使用它。
源码节点:TaskDisplayArea 创建 Root Task
- 类:
TaskDisplayArea - 方法:
createRootTask(int, int, boolean, ActivityOptions) - 路径:
frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java - 进程:
system_server - 线程:持有 WMS 全局锁的调用路径
- 作用:以该区域为父节点构建顶层 Task。
阅读问题:Root Task 的“root”相对于谁?
Task createRootTask(int windowingMode, int activityType, boolean onTop,
ActivityOptions opts) {
return new Task.Builder(mAtmService)
.setWindowingMode(windowingMode)
.setActivityType(activityType)
.setParent(this)
.setOnTop(onTop)
.setActivityOptions(opts)
.build();
}结论:setParent(this) 明确 Root Task 的父节点是 TaskDisplayArea。因此 Root Task 是“该区域下的顶层任务”,不是 DisplayContent 或系统全部窗口的根。
源码节点:Task 交接组织权
- 类:
Task - 方法:
setTaskOrganizer(ITaskOrganizer, boolean) - 路径:
frameworks/base/services/core/java/com/android/server/wm/Task.java - 进程:
system_server - 线程:持有 WMS 全局锁的调用路径
- 作用:记录新的组织者,并在组织权变化时发送 appeared/vanished 回调。
阅读问题:Task 为何能被 Shell 观察和组织,而逻辑层级仍留在系统服务?
ITaskOrganizer prevOrganizer = mTaskOrganizer;
mTaskOrganizer = organizer;
sendTaskVanished(prevOrganizer);
if (mTaskOrganizer != null) {
if (!skipTaskAppeared) {
sendTaskAppeared();
}
} else {
final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (taskDisplayArea != null) {
taskDisplayArea.removeLaunchRootTask(this);
}
}结论:组织者是 Task 的外部协作者,不是 Task 的父容器。Task 仍由 system_server 管理,只是在合适时机把信息与可控制 Surface 交给组织者。
源码节点:ActivityRecord 与窗口归属
- 类:
ActivityRecord - 方法:
getTask()、addWindow(WindowState) - 路径:
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java - 进程:
system_server - 线程:持有 WMS 全局锁的调用路径
- 作用:保存 Activity 所属 Task,并把其
WindowState加入自己的子节点。
阅读问题:一个 Activity 的窗口怎样回到任务层级?
Task getTask() {
return task;
}
@Override
void addWindow(WindowState w) {
super.addWindow(w);
checkKeyguardFlagsChanged();
}结论:ActivityRecord 连接了“任务记录”和“具体窗口”。同一 Activity 不等同于其 View 树;其窗口由 WindowState 表达并作为容器子节点维护。
源码节点:WindowState 追溯所属 Root Task
- 类:
WindowState - 方法:
getTask()、getRootTask() - 路径:
frameworks/base/services/core/java/com/android/server/wm/WindowState.java - 进程:
system_server - 线程:持有 WMS 全局锁的调用路径
- 作用:通过
ActivityRecord回到 Task,再取得 Root Task。
阅读问题:排查某个窗口时,怎样判断它属于哪棵任务子树?
Task getTask() {
return mActivityRecord != null ? mActivityRecord.getTask() : null;
}
@Nullable Task getRootTask() {
final Task task = getTask();
if (task != null) {
return task.getRootTask();
}
// 省略系统窗口的 Home Root Task 回退分支
}结论:应用窗口可沿 WindowState → ActivityRecord → Task → Root Task 追溯;系统窗口可能没有 Activity Task,不能把所有 WindowState 都归为某个分屏应用。
源码节点:TaskOrganizer 接收 leash
- 类:
TaskOrganizerController.TaskOrganizerCallbacks - 方法:
onTaskAppeared(Task)、prepareLeash(Task, String) - 路径:
frameworks/base/services/core/java/com/android/server/wm/TaskOrganizerController.java - 进程:发送端为
system_server;接收端为注册组织者进程 - 线程:发送端在全局锁路径;接收端先为 Binder 回调,再由客户端
Executor调度 - 作用:从 Task 的
SurfaceControl构造 leash,并随RunningTaskInfo回调给组织者。
阅读问题:Surface leash 与 Task 的逻辑身份分别从哪里来?
SurfaceControl prepareLeash(Task task, String reason) {
return new SurfaceControl(task.getSurfaceControl(), reason);
}
void onTaskAppeared(Task task) {
final RunningTaskInfo taskInfo = task.getTaskInfo();
try {
mTaskOrganizer.onTaskAppeared(taskInfo, prepareLeash(task,
"TaskOrganizerController.onTaskAppeared"));
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskAppeared callback", e);
}
}结论:TaskInfo 描述逻辑 Task,leash 是基于该 Task 当前 SurfaceControl 创建的控制句柄。二者同时回调不表示它们是同一个对象,更不表示 Shell 拥有容器树。
源码节点:TaskOrganizer 把 Binder 回调交给 Executor
- 类:
android.window.TaskOrganizer - 方法:
ITaskOrganizer.Stub.onTaskAppeared(...) - 路径:
frameworks/base/core/java/android/window/TaskOrganizer.java - 进程:注册
TaskOrganizer的进程(通常是具备相应权限的 Shell 组件) - 线程:Binder 回调线程转交到构造时提供的
Executor - 作用:避免把组织者的业务处理固定在 Binder 线程。
阅读问题:组织者收到回调后在哪个线程继续执行?
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash) {
mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash));
}结论:线程边界在这里显式存在;文章中的“Shell 处理 leash”应理解为该 Executor 的任务,而不是 system_server 直接运行 Shell 代码。
验证与排障
目标:在真实设备进入/退出分屏前后,比较任务的窗口模式、边界和窗口归属,而非猜测一套固定的 OEM dump 树。
环境与权限:连接 Android 14 设备并打开 USB 调试;以下命令在主机终端运行,只读取系统状态,通常不需要 root。先在设备上实际进入分屏。
adb shell dumpsys activity activities
adb shell dumpsys window windows预期:在第一份输出中搜索 Task、rootTaskId、windowingMode、bounds 等任务线索;在第二份输出中以应用包名或窗口标题定位 WindowState。对比进入分屏前后,检查两侧任务边界及窗口所属任务是否变化。
异常方向:若没有找到字段,先保留设备原始输出,再用 adb shell dumpsys activity --help 或 adb shell dumpsys window --help 查看该镜像公开的过滤方式;随后从本文的 WindowState.getTask() 和 TaskDisplayArea.createRootTask() 回查版本源码。
限制:dumpsys 的字段、缩进和可用过滤参数会随设备构建、厂商改动及权限而变化;本文没有把任何未在你的设备上验证的子命令或节点名称写成必然可用。
常见误区
- 误区:分屏等于把两个 Activity 的 View 合到一起。
- 问题:忽略了 Task、配置和窗口层级仍由
system_server管理。 - 正确理解:两侧是不同 Task 子树,系统重组容器及边界;应用仍绘制各自窗口。
- 验证方式:对比
dumpsys activity activities与WindowState.getTask()的追溯关系。
- 问题:忽略了 Task、配置和窗口层级仍由
- 误区:Root Task 就是显示器根节点。
- 问题:忽略了
DisplayContent → TaskDisplayArea → Root Task三层职责。 - 正确理解:Root Task 的父节点是
TaskDisplayArea,见createRootTask()的setParent(this)。 - 验证方式:阅读
DisplayContent.getDefaultTaskDisplayArea()与TaskDisplayArea.createRootTask()。
- 问题:忽略了
- 误区:leash 就是逻辑 Task 本身。
- 问题:混淆 Task 信息模型和
SurfaceControl控制句柄。 - 正确理解:回调分别携带
RunningTaskInfo与由 Task Surface 构造的 leash。 - 验证方式:阅读
TaskOrganizerController.prepareLeash()。
- 问题:混淆 Task 信息模型和
延伸阅读
- 窗口加入了哪棵树:WMS 系列负责普通 Activity 窗口的入树路径,本篇负责多窗口容器的复杂结构。
- 多窗口配置:在容器层级之上理解窗口配置变化。
- 分屏:继续追踪分屏的组织与交互流程。
frameworks/base/services/core/java/com/android/server/wm/:从WindowContainer、Task与TaskOrganizerController继续阅读容器和组织权实现。
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
| 见本章“关键源码”各节点 | 类名 / 方法名 | 证明主流程的关键动作 |
| Android 14 r75 对应源码文件 | 使用 rg -n 核对 | 发布前记录真实行号,避免跨 tag 漂移 |