悬浮窗架构浅析
一、架构背景与演进
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 整体架构图
scss
┌─────────────────────────────────────────────────────────────────────────┐
│ 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 点击切换为悬浮窗 - 详细流程
4.3 关键代码路径
Step 1: Shell 层发起切换请求(Shell Transition 流程)
java
// 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 处理窗口模式变化
java
// 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 处理
java
// 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 创建系统控制栏
java
// 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 与应用解耦:
ini
┌─────────────────────────────────────────────────┐
│ ① 应用层:按 freeform bounds 重新布局 │
│ appBounds = bounds = freeform 区域尺寸 │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ ② Surface 层:按 bounds 裁剪(setWindowCrop) │
│ crop = bounds.width × bounds.height │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ ③ 系统控制栏:独立 Surface,叠加在任务 Surface 上 │
│ 与应用 DecorView 布局完全解耦 │
└─────────────────────────────────────────────────┘
5.2 数据对比
全屏应用:
yaml
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)
悬浮窗应用:
yaml
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 源码实现
java
// 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() 和动画适配器实现:
java
// 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 关键类关系
scss
FreeformTaskListener (监听 WMS 通知)
↓
FreeformController (总管)
├── FreeformCoordinator (bounds 计算)
├── MiniformController (Mini 窗)
├── WindowDecorViewModel (系统控制栏)
│ └── WindowDecoration (单窗口对象)
│ └── DragResizeInputListener (事件监听)
│ └── FluidResizeTaskPositioner (拖拽处理)
└── FreeformTaskOperations (操作工具类)