悬浮窗架构浅析
一、架构背景与演进
1.1 老架构的问题
旧版悬浮窗基于 Google 老的 DecorCaptionView 框架。该框架存在根本性缺陷:
布局耦合问题:应用布局和 CaptionView 布局共用一个 DecorView,相互之间存在布局耦合,导致三方应用出现遮挡、漏底、挤让等问题。
窗口类型定制:早期实现区别于原生 WINDOWING_MODE_FREEFORM,自定义了额外的窗口类型,在窗口系统侧和应用布局框架侧存在大量插桩定制修改,长期升级维护困难。
1.2 U 版本架构升级目标
升级到 Google U 版本的 libs/wm/shell/freeform 架构:
- Shell 层管理:将悬浮窗的管理迁移到
libs/wm/shell中 - 独立 Surface:在 Shell 中创建独立的 Surface 用于显示悬浮窗控制栏
- 布局解耦:从架构上解决应用布局与系统控制栏的耦合问题
- UI 交互解耦:将 UI 交互特性从
wm/core中解耦出来,具有更好的 OEM 可扩展性
二、系统架构
2.1 整体架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ APP 层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Launcher │ │ DockBar │ │ 悬浮球 │ │
│ │ (最近任务入口)│ │(侧边栏入口) │ │(后台任务管理)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
↓ E/F 接口
┌─────────────────────────────────────────────────────────────────────────┐
│ WM SHELL 层 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ FreeformTaskListener │ │
│ │ (接收 WMS 通知:Task 启动、销毁、状态变化) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ FreeformController │ │
│ │ (悬浮窗总管:生命周期、位置、大小、动画、焦点、阴影) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ↓ ↓ ↓ │
│ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │Coordinator │ │WindowDecorViewModel│ │MiniformController│ │
│ │(bounds计算) │ │(系统控制栏管理) │ │(Mini窗管理) │ │
│ └─────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
↓ WCT (WindowContainerTransaction)
┌─────────────────────────────────────────────────────────────────────────┐
│ WM CORE 层 (WMS) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ WindowManagerService │ │
│ │ (窗口模式管理、Task 栈管理、配置传播) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Task │ │
│ │ (windowingMode、bounds、Configuration 合并与传播) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ SurfaceFlinger │
│ (Surface 合成与渲染) │
└─────────────────────────────────────────────────────────────────────────┘
2.2 WM-CORE 与 WM-SHELL 交互接口
| 接口 | 作用 |
|---|---|
ITaskOrganizer | WM-CORE 通知 WM-SHELL 侧,管理启动窗口、Task 状态管理等 |
ITaskOrganizerController | 注册 ITaskOrganizer,建立 core 和 shell 通信通道 |
IWindowOrganizerController | WM-SHELL 通过此通道控制系统侧窗口节点属性更新 |
ITransitionPlayer | WM-CORE 通知 WM-Shell 申请、执行 Transition 的接口通道 |
三、核心类职责
3.1 Shell 层核心类
| 类名 | 路径 | 职责 |
|---|---|---|
FreeformTaskListener | freeform/FreeformTaskListener.java | 接收 WMS 通知,转发给 FreeformController |
FreeformController | freeform/ | 悬浮窗总管,管理整个悬浮窗生命周期 |
FreeformCoordinator | freeform/ | 计算悬浮窗位置和大小 |
CaptionWindowDecorViewModel | windowdecor/CaptionWindowDecorViewModel.java | 系统控制栏管理,处理顶部 Bar 交互 |
WindowDecoration | windowdecor/WindowDecoration.java | 单个悬浮窗的 Shell 对象,承载系统控制栏 |
DragResizeInputListener | windowdecor/DragResizeInputListener.java | 创建 input 事件监听器 |
FluidResizeTaskPositioner | windowdecor/FluidResizeTaskPositioner.java | 实现悬浮窗事件操作(跟手调整位移和大小) |
MiniformController | freeform/ | Mini 窗管理 |
3.2 WMS 层核心类
| 类名 | 路径 | 职责 |
|---|---|---|
Task | services/wm/Task.java | 管理窗口配置、bounds、Configuration 传播 |
WindowContainer | services/wm/WindowContainer.java | 配置合并与传播基类 |
ActivityRecord | services/wm/ActivityRecord.java | Activity 级配置管理 |
ShellTaskOrganizer | libs/WindowManager/Shell/... | Shell 侧操作 Task 的底层接口 |
四、进入悬浮窗的完整流程
4.1 入口场景
| 场景 | 触发方式 |
|---|---|
| 全屏 Bar 点击 | 点击全屏应用顶部 Bar 的悬浮窗按钮 |
| 分屏 Bar 点击 | 点击分屏应用顶部 Bar 的悬浮窗按钮 |
| 手势拖拽 | 从全屏/分屏顶部下滑拖拽 |
| 侧边栏冷启动 | 从侧边 DockBar 点击启动 |
| 悬浮球热启动 | 从悬浮球点击恢复 |
| 最近任务启动 | 从最近任务列表点击 |
4.2 全屏 Bar 点击切换为悬浮窗 - 详细流程
sequenceDiagram
participant User as 用户
participant Bar as 全屏Bar
participant MultiWin as MultiWindowController
participant Freeform as FreeformController
participant Coordinator as FreeformCoordinator
participant Transitions as Transitions
participant WCT as WindowContainerTransaction
participant WMS as WindowManagerService
participant Task as Task
participant Listener as FreeformTaskListener
participant DecorVM as WindowDecorViewModel
User->>Bar: 点击悬浮窗按钮
Bar->>MultiWin: handleSplitScreenOrFullToFreeForm(taskId)
MultiWin->>Freeform: 调用切换接口
rect rgb(240, 248, 255)
Note over Freeform,Coordinator: Shell 层计算目标 bounds
Freeform->>Coordinator: calcFreeformTaskBounds(taskInfo)
Coordinator->>Coordinator: 获取屏幕尺寸
Coordinator->>Coordinator: 计算默认大小(屏幕70%)
Coordinator->>Coordinator: 计算居中位置
Coordinator-->>Freeform: 返回目标 bounds
end
Freeform->>WCT: 创建 WindowContainerTransaction
WCT->>WCT: setWindowingMode(token, FREEFORM)
WCT->>WCT: setBounds(token, bounds)
rect rgb(255, 250, 240)
Note over Freeform,Transitions: Shell Transition 流程
Freeform->>Transitions: startTransition(TRANSIT_XXX, wct, handler)
Transitions->>WMS: 通过 ITransitionPlayer 请求开始 Transition
end
rect rgb(255, 248, 240)
Note over WMS,Task: WMS 层处理窗口模式切换
WMS->>Task: onRequestedOverrideConfigurationChanged()
Task->>Task: setWindowingMode(FREEFORM)
Task->>Task: setBounds(bounds)
Task->>Task: onConfigurationChanged()
Task->>Task: updateSurfaceSize()
end
Task->>Listener: onTaskInfoChanged(taskInfo)
Listener->>Freeform: onTaskInfoChanged(taskInfo, leash)
rect rgb(240, 255, 240)
Note over Freeform,DecorVM: Shell 层创建系统控制栏
Freeform->>DecorVM: addTaskStateInfos(taskInfo, leash)
DecorVM->>DecorVM: createWindowDecoration()
DecorVM->>DecorVM: 创建独立 Surface (mDecorationContainerSurface)
DecorVM->>DecorVM: 加载顶部 Bar / 底部 Bar 布局
end
Freeform->>Freeform: 记录 Task 信息到 mFreeformTasks
4.3 关键代码路径
Step 1: Shell 层发起切换请求(Shell Transition 流程)
// FreeformController.java
public void startFullToFreeformTransition(ActivityManager.RunningTaskInfo taskInfo) {
mWindowDecorViewModel.startFullToFreeformTransition(taskInfo);
}
// FreeformController.java - 其他场景示例
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
// 当前流程:通过 Shell Transition 机制
mTransitions.startTransition(TRANSIT_TO_BACK, wct, null /* handler */);
} else {
// 旧流程(已废弃):直接 applyTransaction
mTaskOrganizer.applyTransaction(wct);
}
Shell Transition 流程说明:
| 对比项 | Shell Transition (当前) | applyTransaction (已废弃) |
|---|---|---|
| 触发方式 | mTransitions.startTransition(type, wct, handler) | mTaskOrganizer.applyTransaction(wct) |
| 动画同步 | 与 Transition 框架同步,动画完成后才生效 | 立即生效,动画需额外处理 |
| WMS 交互 | 通过 ITransitionPlayer.requestStartTransition() | 直接执行 WCT |
| 状态管理 | Transition 状态机管理(COLLECTING → STARTED → PLAYING) | 无状态管理 |
Step 2: WMS 处理窗口模式变化
// Task.java
public int setBounds(Rect bounds) {
final int boundsChange = super.setBounds(bounds);
updateSurfacePositionNonOrganized();
return boundsChange;
}
private void onConfigurationChangedInner(Configuration newParentConfig) {
// 检查是否切换到 FREEFORM 模式
final boolean prevPersistTaskBounds = persistTaskBounds(getWindowConfiguration());
boolean nextPersistTaskBounds = persistTaskBounds(
getRequestedOverrideConfiguration().windowConfiguration);
// 从全屏切换到 FREEFORM,恢复保存的 bounds
if (!prevPersistTaskBounds && nextPersistTaskBounds
&& mLastNonFullscreenBounds != null) {
getRequestedOverrideConfiguration().windowConfiguration
.setBounds(mLastNonFullscreenBounds);
}
super.onConfigurationChanged(newParentConfig);
updateSurfaceSize(getSyncTransaction());
}
Step 3: FREEFORM 模式的 Surface 处理
// Task.java
void updateSurfaceSize(SurfaceControl.Transaction transaction) {
// AOSP 原生:对 root task 按 bounds 宽高裁剪 Surface
int width = 0;
int height = 0;
if (isRootTask() || !fillsParentBounds()) {
final Rect taskBounds = getBounds();
width = taskBounds.width();
height = taskBounds.height();
}
transaction.setWindowCrop(mSurfaceControl, width, height);
}
注:某些厂商定制实现会针对自由窗口模式改用
setWindowCrop(-1, -1)(不裁剪)配合 Surface Transform 缩放来"伪装"小窗,让应用保持全屏绘制。但 AOSP 原生Task.updateSurfaceSize对自由窗口同样按 bounds 裁剪,应用会真正按新 bounds 重新布局。
Step 4: Shell 创建系统控制栏
// CaptionWindowDecorViewModel.java
public boolean onTaskOpening(RunningTaskInfo taskInfo, SurfaceControl taskSurface,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
if (!shouldShowWindowDecor(taskInfo)) return false;
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
return true;
}
private void createWindowDecoration(RunningTaskInfo taskInfo, SurfaceControl taskSurface,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
// 创建独立的 Surface 用于显示系统控制栏
CaptionWindowDecoration decor = new CaptionWindowDecoration(...);
decor.setupDecorationSurface(startT, taskSurface);
// 加载顶部 Bar 和底部 Bar 布局
View decorView = inflateCaptionBar();
decor.addView(decorView);
mWindowDecorByTaskId.put(taskInfo.taskId, decor);
}
五、应用布局不变的原理
5.1 核心机制:应用配置与 Surface 同步
切换到悬浮窗时,应用并不"假装"全屏绘制,而是按新的 freeform bounds 真实重新布局,系统控制栏通过独立 Surface 与应用解耦:
┌─────────────────────────────────────────────────┐
│ ① 应用层:按 freeform bounds 重新布局 │
│ appBounds = bounds = freeform 区域尺寸 │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ ② Surface 层:按 bounds 裁剪(setWindowCrop) │
│ crop = bounds.width × bounds.height │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ ③ 系统控制栏:独立 Surface,叠加在任务 Surface 上 │
│ 与应用 DecorView 布局完全解耦 │
└─────────────────────────────────────────────────┘
5.2 数据对比
全屏应用:
appBounds: (0, 0) - (1060, 2376)
bounds: (0, 0) - (1060, 2376)
maxBounds: (0, 0) - (1060, 2376)
windowingMode: WINDOWING_MODE_FULLSCREEN
Surface Crop: (0, 0) - (1060, 2376)
悬浮窗应用:
appBounds: (111, 508) - (1170, 2228) // = bounds
bounds: (111, 508) - (1170, 2228)
maxBounds: (0, 0) - (1060, 2376) // 仍是屏幕大小
windowingMode: WINDOWING_MODE_FREEFORM
Surface Crop: bounds.width × bounds.height
5.3 关键分析
应用按新 bounds 重新布局:悬浮窗的 appBounds = bounds,即 freeform 区域尺寸。应用通过 onConfigurationChanged 收到新的 bounds/appBounds,按该尺寸真实重新测量布局。
Surface 按_bounds 裁剪:Task.updateSurfaceSize 对 root task 按 getBounds().width()/height() 裁剪 Surface,确保应用绘制内容不超过任务边界。
系统控制栏独立 Surface:WindowDecoration/CaptionWindowDecorViewModel 创建独立的 mDecorationContainerSurface(挂载在任务 Surface 上),加载顶部 Bar / 底部 Bar 布局。控制栏的布局与绘制完全不依赖应用 DecorView,从架构上解耦。
maxBounds 保持屏幕大小:悬浮窗的 maxBounds 仍是 (0,0)-(1060,2376),应用仍可获取屏幕尺寸做适配决策。
5.4 源码实现
// Task.updateSurfaceSize()
void updateSurfaceSize(SurfaceControl.Transaction transaction) {
// 对 root task 按 bounds 宽高裁剪 Surface
int width = 0;
int height = 0;
if (isRootTask() || !fillsParentBounds()) {
final Rect taskBounds = getBounds();
width = taskBounds.width();
height = taskBounds.height();
}
transaction.setWindowCrop(mSurfaceControl, width, height);
}
缩放变换通过 SurfaceControl.Transaction.setMatrix() 和动画适配器实现:
// FreeformAnimationAdapter.java
void onAnimationUpdateInner(SurfaceControl.Transaction transaction) {
mTransformation.getMatrix().preScale(mLastScale, mLastScale);
transaction.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
}
5.5 Configuration 传播控制
切换悬浮窗时,WMS 判断 Configuration 变化是否需要 relaunch:
| 变化类型 | 应用是否感知 | 处理方式 |
|---|---|---|
| density 变化 | 是 | relaunch |
| orientation 变化 | 是 | relaunch |
| bounds 变化 | 是 | onConfigurationChanged 重新布局 |
bounds 变化通常不触发 relaunch,应用通过 onConfigurationChanged 收到新的 bounds/appBounds,按新尺寸真实重新布局,Surface 按新 bounds 裁剪显示。
5.6 核心结论
- 应用按新 bounds 重新布局 — appBounds = bounds,应用真实按 freeform 尺寸布局
- Surface 按 bounds 裁剪 —
setWindowCrop(width, height),内容不超过任务边界 - 系统控制栏独立 Surface — 与应用 DecorView 布局解耦,互不影响
- maxBounds 保持全屏 — 应用仍可获取屏幕尺寸做适配
六、总结
6.1 架构核心要点
- Shell 层主导:悬浮窗的 UI 交互、生命周期管理都在 Shell 层完成
- 独立 Surface:系统控制栏使用独立 Surface,与应用布局解耦
- WCT 通信:Shell 层通过 WindowContainerTransaction 与 WMS 通信
- Surface 按 bounds 裁剪:FREEFORM 模式下按任务 bounds 裁剪 Surface,应用按新 bounds 重新布局
6.2 进入悬浮窗的关键步骤
- Shell 层计算目标 bounds(FreeformCoordinator)
- 创建 WCT,设置 windowingMode 和 bounds
- WMS 更新 Task 配置,更新 Surface 尺寸(按 bounds 裁剪)
- 回调 Shell 层,创建系统控制栏 Surface
- 加载顶部 Bar / 底部 Bar 布局
- 设置事件监听,处理用户交互
6.3 关键类关系
FreeformTaskListener (监听 WMS 通知)
↓
FreeformController (总管)
├── FreeformCoordinator (bounds 计算)
├── MiniformController (Mini 窗)
├── WindowDecorViewModel (系统控制栏)
│ └── WindowDecoration (单窗口对象)
│ └── DragResizeInputListener (事件监听)
│ └── FluidResizeTaskPositioner (拖拽处理)
└── FreeformTaskOperations (操作工具类)