悬浮窗架构浅析

悬浮窗架构浅析


一、架构背景与演进

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 点击切换为悬浮窗 - 详细流程

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 流程)
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 核心结论

  1. 应用按新 bounds 重新布局 --- appBounds = bounds,应用真实按 freeform 尺寸布局
  2. Surface 按 bounds 裁剪 --- setWindowCrop(width, height),内容不超过任务边界
  3. 系统控制栏独立 Surface --- 与应用 DecorView 布局解耦,互不影响
  4. maxBounds 保持全屏 --- 应用仍可获取屏幕尺寸做适配

六、总结

6.1 架构核心要点

  1. Shell 层主导:悬浮窗的 UI 交互、生命周期管理都在 Shell 层完成
  2. 独立 Surface:系统控制栏使用独立 Surface,与应用布局解耦
  3. WCT 通信:Shell 层通过 WindowContainerTransaction 与 WMS 通信
  4. Surface 按 bounds 裁剪:FREEFORM 模式下按任务 bounds 裁剪 Surface,应用按新 bounds 重新布局

6.2 进入悬浮窗的关键步骤

  1. Shell 层计算目标 bounds(FreeformCoordinator)
  2. 创建 WCT,设置 windowingMode 和 bounds
  3. WMS 更新 Task 配置,更新 Surface 尺寸(按 bounds 裁剪)
  4. 回调 Shell 层,创建系统控制栏 Surface
  5. 加载顶部 Bar / 底部 Bar 布局
  6. 设置事件监听,处理用户交互

6.3 关键类关系

scss 复制代码
FreeformTaskListener (监听 WMS 通知)
        ↓
FreeformController (总管)
        ├── FreeformCoordinator (bounds 计算)
        ├── MiniformController (Mini 窗)
        ├── WindowDecorViewModel (系统控制栏)
        │       └── WindowDecoration (单窗口对象)
        │               └── DragResizeInputListener (事件监听)
        │                       └── FluidResizeTaskPositioner (拖拽处理)
        └── FreeformTaskOperations (操作工具类)
相关推荐
哈基米南北1 小时前
Android 亮屏流程分析
源码
Amos_Web1 小时前
Rspack 源码解析(十四):Resolver 与 NormalModuleFactory
前端·rust·源码
哈基米南北1 小时前
Activity Embedding 架构和应用启动流程
架构·源码
哈基米南北1 小时前
Android 输入法框架浅析
源码
哈基米南北1 小时前
Shell Transition 机制详解
源码
哈基米南北1 小时前
Android 启动窗口(Starting Window)技术文档
源码
哈基米南北1 小时前
PhoneWindow、DecorView 和 ViewRootImpl 的关系与工作原理
源码
哈基米南北5 天前
ViewRootImpl 事件分发责任链分析
源码
Amos_Web6 天前
Rspack 源码解析(十二):JavaScript Chunk 是如何被渲染出来的
前端·rust·源码