WindowOrganizer 如何跨进程重组窗口容器
WindowOrganizer 如何跨进程重组窗口容器
问题场景
分屏不是 Shell 直接移动某个 SurfaceControl 就完成的:任务的父子关系、bounds 和窗口模式必须由 system_server 中的窗口层级统一修改。WindowOrganizer 提供的正是这个受权限保护的跨进程入口;调用方先把“想怎样改”写进 WindowContainerTransaction(WCT),服务端再原子地解释并提交这些变化。
下面以 AOSP Android 14 android-14.0.0_r75 为准,跟踪 WCT 如何抵达 WindowOrganizerController,以及 Shell 怎样取得并管理 Task 回调。阅读一份 WCT 时,先区分 它修改的是配置、层级还是两者,再定位 Binder、权限和全局锁边界。
核心结论
- WCT 是“变更描述”而不是已提交的 Surface 事务:
setBounds()、setWindowingMode()写入某个容器的Change,reparent()追加顺序敏感的层级操作。 WindowOrganizer.applyTransaction()通过IWindowOrganizerControllerBinder 进入system_server;服务端检查MANAGE_ACTIVITY_TASKS后,在mGlobalLock内应用事务。applyTransaction()的 Binder 调用会返回,但它不提供 Surface 就绪回调;applySyncTransaction()用 BLAST 同步组在 Surface 结果就绪时回调;与 transition 关联时则由startNewTransition()/startTransition()把 WCT 纳入 transition 的收集与完成生命周期。TaskOrganizer.registerOrganizer()是任务可见性/生命周期回调的登记入口。ShellTaskOrganizer收到onTaskAppeared(taskInfo, leash)后缓存任务,并分派给匹配的TaskListener,从而让 WM Shell 把“逻辑 Task”与可操作的 leash 对齐。
观察下面的主线:真正跨进程的是 Organizer 到 WindowOrganizerController 的 Binder;leash 则是任务出现回调携带的渲染控制句柄,不是 WCT 本身。
主流程
阶段一:调用方描述,而不直接改窗口树
- 执行者:WM Shell 等持有系统权限的组件;通常在其 executor 线程。
- 输入:目标
WindowContainerToken、目标 bounds、窗口模式或新的父容器。 - 动作:WCT 以 token 为键积累
Change,并维护层级操作列表。 - 下一跳:
WindowOrganizer.applyTransaction(wct)。 - 边界:仍在调用方进程;尚未改变服务端窗口树。
阶段二:Binder 进入窗口组织控制器
- 执行者:
system_server的 Binder 调用线程。 - 输入:被 Parcel 化的 WCT。
- 动作:
IWindowOrganizerController将调用分派给WindowOrganizerController.applyTransaction();它执行权限检查、清除调用身份,并在mGlobalLock下应用。 - 下一跳:私有
applyTransaction()逐个处理Change与HierarchyOp。 - 边界:跨进程 Binder;锁保护窗口层级与可见性状态的一致观察。
阶段三:任务出现后交给 Shell
- 执行者:
TaskOrganizer的 Binder 回调随后转交其配置的 executor;ShellTaskOrganizer自己用mLock保护任务表。 - 输入:
RunningTaskInfo与SurfaceControl leash。 - 动作:登记时返回当前应管理的 Task;出现时 Shell 缓存信息,选择
TaskListener并把 taskInfo/leash 交给它。 - 下一跳:具体 Shell 模块据此安排布局、动画或 Surface 操作。
- 边界:任务组织器回调是从
system_server回到 Shell 的 Binder;不要把它同 WCT 请求方向混为一谈。
关键源码
源码节点:WCT 如何保存两类修改
- 类与方法:
WindowContainerTransaction.setBounds()、setWindowingMode()、reparent() - 路径:
frameworks/base/core/java/android/window/WindowContainerTransaction.java - 进程与线程:调用方进程、调用方线程。
- 作用:把配置修改与层级修改累积为可跨进程传输的描述。
阅读问题:bounds、窗口模式和换父节点是否都在调用时立即生效?
public WindowContainerTransaction setBounds(
@NonNull WindowContainerToken container, @NonNull Rect bounds) {
Change chg = getOrCreateChange(container.asBinder());
chg.mConfiguration.windowConfiguration.setBounds(bounds);
chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
return this;
}
// 省略无关代码
public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
@Nullable WindowContainerToken parent, boolean onTop) {
mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),
parent == null ? null : parent.asBinder(), onTop));
return this;
}结论:前者写入按 token 归属的 Change,后者追加 HierarchyOp。它们只是 WCT 内容;只有交给 Organizer 后,服务端窗口树才会改变。
源码节点:客户端 API 到 Binder 接口
- 类与方法:
WindowOrganizer.applyTransaction();IWindowOrganizerController.applyTransaction() - 路径:
frameworks/base/core/java/android/window/WindowOrganizer.java;frameworks/base/core/java/android/window/IWindowOrganizerController.aidl - 进程与线程:调用方线程 →
system_serverBinder 线程。 - 作用:以 Binder 传递非空 WCT,并声明调用方需要的权限。
阅读问题:普通事务与同步事务的 API 边界在哪里?
@RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
public void applyTransaction(@NonNull WindowContainerTransaction t) {
try {
if (!t.isEmpty()) {
getWindowOrganizerController().applyTransaction(t);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
// 省略无关代码
public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) {
try {
return getWindowOrganizerController().applySyncTransaction(t, callback.mInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}结论:applyTransaction() 对空 WCT 不发 Binder 请求;同步版本额外传递回调接口并返回 sync id。两者都不是普通三方应用可随意调用的公开能力,因为需 MANAGE_ACTIVITY_TASKS。
源码节点:服务端的权限和全局锁
- 类与方法:
WindowOrganizerController.applyTransaction() - 路径:
frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java - 进程与线程:
system_serverBinder 线程;进入mGlobalLock临界区。 - 作用:拒绝无权限请求,并将 WCT 的配置/层级改变纳入窗口管理的统一临界区。
阅读问题:服务端为何不能在 Binder 回调中无保护地逐项修改?
public void applyTransaction(WindowContainerTransaction t) {
if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applyTransaction");
}
enforceTaskPermission("applyTransaction()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
applyTransaction(t, -1 /*syncId*/, null /*transition*/, caller);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}结论:权限由服务端再次强制执行,不能只依赖客户端注解;锁把 WCT 的处理置于窗口管理共享状态的同步边界。私有实现会遍历 WCT 的 Change 和 HierarchyOp,并推迟布局/可见性更新直到本次处理结束。
源码节点:注册和出现回调
- 类与方法:
TaskOrganizer.registerOrganizer();ShellTaskOrganizer.onTaskAppeared() - 路径:
frameworks/base/core/java/android/window/TaskOrganizer.java;frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java - 进程与线程:Shell 侧注册线程;任务回调由 Binder 到达,随后由 organizer 的 executor 或 Shell 的锁保护路径处理。
- 作用:把应管理的 Task 和它的 leash 交给 Shell。
public List<TaskAppearedInfo> registerOrganizer() {
try {
return mTaskOrganizerController.registerTaskOrganizer(mInterface).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
// 省略无关代码
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mLock) {
onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
}
}结论:注册获得的是当前需要管理的 Task 列表;后来出现的 Task 则经回调进入 Shell。ShellTaskOrganizer 会保存该信息并通知匹配 listener,因而 layout/动画模块无需自行轮询窗口树。
三种提交方式如何选择
| 方式 | API 与完成信号 | 适合的边界 | 不应推断的事 |
|---|---|---|---|
| 普通(无完成回调) | applyTransaction();Binder 调用返回但无 Surface 就绪回调 | 只需把逻辑窗口状态提交给 WM | 返回即代表合成画面已出现 |
| 同步 | applySyncTransaction();transactionReady 回调和 sync id | 需要等待关联 Surface 结果可一起使用 | 它自动创建或播放 Shell transition |
| 关联 transition | startNewTransition() / startTransition() 携带 WCT | WCT 必须属于 transition 的收集、ready、finish 生命周期 | 它等同于任意一次普通同步调用 |
服务端的同步路径创建 BLASTSyncEngine.SyncGroup 并在 ready 后回调;transition 路径把 WCT 交给 TransitionController 收集。因而本文把“异步”限定为调用方不等待 Surface 完成的观察语义:applyTransaction() 本身的 Binder RPC 不是异步 one-way 调用。
验证与排障
目标:确认文章引用的入口存在,并在设备上观察分屏 Task 的层级状态,而不把厂商 UI 行为写成 AOSP 结论。
环境与权限:源码核对在主机终端运行;设备观察需要已连接的 Android 14 AOSP 风格设备或模拟器和 adb shell 权限。普通应用通常没有 MANAGE_ACTIVITY_TASKS,不要把示例 WCT 当作可直接粘贴到应用的 API。
# 主机终端:在本地 android-14.0.0_r75 源码树运行。
rg -n 'applyTransaction|applySyncTransaction|startNewTransition' \
frameworks/base/core/java/android/window/WindowOrganizer.java
rg -n 'enforceTaskPermission|mGlobalLock|applyTransaction' \
frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java
# 主机终端:设备已进入分屏后运行。
adb shell dumpsys activity activities
adb shell dumpsys window windows预期:前两条可定位 Binder 客户端与服务端保护点;后两条可观察 Task/窗口状态随分屏操作变化。异常时先确认设备是否真的支持该分屏入口、Shell 进程日志及 MANAGE_ACTIVITY_TASKS 调用者身份,再回到上述服务端节点。
限制:dumpsys 能显示某一时刻状态,不能单独证明某次 Surface 合成或 transition 的完成顺序;不同设备的 SystemUI 流程与输出格式可能不同。
常见误区
- 误区:WCT 等同于
SurfaceControl.Transaction。- 问题:前者描述 WindowContainer 的配置和层级,后者操作合成层;它们的提交者与完成语义不同。
- 正确理解:WCT 先由
WindowOrganizerController解释,随后才可能产生 Surface 侧效果。 - 验证方式:对照 WCT 的
Change/HierarchyOp与服务端applyTransaction()的遍历。
- 误区:
onTaskAppeared()就表示 WCT 已完成。- 问题:该回调表达任务组织器观察到 Task 和 leash,不是任一 WCT 的逐笔确认。
- 正确理解:若需要 Surface 同步结果,使用同步事务回调;若需要动画生命周期,查看 transition 路径。
- 验证方式:比较
applySyncTransaction()的 callback 与TaskOrganizer的回调接口。
- 误区:权限注解只是一份文档。
- 问题:服务端也调用
enforceTaskPermission(),无权限请求会在 Binder 服务端被拒绝。 - 正确理解:系统组件的调用资格与全局锁中的状态一致性共同构成这条边界。
- 问题:服务端也调用
延伸阅读
- Task、Root Task 与 WindowContainer 如何描述多窗口层级:WCT 的 token、父子关系和层级操作落到哪里。
- 多窗口 Configuration 如何从 WindowManager 传到应用:bounds/窗口模式改变后的配置传播边界。
- WM Shell 与 SystemUI 如何组织分屏:ShellTaskOrganizer 的 listener 如何服务分屏模块。
- 双应用分屏如何从一次手势走到两个 Task:把本篇的事务模型放回完整用户操作链路。
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
| 见本章“关键源码”各节点 | 类名 / 方法名 | 证明主流程的关键动作 |
| Android 14 r75 对应源码文件 | 使用 rg -n 核对 | 发布前记录真实行号,避免跨 tag 漂移 |