Android Freeform 自由窗口功能演进分析(Android 14 → 17)

本文主要介绍在高版本AOSP上,freeform窗口模式的功能演进,因为在几个版本上都有一定的实践和研究,现在记录主要用于Android桌面功能的这个模式的一些特性分析。前4个部分是Android 14 的架构演进,总结了部分WMShell的功能,后4个部分是Android 17的代码和特性分析。如果只想了解Android 17桌面化的功能演示,可以直接看最后第8节。

目录

  1. Android 14 之前的架构演进

  2. Android 14 窗口装饰实现对比

  3. Android 14 过渡动画管理对比

  4. Android 14 交互逻辑对比

  5. Android 17 新增功能分析

  6. Android 17 Desktop Mode 包架构

  7. Android 17 总结与最佳实践

  8. Android 17 功能演示和实现分析


1. Android 14之前的架构演进

1.1 背景:从 WMCore 到 Shell 的迁移

Android 14 之前,自由窗口的控制逻辑主要驻留在 WindowManagerService(WMCore) 进程内,通过 DecorCaptionView(运行在 SystemUI 进程的客户端 View)来渲染窗口标题栏。这种设计存在以下问题:

  • 进程耦合度高:窗口装饰(标题栏)的触摸事件需要跨进程传递给 WMCore 处理

  • 动画不统一:自由窗口的打开/关闭/最大化等过渡动画缺乏统一的编排机制

  • 扩展困难:添加新的窗口操作(如桌面模式)需要修改多个进程代码

从Android 14起开始默认启用 Shell Transitions 基础设施,几乎所有的全局过渡(App 切换、旋转、分屏、自由窗口)都迁移到了这个新框架下。使用了新的 Shell 进程(libs/WindowManager/Shell),来实现窗口管理和动画变换的职责分离。

1.2 核心注入点:FreeformComponents

FreeformComponents.java(2022年引入)作为 Dagger 依赖注入的统一入口点,封装了四个核心组件:

// FreeformComponents.java:29-33
public class FreeformComponents {
    public final ShellTaskOrganizer.TaskListener mTaskListener;
    public final Optional<Transitions.TransitionHandler> mTransitionHandler;   // 动画播放
    public final Optional<Transitions.TransitionObserver> mTransitionObserver; // 状态观察
    public final Optional<FreeformTaskTransitionStarterInitializer> mTransitionStarterInitializer;
}// FreeformComponents.java:29-33
public class FreeformComponents {
    public final ShellTaskOrganizer.TaskListener mTaskListener;
    public final Optional<Transitions.TransitionHandler> mTransitionHandler;   // 动画播放
    public final Optional<Transitions.TransitionObserver> mTransitionObserver; // 状态观察
    public final Optional<FreeformTaskTransitionStarterInitializer> mTransitionStarterInitializer;
}

其设计意图在于避免向 Dagger 模块暴露实现细节("single injection point of all freeform classes")。

1.3 Shell 与 SystemUI 的桥接:WMShell

WMShell.java(位于 packages/SystemUI,非 libs/WindowManager)作为 SystemUI 进程中的服务,负责:

  • 桥接 Shell 导出的接口(Pip, SplitScreen, DesktopMode, RecentTasks 等)

  • 管理 SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE 标志位,用于同步自由窗口激活状态到 SystemUI(如导航栏、状态栏的响应)

  • 处理 SystemUI 侧的状态回调(如 Keyguard 状态、用户切换)

// WMShell.java:25
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;

1.4 版本演进对比表

|
维度
|
Android 13 及以前
|
Android 14+
|
| --- | --- | --- |
|
进程归属
|
WMCore (SystemServer)
|
Shell 进程
|
|
依赖注入
|
无统一入口
|
FreeformComponents
|
|
动画编排
|
分散在各处,缺乏统一机制
|
Transitions 统一引擎
|
|
窗口装饰渲染
|
客户端 View (DecorCaptionView)
|
Shell 端 Surface 直接渲染
|
|
SystemUI 集成
|
紧耦合
|
WMShell 桥接
|
|
扩展性
|
修改多个进程
|
注册 Handler/Observer 即可
|


2. Android 14前后窗口装饰实现对比

2.1 旧方案:DecorCaptionView(Android 13 及以前)

在 Android 17 代码库中,DecorCaptionView 已被完全移除(整个 repo 无匹配)。

该方案的特点:

  • 运行在 SystemUI 进程,是一个普通的 Android View

  • 标题栏按钮(最大化/最小化/关闭)的点击事件通过 IPC(Binder)回调到 WMCore

  • 拖拽移动通过 View.OnTouchListener 处理,位置更新通过 WindowManager.updateViewLayout() 传递

  • 进程间延迟较高,拖拽流畅度受限

2.2 新方案:CaptionWindowDecorViewModel + CaptionWindowDecoration

Android 14+ 完全重写了窗口装饰系统,核心由两个类组成:

2.2.1 CaptionWindowDecorViewModel(主控类)

// CaptionWindowDecorViewModel.java:83
public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusTransitionListener

作为 ViewModel,它负责:

  • 生命周期管理:通过 mWindowDecorByTaskId(SparseArray)维护每个 Freeform 任务的装饰实例

  • 创建装饰:createWindowDecoration() 方法为每个任务创建 CaptionWindowDecoration + FluidResizeTaskPositioner + CaptionTouchEventListener 三元组

  • 决定是否显示:shouldShowWindowDecor() 检查窗口模式(必须是 FREEFORM)、Display 存在性等

  • 手势排除区域:监听 ISystemGestureExclusionListener,处理系统手势与自定义标题栏区域的交互

2.2.2 CaptionWindowDecoration(视觉与行为)

// CaptionWindowDecoration.java:81
public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout>

继承自 WindowDecoration,负责:

  • 标题栏渲染:通过 R.layout.caption_window_decor 布局呈现四个按钮(Back / Minimize / Maximize / Close)

  • 阴影与聚焦状态:根据 hasGlobalFocus 切换聚焦/非聚焦阴影厚度

  • 拖拽缩放监听:通过 DragResizeInputListener 监听窗口边缘的 resize 手势

  • 自定义标题栏支持:当 App 声明透明标题栏时,通过 INPUT_FEATURE_SPY 让事件透传到 App 窗口

2.2.3 关键差异对比

|
维度
|
DecorCaptionView (旧)
|
CaptionWindowDecorViewModel + CaptionWindowDecoration (新)
|
| --- | --- | --- |
|
进程归属
|
SystemUI 进程
|
Shell 进程
|
|
渲染方式
|
View.draw() → SurfaceFlinger
|
SurfaceControl 直接操作
|
|
拖拽处理
|
View.onTouchEvent
|
FluidResizeTaskPositioner + DragDetector
|
|
动画同步
|
无统一机制
|
通过 Transitions 与 WMCore 同步
|
|
手势分离
|
View 层级处理
|
ISystemGestureExclusionListener 协调
|
|
标题栏透明度
|
不支持
|
INPUT_FEATURE_SPY 透传
|
|
事件分发
|
Android View 层级
|
DragResizeInputListener 直接监听 InputChannel
|

2.3 事件分发路径对比

旧方案(DecorCaptionView)

InputDispatcher → ViewRootImpl → DecorCaptionView.onTouchEvent()
    → if (按钮点击) → IPC Binder → WMCore
    → if (拖拽移动) → WindowManager.updateViewLayout() → WMCore.updateTaskBounds()

新方案(CaptionWindowDecorViewModel)

InputDispatcher → DragResizeInputListener (InputChannel)
    → if (resize 区域) → FluidResizeTaskPositioner.onDragPositioningMove()
        → SurfaceControl.Transaction.setPosition() (直接更新)
    → if (标题栏按钮) → TaskOperations.maximizeTask/closeTask/minimizeTask()
        → FreeformTaskTransitionStarter → Transitions.startTransition()

3. Android 14前后过渡动画管理对比

3.1 Transitions 核心引擎

Transitions.java(2021年引入,2355行)是整个 Shell 过渡动画的中央调度器,其生命周期如下:

--start→ PENDING --onTransitionReady→ READY --play→ ACTIVE --finish→ |
                                                   --merge→ MERGED ──^

关键设计特点:

  • Track 机制:多个 Track 可并行播放动画,同一 Track 内动画串行执行

  • Handler 链:mHandlers 列表从高到低优先级遍历,第一个 startAnimation() 返回 true 的 Handler 接管动画

  • Merge 机制:当动画播放中有新过渡 Ready 时,尝试 mergeAnimation() 合并,避免动画冲突

  • 自定义过渡类型:定义了 TRANSIT_MAXIMIZE、TRANSIT_RESTORE_FROM_MAXIMIZE、TRANSIT_MINIMIZE 等自由窗口专用类型

// Transitions.java:178-182
public static final int TRANSIT_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 8;
public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
public static final int TRANSIT_MINIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 20;

3.2 FreeformTaskTransitionHandler(动画执行者)

FreeformTaskTransitionHandler 实现了 TransitionHandler 接口,接管自由窗口的专用动画:

// FreeformTaskTransitionHandler.java:56-57
public class FreeformTaskTransitionHandler
        implements Transitions.TransitionHandler, FreeformTaskTransitionStarter

核心职责:

  • startWindowingModeTransition():最大化/还原 → 映射到 TRANSIT_MAXIMIZE / TRANSIT_RESTORE_FROM_MAXIMIZE

  • startMinimizedModeTransition():最小化 → 使用 MinimizeAnimator 执行缩小到图标位移动画

  • startRemoveTransition():关闭 → 使用 ValueAnimator 执行窗口向下滑出屏幕动画(CLOSE_ANIM_DURATION = 400ms)

  • startPipTransition():画中画切换

所有动画通过 mAnimations(ArrayMap<IBinder, ArrayList>)管理,使用 mAnimExecutor 在动画线程执行。

3.3 FreeformTaskTransitionObserver(状态观察者)

FreeformTaskTransitionObserver 实现了 TransitionObserver 接口,不直接操作动画,而是响应过渡事件来管理窗口装饰的生命周期

// FreeformTaskTransitionObserver.java:55
public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver

事件分发逻辑:

|
过渡模式
|
处理逻辑
|
| --- | --- |
|
TRANSIT_OPEN
|
mWindowDecorViewModel.onTaskOpening() 创建装饰
|
|
TRANSIT_CLOSE
|
mWindowDecorViewModel.onTaskClosing() + 收集 taskInfo
|
|
TRANSIT_CHANGE
|
mWindowDecorViewModel.onTaskChanging() 更新装饰
|
|
TRANSIT_TO_FRONT
|
mWindowDecorViewModel.onTaskChanging() + 取消挂起隐藏
|
|
TRANSIT_TO_BACK
|
标记为 mPendingHiddenTasks(等待 Recents 过渡完成)
|
|
过渡完成
|
mWindowDecorViewModel.destroyWindowDecoration() 清理关闭的任务
|

此外,它还集成了 DesktopInOrderTransitionObserver 和 FreeformFallbackTransitionObserver 来支持桌面模式。

3.4 旧方案对比

在 Android 13 及以前,自由窗口的过渡动画通常通过 WindowManagerService 内部的 WindowAnimator 或 SurfaceAnimator 执行,缺乏统一的 Shell 端协调机制。窗口装饰的变化(如标题栏在最大化/还原时的切换)需要额外的回调链路。

|
维度
|
旧方案
|
新方案
|
| --- | --- | --- |
|
动画引擎
|
WMCore WindowAnimator
|
Shell Transitions
|
|
动画定义
|
系统预定义,扩展困难
|
TransitionHandler 接口,自由扩展
|
|
动画与状态同步
|
手动回调
|
TransitionObserver 自动通知
|
|
并行能力
|
有限
|
Track 机制支持多路并行
|
|
Merge 支持
|

|
内建 merge 合并机制
|
|
窗口装饰联动
|
分离处理
|
Observer 统一协调
|


4. Android 14前后交互逻辑对比

4.1 旧方案:View 层级触摸处理

Android 13 及以前,窗口拖拽调整通过 DecorCaptionView 的 OnTouchListener 处理:

  • 触摸事件按标准 Android View 层级分发

  • 每次拖拽移动需要 IPC 调用 WindowManager 更新位置

  • 拖拽过程中无法流畅渲染,因为每次位置更新都需要等待 WMCore 处理

4.2 新方案:FluidResizeTaskPositioner + DragPositioningCallbackUtility

4.2.1 FluidResizeTaskPositioner(流畅拖拽引擎)

// FluidResizeTaskPositioner.java:62
class FluidResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler

同时实现了 TaskPositioner(拖拽回调)和 TransitionHandler(动画回调),实现拖拽全链路的闭环。

拖拽生命周期

// FluidResizeTaskPositioner.java:116  - 拖拽开始
onDragPositioningStart(ctrlType, displayId, x, y, inputMethodType):
    1. 保存 taskBoundsAtDragStart(初始边界)
    2. 保存 repositionStartPoint(触摸起始点)
    3. 如果是 resize 操作 → 开始 CUJ_DESKTOP_MODE_RESIZE_WINDOW Jank 监控
    4. 获取 stableBounds(显示区域限制)
​
// FluidResizeTaskPositioner.java:138  - 拖拽中(实时更新)
onDragPositioningMove(displayId, x, y):
    1. 计算 delta = currentPoint - startPoint
    2. changeBounds() → 根据 ctrlType 调整四边
    3. IF resize:
       - 首次变化时 → wct.setDragResizing(true) 通知 WMCore
       - wct.setBounds(token, newBounds) → 通过 ShellTaskOrganizer 应用
    4. IF 纯移动:
       - SurfaceControl.Transaction.setPosition() 直接更新 Surface
       - 开始 CUJ_DESKTOP_MODE_DRAG_WINDOW Jank 监控
​
// FluidResizeTaskPositioner.java:172  - 拖拽结束
onDragPositioningEnd(displayId, x, y):
    1. IF resize: wct.setDragResizing(false) + mTransitions.startTransition(TRANSIT_CHANGE, wct, this)
    2. IF 纯移动: updateTaskBounds() 计算最终位置
    3. 同时作为 TransitionHandler.startAnimation() 设置 crop 和 position

关键创新:拖拽过程中直接操作 SurfaceControl.Transaction 更新 Surface 位置,绕过 WMCore 中间层,实现零延迟流畅拖拽。拖拽结束后才通过 Transitions.startTransition() 通知 WMCore 确认最终位置。

4.2.2 DragPositioningCallbackUtility(计算工具类)

// DragPositioningCallbackUtility.java:60
public class DragPositioningCallbackUtility

提供纯静态工具方法:

  • calculateDelta(x, y, startPoint):计算输入偏移量

  • changeBounds(ctrlType, ...):根据控制类型(LEFT/RIGHT/TOP/BOTTOM)和 delta 计算新边界,同时处理:

    • 最小/最大尺寸约束:调用 isViolatingWidthConstraints() / isViolatingHeightConstraints()

    • Stable bounds 限制:窗口不能拖出屏幕可用区域

    • 不可缩放应用:isResizeable == false 时回退所有边界变更

    • Pinned Layer 约束:最大不超过屏幕 70%

    • 桌面模式尺寸约束:desktop_mode_minimum_window_width/height

  • setPositionOnDrag():纯移动场景直接更新 Surface 位置

  • snapTaskBoundsIfNecessary():拖拽结束后将窗口吸附到有效区域内

  • getInputMethodFromMotionEvent():检测输入方式(触摸/鼠标/触控笔/触控板)

4.2.3 事件分发路径对比

┌─── 旧方案 ──────────────────────────────────────────┐
│  InputDispatcher                                      │
│    → ViewRootImpl                                     │
│      → DecorCaptionView.onTouchEvent()                │
│        → WindowManager IPC (每次移动都跨进程)           │
│          → WMCore.updateTaskBounds()                   │
│            → SurfaceFlinger                            │
└──────────────────────────────────────────────────────┘
​
┌─── 新方案 ──────────────────────────────────────────┐
│  InputDispatcher                                      │
│    → DragResizeInputListener (直接监听 InputChannel)    │
│      ├── 标题栏拖拽 → DragDetector.onMotionEvent()     │
│      │    → CaptionTouchEventListener.handleMotionEvent│
│      │      → FluidResizeTaskPositioner.onDragXxx()   │
│      │        → SurfaceControl.setPosition() (直接)     │
│      │
│      ├── 边缘缩放 → DragResizeInputListener              │
│      │    → FluidResizeTaskPositioner.onDragXxx()       │
│      │      → WindowContainerTransaction (批量更新)      │
│      │        → ShellTaskOrganizer.applyTransaction()  │
│      │
│      └── 按钮点击 → CaptionTouchEventListener.onClick() │
│           → TaskOperations.maximizeTask/closeTask()    │
│             → FreeformTaskTransitionHandler.startXxx() │
│               → Transitions.startTransition()          │
│                 → WMCore 过渡处理                       │
└──────────────────────────────────────────────────────┘

5. Android 17 新增功能分析

5.1 从 Freeform 到 Desktop Windowing Mode 的质变

Android 14 实现的是基础的 Freeform Windowing(窗口可浮动、可拖拽调整),而 Android 17 将其扩展为完整的 Desktop Windowing Mode(桌面窗口管理模式)。关键转变在于从"单个窗口的管理"升级到"多个窗口在虚拟桌面生态系统中的协调管理"。

5.2 双轨功能标志系统

Android 17 引入了两套并行的功能标志体系:

稳定功能标志:DesktopModeFlags

// core/java/android/window/DesktopModeFlags.java
public enum DesktopModeFlags {
    ENABLE_DESKTOP_WINDOWING_MODE,              // 主开关(默认开启)
    ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS,   // 尺寸约束
    ENABLE_DESKTOP_WINDOWING_PERSISTENCE,        // 跨会话持久化
    ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, // 桌面壁纸
    ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,    // 返回键最小化
    ENABLE_DESKTOP_APP_HANDLE_ANIMATION,         // App Handle 动画
    ENABLE_TASKBAR_OVERFLOW,                     // 任务栏溢出(默认关闭)
    ENABLE_TASK_STACK_OBSERVER_IN_SHELL,         // Shell 内任务栈观察
}

可通过 Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES 开发者选项全局覆盖。

实验性功能标志:DesktopExperienceFlags

// core/java/android/window/DesktopExperienceFlags.java
public enum DesktopExperienceFlags {
    ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE,                  // 外接显示器桌面
    ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS,            // 自由窗口上的任务栏
    ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,                  // 内容模式管理
    ENABLE_DISPLAY_DISCONNECT_INTERACTION,                   // 显示器断开处理
    ENABLE_DISPLAY_RECONNECT_INTERACTION,                    // 显示器重连处理
    ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING,                 // 显示模式切换
    ENABLE_WINDOW_DECORATION_REFACTOR,                       // 窗口装饰重构(DefaultWindowDecoration)
    ENABLE_FREEFORM_BOX_SHADOWS_V2,                          // V2 盒阴影
    ENABLE_ADD_WINDOW_DECORATION_TO_ALL_TASKS,               // 所有任务添加装饰
    ENABLE_FULLSCREEN_WINDOW_CONTROLS,                       // 全屏窗口控件
    ENABLE_INTERACTIVE_PICTURE_IN_PICTURE,                   // 交互式画中画
    ENABLE_MULTIPLE_DESKTOPS_ACTIVATION_IN_DESKTOP_FIRST_DISPLAYS, // 桌面优先显示器的多桌面
    // ... 共 30+ 个标志
}

通过 persist.wm.debug.desktop_experience_devopts 系统属性控制。

5.3 桌面沉浸模式(Desktop Immersive Controller)

// DesktopImmersiveController.kt
class DesktopImmersiveController(...)

功能:允许 Freeform 任务暂时占据全屏尺寸,而窗口标题栏(App Header)自动滑出屏幕(隐藏到状态栏后方),但任务保持 WINDOWING_MODE_FREEFORM 模式不变——未实际切换到全屏模式。

核心方法

  • moveTaskToImmersive(taskInfo) — 进入沉浸态,保存原始边界 boundsBeforeFullImmersive

  • moveTaskToNonImmersive(taskInfo, reason) — 退出沉浸态,恢复原始边界

  • exitImmersiveIfApplicable(wct, displayId, reason) — 当有新任务启动或任务最小化时自动退出沉浸

  • animateResizeChange() — 使用 DecelerateInterpolator(336ms)平滑过渡

退出原因:APP_NOT_IMMERSIVE、USER_INTERACTION、TASK_LAUNCH、MINIMIZED、CLOSED

与 Android 14 的差异:Android 14 中不存在此概念。窗口要么是完全的 Freeform 要么是完全的全屏,没有"保持 Freeform 身份却占全屏尺寸"的中间态。

5.4 主屏幕 Peek 机制(Home Screen Peeking)

// DesktopHomeScreenPeekController.kt
class DesktopHomeScreenPeekController(...)

功能:将所有可见桌面任务暂时滑向屏幕边缘,露出底层的主屏幕(Launcher),每项任务在屏幕外保留一小部分可见。由 Flags.enableHomeScreenPeeking() 控制。

核心方法

  • peek() — 根据任务中心点位置,将任务滑向左侧或右侧

  • unpeek() — 将任务恢复到原始位置

  • calculatePeekBounds(task) — 计算每项任务的滑出目标位置

实现原理:直接通过 WindowContainerTransaction.setBounds() 调整每个任务的位置,借助动画使其平滑移动。是 Android 17 的全新概念。

5.5 任务数量限制与自动最小化(Desktop Tasks Limiter)

// DesktopTasksLimiter.kt
class DesktopTasksLimiter(...)

功能:限制桌面模式下同时可见的最大任务数。当新任务启动超出限制时,自动将最旧的后台任务最小化。通过 DesktopConfig.maxTaskLimit(来自 config_maxDesktopWindowingActiveTasks 资源或 persist.wm.debug.desktop_max_task_limit 系统属性)配置。

核心方法

  • addAndGetMinimizeTaskChanges(deskId, wct, newFrontTaskId) — 判断是否有任务需要最小化

  • getTaskIdToMinimize(visibleOrderedTasks) — 找出最旧的后台任务

  • addPendingMinimizeChange() / addPendingUnminimizeChange() — 跟踪最小化/恢复过渡状态

  • LeftoverMinimizedTasksRemover — 清理关闭返回导航时的残留任务

与 Android 14 的差异:Android 14 没有任务数量限制概念,所有 Freeform 任务平等共存。

5.6 返回键最小化(Back Navigation Minimization)

// DesktopBackNavTransitionObserver.kt
class DesktopBackNavTransitionObserver(...)

功能:用户按返回键时不关闭任务,而是将任务最小化并保持在桌面中。由 DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION 控制。

实现逻辑

  • handleBackNavigation(transition, info) — 检测 TRANSIT_CLOSE 或 TRANSIT_TO_BACK 过渡

  • findTaskToMinimize(info) — 确定被关闭任务的最顶部 Freeform 任务

  • getMinimizingTaskForClosingTransition() — 为 TRANSIT_CLOSE 模式最小化

  • getMinimizingTaskForToBackTransition() — 为 TRANSIT_TO_BACK 模式最小化

与 Android 14 的差异:Android 14 中按返回键会直接 finish Activity / remove Task。此特性模拟桌面操作系统的行为。

5.7 窗口快贴/平铺(Window Tiling/Snapping)

// SnapController.kt — 委托到 SnapEventHandler 实现
class SnapController(private val desktopTasksController: DesktopTasksController)

功能:将桌面窗口快速贴靠到半屏(左/右),支持跨桌面持久化。由 DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE 控制持久化。

SnapEventHandler 接口定义了完整的平铺操作:

  • snapToHalfScreen() — 拖拽快贴到半屏

  • snapPersistedTaskToHalfScreen() — 从持久化恢复平铺

  • removeTaskIfTiled() — 任务退出时取消平铺

  • moveTaskToFrontIfTiled() — 将平铺任务置顶

  • getLeftSnapBoundsIfTiled() / getRightSnapBoundsIfTiled() — 平铺几何信息

  • getDividerBounds() — 分隔条位置

  • onDisplayLayoutChange() / onDeskActivated() / onDeskSwitchAnimationStarting() — 生命周期钩子

与 Android 14 的差异:Android 14 仅支持 Split-Screen(均分),桌面平铺与多桌面、持久化、任务最小化深度集成。

5.8 多桌面支持(Multiple Desks)

// DesksOrganizer.kt、DesksController.kt、DesksTransitionObserver.kt
// 包:com.android.wm.shell.desktopmode.multidesks

功能:支持创建、切换、删除虚拟桌面,每个桌面有独立的任务集合和平铺状态。

核心组件

|
组件
|
职责
|
| --- | --- |
|
DesksController
|
桌面创建/删除逻辑,通过 WindowContainerTransaction 创建虚拟桌面容器
|
|
DesksOrganizer
|
桌面操作的抽象接口,管理桌面容器的创建/激活/停用/移除/任务迁移
|
|
DesksTransitionObserver
|
响应桌面生命周期的过渡事件,协调装饰的创建/销毁
|
|
DeskSwitchTransitionHandler
|
桌面切换动画(配合 DeskWallpaperAnimator)
|
|
Desk.kt
|
桌面数据模型:deskId、displayId、activeTasks、visibleTasks、minimizedTasks、freeformTasksInZOrder、tiledTaskIds
|

与 Android 14 的差异:Android 14 无多桌面概念,所有任务在同一平面共存。

5.9 热插拔显示器管理

// DesktopDisplayEventHandler.kt
// DisplayDisconnectTransitionHandler.kt
// DesktopDisplayModeController.kt (desktopfirst 包)

功能

  • 显示器断开:自动迁移任务到主显示器,保留桌面和任务状态

  • 显示器重连:恢复之前的桌面布局(如果持久化启用)

  • Desktop-First 显示切换:根据输入设备类型自动切换显示器窗口模式

通过 DesktopExperienceFlags.ENABLE_DISPLAY_DISCONNECT_INTERACTION / ENABLE_DISPLAY_RECONNECT_INTERACTION / ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING 控制。

5.10 桌面壁纸活动(Desktop Wallpaper Activity)

// DesktopWallpaperActivity.kt — 透明 Fragment 活动,显示在自由窗口下方
// DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY 控制
// DesktopExperienceFlags.ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY 支持多显示器

功能:在桌面窗口下方渲染透明壁纸,提供完整的桌面视觉体验。支持 Scrim(遮罩)效果——当窗口最大化或平铺时变暗壁纸(DIMMING_WALLPAPER_FOR_MAXIMIZED_AND_TILED 标志)。

5.11 窗口装饰重构(Window Decoration Refactor)

// WindowDecoration.java — @Deprecated 基类(938 行)
// DesktopModeWindowDecoration.java — 桌面模式专用装饰(2110 行)
// DefaultWindowDecoration — 新的默认装饰路径(通过 ENABLE_WINDOW_DECORATION_REFACTOR 切换)

功能:Android 17 引入 DesktopModeWindowDecoration 作为 WindowDecoration 的桌面模式专用子类,同时通过 ENABLE_WINDOW_DECORATION_REFACTOR 标志引入 DefaultWindowDecoration 替代重构路径。

关键差异:

  • V2 盒阴影系统(ENABLE_FREEFORM_BOX_SHADOWS_V2):使用 BoxShadowParamsKeyFocused / BoxShadowParamsAmbientFocused 样式数组替代传统 shadowRadius

  • 两种布局模式:desktop_mode_app_header(Freeform 标题栏)+ desktop_mode_app_handle(全屏/分屏的轻量手柄)

  • 沉浸模式集成:标题栏根据沉浸态调整顶部内边距以适配状态栏偏移

  • App Handle 输入修复(ENABLE_HANDLE_INPUT_FIX):全屏模式下的 App Handle 输入路由优化

  • 标题栏内边距源:FLAG_FORCE_CONSUMING / FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR 兼容多种 App 类型

5.12 PiP(画中画)桌面模式适配

// DesktopPipTransitionController.kt
class DesktopPipTransitionController(...)

功能:当设备处于桌面模式时,PiP 的展开/关闭行为需要适配多桌面、多显示器场景。

关键处理:

  • 展开:检查父任务窗口模式,将 WindowContainerTransaction 调整为桌面友好的窗口模式

  • 桌面重定向:PiP 展开时重定向到当前活跃的桌面

  • 跨显示器展开:当 PiP 在外部显示器上而任务需要回到内部显示器时的处理

  • 桌面环境移除:在桌面会话中,PiP 关闭变为直接 Remove 而非 Reorder_to_back


6. Android 17 Desktop Mode 包架构

6.1 包层次结构

Android 17 在 libs/WindowManager/Shell 下增加了 desktopmode/ 顶级包(~85+ 文件),组织结构如下:

libs/WindowManager/Shell/src/com/android/wm/shell/
├── desktopmode/                         # 桌面模式主包
│   ├── api/                             # 公开 API 接口
│   │   ├── DesktopMode.java            # 外部接口 (moveTaskToDesktop, toggleFullscreen 等)
│   │   └── impl/
│   │       ├── DesktopModeImpl.kt      # 委托到 DesktopTasksController
│   │       └── DesktopModeAidlProvider.kt  # AIDL 注册
│   ├── DesktopTasksController.kt       # 主控制器(2500+ 行,70+ 依赖注入)
│   ├── DesktopImmersiveController.kt   # 沉浸模式控制器
│   ├── DesktopScrimController.kt       # 壁纸遮罩控制器
│   ├── DesktopPipTransitionController.kt # PiP 桌面适配
│   ├── DesktopTasksLimiter.kt           # 任务数量限制器
│   ├── SnapController.kt                # 窗口平铺控制器
│   ├── DesktopModeLoggerTransitionObserver.kt  # 事件日志(557 行)
│   ├── DesktopTasksTransitionObserver.kt       # 仓库状态更新(388 行)
│   ├── animation/                       # 过渡动画
│   │   ├── DesktopMixedTransitionHandler.kt    # 父过渡处理器
│   │   ├── EnterDesktopTaskTransitionHandler.java
│   │   ├── ExitDesktopTaskTransitionHandler.java
│   │   ├── ToggleResizeDesktopTaskTransitionHandler.kt
│   │   └── ...(共 10+ 个 Handler)
│   ├── multidesks/                      # 多桌面
│   │   ├── DesksController.kt          # 桌面创建/删除
│   │   ├── DesksOrganizer.kt           # 桌面操作接口
│   │   ├── DesksTransitionObserver.kt  # 桌面生命周期观察
│   │   └── animation/
│   │       ├── DeskWallpaperAnimator.kt
│   │       └── DeskSwitchAnimationUtils.kt
│   ├── desktopfirst/                    # 桌面优先显示器
│   │   ├── DesktopDisplayModeController.kt  # 显示模式切换
│   │   └── DesktopFirstListenerManager.kt
│   ├── homescreenpeeking/               # 主屏幕 Peek
│   │   ├── DesktopHomeScreenPeekController.kt
│   │   └── DesktopHomeScreenPeekTransitionHandler.kt
│   ├── data/                            # 数据层
│   │   ├── DesktopRepository.kt        # 主数据仓库(1647 行)
│   │   ├── DesktopUserRepositories.kt  # 多用户仓库管理
│   │   ├── Desk.kt                     # 桌面数据模型
│   │   ├── DesktopDisplay.kt           # 显示器数据模型
│   │   └── persistence/                 # 持久化存储
│   │       └── DesktopPersistentRepository.kt
│   ├── education/                       # 用户教育
│   │   ├── AppHandleEducationController.kt
│   │   └── AppToWebEducationController.kt
│   └── common/                          # 通用工具
│       └── ToggleTaskSizeUtils.kt
├── shared/desktopmode/                  # 跨进程共享代码
│   ├── DesktopState.kt / DesktopStateImpl.kt     # 桌面功能可用性
│   ├── DesktopConfig.kt / DesktopConfigImpl.kt   # 桌面配置参数
│   ├── DesktopModeTransitionSource.kt            # 过渡来源枚举
│   ├── ProjectedModeState.kt                     # 投影/连接显示器状态
│   └── DesktopFirstListener.kt / DesktopScrimListener.kt
└── windowdecor/                         # 窗口装饰
    ├── WindowDecoration.java            # @Deprecated 基类
    ├── DesktopModeWindowDecoration.java # 桌面模式专用装饰(2110 行)
    ├── DesktopModeWindowDecorViewModel.java  # 桌面模式装饰 ViewModel(2411 行)
    └── CaptionWindowDecorViewModel.java # 传统 Freeform 装饰 ViewModel

6.2 架构分层

┌─────────────────────────────────────────────────────────────┐
│                    公开 API Layer                             │
│  DesktopMode.java → DesktopModeImpl → DesktopTasksController │
│  IDesktopMode.aidl → DesktopModeAidlProvider                 │
├─────────────────────────────────────────────────────────────┤
│                    控制器 Layer                               │
│  DesktopTasksController (2500+ lines, 主编排器)               │
│  ├── DesksController (多桌面管理)                              │
│  ├── SnapController  (平铺控制)                                │
│  ├── DesktopImmersiveController (沉浸态)                      │
│  ├── DesktopScrimController (遮罩)                            │
│  ├── DesktopTasksLimiter (任务限制)                            │
│  ├── DesktopPipTransitionController (PiP 适配)                │
│  └── DesktopDisplayModeController (显示器管理)                 │
├─────────────────────────────────────────────────────────────┤
│                    过渡层                                     │
│  Transitions ← 调度                                            │
│  ├── DesktopMixedTransitionHandler (桌面父级)                  │
│  │   ├── EnterDesktopTaskTransitionHandler                   │
│  │   ├── ExitDesktopTaskTransitionHandler                   │
│  │   ├── ToggleResizeDesktopTaskTransitionHandler            │
│  │   ├── DesktopMinimizationTransitionHandler                │
│  │   └── ...(共 10+ Handler)                                │
│  └── Observers                                               │
│      ├── DesktopInOrderTransitionObserver (顺序协调)           │
│      ├── DesktopTasksTransitionObserver (仓库更新)            │
│      ├── DesksTransitionObserver (桌面生命周期)                │
│      ├── FreeformFallbackTransitionObserver (游离任务恢复)     │
│      ├── DesktopBackNavTransitionObserver (返回导航)           │
│      └── DesktopModeLoggerTransitionObserver (事件日志)        │
├─────────────────────────────────────────────────────────────┤
│                    数据层                                     │
│  DesktopRepository (1647 行) → Desk + DesktopDisplay          │
│  DesktopPersistentRepository (持久化)                         │
│  DesktopUserRepositories (多用户)                             │
├─────────────────────────────────────────────────────────────┤
│                    共享层                                     │
│  DesktopState (功能可用性) + DesktopConfig (参数配置)           │
│  DesktopModeFlags (稳定标志) + DesktopExperienceFlags (实验)    │
└─────────────────────────────────────────────────────────────┘

6.3 观察者执行顺序(DesktopInOrderTransitionObserver)

DesktopInOrderTransitionObserver 按严格顺序调用子观察者,确保状态变更的一致性:


7. Android 17 总结与最佳实践

7.1 演进趋势总结

|
维度
|
Android 14
|
Android 17
|
| --- | --- | --- |
|
进程架构
|
WMCore 集中式 → Shell 进程分布式
|
稳定;新增桌面模式专用控制器
|
|
动画编排
|
Transitions 引擎确立
|
增加 10+ 桌面专用过渡 Handler
|
|
窗口装饰
|
CaptionWindowDecoration(标题栏)
|
DesktopModeWindowDecoration(标题栏+手柄)、DefaultWindowDecoration(重构路径)、V2 盒阴影
|
|
拖拽交互
|
FluidResizeTaskPositioner(流畅拖拽)
|
增加 SnapController(平铺)、ToggleResize(最大化/还原快切)
|
|
窗口管理
|
单层 Freeform 任务
|
多桌面(Desk)、任务限制、最小化/恢复、沉浸态
|
|
系统集成
|
基础状态栏/导航栏协作
|
Peek 主屏幕、壁纸活动、App Handle 教育
|
|
显示器支持
|
基础外接显示器
|
热插拔、内容模式管理、Desktop-First 显示器
|
|
功能开关
|
少数系统属性
|
双轨制:DesktopModeFlags(稳定)+ DesktopExperienceFlags(实验)
|
|
扩展性
|
注册 Handler/Observer
|
增加 Planner 机制(TransitionMixpatcher)、教育系统、事件日志
|

7.2 面向开发者的最佳实践(更新)

  1. 窗口装饰实现

    • 旧方式(Android 13 及以前):继承 DecorCaptionView(已移除)

    • Android 14 方式:实现 WindowDecorViewModel + 使用 CaptionWindowDecoration

    • Android 17 新方式:实现 WindowDecorViewModel + 使用 DesktopModeWindowDecoration(桌面模式)或 DefaultWindowDecoration(重构路径),通过 DesktopModeWindowDecorViewModel 编排。根据 ENABLE_WINDOW_DECORATION_REFACTOR 标志选择路径。

  2. 添加自定义过渡动画

    • 实现 Transitions.TransitionHandler 并通过 addHandler() 注册

    • Android 17 新增:可以同时实现 ITransitionPlanner 并通过 addPlanner() 注册,利用 TransitionMixpatcher 进行多 Planner 编排

    • 自定义过渡类型使用 TRANSIT_DESKTOP_MODE_TYPES + N 的编号空间

  3. 使用功能标志

    • Android 14:通过系统属性 persist.wm.debug.* 控制

    • Android 17:使用 DesktopModeFlags(稳定)或 DesktopExperienceFlags(实验性)枚举值。稳定标志可通过 Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES 全局切换。实验性标志通过 persist.wm.debug.desktop_experience_devopts 控制。

  4. 多桌面集成

    • 通过 DesksOrganizer 接口创建/激活/删除桌面

    • 通过 Desk.kt 数据模型跟踪每个桌面的任务集合和平铺状态

    • 通过 DesksTransitionObserver 响应桌面生命周期事件

    • 配置参数:DesktopConfig.maxTaskLimit(每桌面最大可见任务)、DesktopConfig.maxDeskLimit(每用户最大桌面数)

  5. 实现窗口平铺

    • 实现 SnapEventHandler 接口并通过 SnapController.start() 注册

    • 通过 DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE 启用跨会话持久化

  6. 桌面状态查询

    • 通过 DesktopState 接口查询桌面功能可用性(canEnterDesktopMode、isFreeformEnabled 等)

    • 通过 DesktopConfig 接口获取配置参数(密度覆盖、任务/桌面数量限制等)

    • DesktopState.fromContext(context) 返回 DesktopStateImpl 实例,会综合检查设备标志、系统属性、开发者选项

7.3 核心文件路径索引

|
组件
|
路径
|
| --- | --- |
|
架构入口
| |
|
注入入口
|
libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java
|
|
SystemUI 桥接
|
packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
|
|
过渡引擎
| |
|
过渡引擎
|
libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
|
|
动画处理器
|
libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
|
|
状态观察器
|
libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
|
|
过渡启动接口
|
libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
|
|
窗口装饰
| |
|
WindowDecorViewModel 接口
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
|
|
传统装饰 ViewModel
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
|
|
桌面装饰 ViewModel
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
|
|
装饰基类
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
|
|
桌面装饰实现
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
|
|
传统标题栏装饰
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
|
|
拖拽交互
| |
|
流畅拖拽
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
|
|
拖拽计算工具
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
|
|
任务操作
|
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
|
|
桌面模式核心
| |
|
主控制器
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
|
|
公开 API
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/api/DesktopMode.java
|
|
数据仓库
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepository.kt
|
|
桌面数据模型
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/Desk.kt
|
|
多桌面控制器
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksController.kt
|
|
桌面操作接口
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
|
|
沉浸模式
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
|
|
任务限制器
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
|
|
窗口平铺
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/SnapController.kt
|
|
主屏幕 Peek
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/homescreenpeeking/DesktopHomeScreenPeekController.kt
|
|
返回导航
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavTransitionObserver.kt
|
|
显示器管理
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
|
|
壁纸活动
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivity.kt
|
|
共享/状态
| |
|
桌面状态
|
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopState.kt
|
|
桌面配置
|
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopConfig.kt
|
|
稳定标志
|
core/java/android/window/DesktopModeFlags.java
|
|
实验性标志
|
core/java/android/window/DesktopExperienceFlags.java
|
|
事件日志
| |
|
事件日志
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
|
|
UI 事件日志
|
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
|

8. Android 17 功能演示和实现分析

目录

窗口模式

工作区功能演示

多桌面(多显示器)支持

自定义标题栏内容

8.1 窗口模式

分析 Android 17 桌面窗口的状态切换机制。当前窗口状态可归纳为以下四种:小窗口(Freeform)、最大化窗口(Freeform)、沉浸模式-系统栏显示、沉浸模式-系统栏隐藏。与 Android 14 相比,新增了沉浸式模式(Immersive Mode)。在该模式下,窗口的 WindowMode 为 Fullscreen,窗口会显示在任务栏和状态栏的后方,且只有在此模式下才能控制系统栏的显示与隐藏。

8.1.1 功能演示

新增了沉浸模式,扩展窗口到任务栏和状态栏区域,但是自由窗口的task与这个全屏的task互斥于launcher之间,另外全屏窗口和自由窗口会分别放置在两个task中,即他们的层级不能互相穿插,另外新增了类似windows和linux桌面的窗口快贴/平铺功能,即吸附分屏功能,也会统一放置到split task中,这种区分task的逻辑会与正常桌面有差别。

当前窗口上没有直接的隐藏/显示系统栏的操作入口,另外用户需要通过左侧或顶部弹出的菜单进入或退出沉浸模式,该入口较为隐蔽,不易发现。需要考虑增加快捷键或其他更便捷的操作入口来优化窗口状态切换的完整交互逻辑。

8.1.2 功能分析

分析处理Fullscreen 与 Freeform 任务在层级切换过程中的表现。当前 DesktopTasksController 在切换层级时,会先将 Launcher 移动至顶层,导致窗口层级只能呈现为“Freeform - Launcher - Fullscreen”或“Fullscreen - Launcher - Freeform”两种固定顺序。

如下面的dumpsys信息,自由窗口com.android.documentsui/.files.FilesActivity 和com.android.deskclock/.DeskClock的祖Task都是Desk,他们的层级切换,区别于fullscreen的窗口com.android.settings.root

  * Task{73080d3 #6 name=Desk type=undefined U=0 visible=true visibleRequested=true mode=freeform translucent=true sz=3}
    * Task{2d80eff #64 type=standard I=com.android.documentsui/.files.FilesActivity U=0 rootTaskId=6 visible=true visibleRequested=true mode=freeform translucent=false sz=1}
      * Hist  #0: ActivityRecord{228899417 u0 com.android.documentsui/.files.FilesActivity t64}
    * Task{29dc7d1 #62 type=standard A=10019:com.android.deskclock U=0 rootTaskId=6 visible=true visibleRequested=true mode=freeform translucent=false sz=1}
      * Hist  #0: ActivityRecord{23633499 u0 com.android.deskclock/.DeskClock t62}
    * Task{30a3731 #61 type=standard I=com.android.documentsui/.files.FilesActivity U=0 rootTaskId=6 visible=true visibleRequested=true mode=freeform translucent=false sz=1}
      * Hist  #0: ActivityRecord{111535803 u0 com.android.documentsui/.files.FilesActivity t61}
  * Task{8f5783a #1 type=home U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
    * Task{609f85e #57 type=home I=com.android.launcher3/.uioverrides.QuickstepLauncher U=0 rootTaskId=1 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
      * Hist  #0: ActivityRecord{112244596 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t57}
  * Task{afa6787 #59 type=standard A=1000:com.android.settings.root U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=3}
    * Hist  #2: ActivityRecord{30757037 u0 com.android.settings/.SubSettings t59}
    * Hist  #1: ActivityRecord{70863807 u0 com.android.settings/.SubSettings t59}
    * Hist  #0: ActivityRecord{93925896 u0 com.android.settings/.Settings t59}

8.2 工作区功能演示

17提供类似windows和linux工作区的功能,该功能是在launcher应该中实现的,他是不同于多显示器的功能,是属于launcher的workspace逻辑层。是单个系统 Display 内部的应用分组、视图切换或虚拟空间管理。

​还有control + shift实现应用切换的功能,切换相当顺滑,跟桌面操作很接近了。

8.3 多桌面(多显示器)支持

“多桌面支持”主要是指系统框架层(Framework/WindowManager)对多 Display 和桌面窗口化(Desktop Windowing)的原生支持。从 Android 17 开始,系统可以在特定 Display 上独立开启“桌面模式”。屏幕可以维持 Touch-first(触控为主/全屏)模式,而连接的外接显示器可以切换为 Desktop-first(窗口化/鼠标键盘)模式,拥有独立的系统 Taskbar、自由缩放窗口(Freeform Windows)和状态管理。 他与工作区是两个层面的功能。

|
维度
|
Android 17 系统级多桌面 (Multi-Display / Desktop Windowing)
|
Launcher 上的多桌面/虚拟桌面 (Launcher Pages / Workspaces)
|
| --- | --- | --- |
|
作用层级
|
系统底层 (Framework / WindowManager / SurfaceFlinger)
|
应用层 (Launcher App / SystemUI Shell)
|
|
硬件依赖
|
依赖多个物理 Display 或系统创建的 VirtualDisplay
|
发生在同一个 Display 上(不需要多屏幕)
|
|
窗口管理
|
管理的是独立的 DisplayContent,支持窗口自由浮动、自由缩放、Taskbar 挂载
|
管理的是图标布局或当前 Display 内的窗口集合分组
|
|
交互触发
|
插拔 Type-C/HDMI 扩展屏、投屏或触发系统的桌面模式模式切换
|
在桌面滑动切页、点击快捷键(如 Win+Tab / 三指滑动)切换虚拟工作区
|

8.4 自定义标题栏内容

8.4.1 官方效果

17(或者15、16上)相对于14终于实现了可以自定义的标题栏,这是向桌面看齐的另一重要特性,本项目曾经在14上自已实现一个标题栏SDK,现在看来是不需要了,官方给出了chrome的效果图和视频,安装了后,并没有看到同样的效果,不过参照官方文档,查阅源码,作者实现了同样的效果。

8.4.2 原理分析

Caption bar 不是画在 App 的 Surface 上,而是通过 SurfaceControlViewHost 独立渲染:
Task Surface (App 绘制区)
└── Decoration Container Surface ← TASK_CHILD_LAYER_WINDOW_DECORATIONS
├── Caption Surface (Z=-1) ← 标题栏, 独立 SurfaceControl
└── Input Sink Surface (Z=-2) ← 防止事件穿透到下层窗口

Caption Bar 的 View 结构
自由窗 (caption_window_decor.xml):
WindowDecorLinearLayout (id=caption) ← 拖动区域
├── Button (back_button) ← 返回
├── Space (layout_weight=1) ← 弹性空间, 把按钮推到右边
├── Button (minimize_window) ← 最小化
├── Button (maximize_window) ← 最大化/还原
└── Button (close_window) ← 关闭
桌面模式 (desktop_mode_app_header.xml):
AppHeaderViewHolder
├── App 图标 + 标题文字 (MarqueedTextView)
├── MaximizeButtonView (最大化/还原)
├── HandleMenuButton (菜单按钮)
└── 更多操作按钮
全屏拖动条 (desktop_mode_app_handle.xml):仅一个 caption_handle,浮在窗口顶部,用于拖入桌面模式。

8.4.3 应用实现

可以将比较复杂的View显示到CaptionBar区域,但是系统默认的功能按钮是不能改的。

关键代码步骤如下:

//将captionbar设为透明,            getWindow().getInsetsController().setSystemBarsAppearance(WindowInsetsController
                            .APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
                    WindowInsetsController
                            .APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);

//自定义captionbar布局
        ViewCompat.setOnApplyWindowInsetsListener(root,
                (view, windowInsets) -> {
                    Insets caption = windowInsets.getInsets(WindowInsetsCompat.Type.captionBar());
                    captionBar.setPadding(0, 0, 0, 0);
                    return windowInsets;
                });

//将标题栏的区域设为应用可以点击
        View decorView = getWindow().getDecorView();
        List<Rect> exclusionRects = new ArrayList<>();
        exclusionRects.add(new Rect(0, 0, decorView.getWidth(), 48));
        decorView.setSystemGestureExclusionRects(exclusionRects);
        decorView.getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        List<Rect> rects = new ArrayList<>();
                        rects.add(new Rect(0, 0, decorView.getWidth(), 48));
                        decorView.setSystemGestureExclusionRects(rects);
                    }
                });

//实现拖拽到窗口外部
        Intent intent =
                new Intent(this, MainActivity2.class);
        intent.putExtra(
                "TAB_ID",
                100);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(
                        this,
                        100,
                        intent,
                        PendingIntent.FLAG_IMMUTABLE);
        ClipData clipData =
                new ClipData(
                        new ClipDescription(
                                "tab",
                                new String[]{"application/tab"}),
                        new ClipData.Item.Builder()
                                .setIntentSender(
                                        pendingIntent.getIntentSender())
                                .build());
        View.DragShadowBuilder shadowBuilder =
                new View.DragShadowBuilder(view);
        view.startDragAndDrop(
                clipData,
                shadowBuilder,
                null,
                View.DRAG_FLAG_GLOBAL
                        | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG
        );
                            

参考链接:

https://source.android.com/docs/core/display/desktop-windowing?hl=zh-cn

https://source.android.com/docs/core/display/multi-window?hl=zh-cn#enable-desktop-windowing

https://source.android.com/docs/whatsnew/site-updates?hl=zh-cn&year=2026#aosp-changes

https://developer.android.com/develop/adaptive-apps/guides/support-desktop-windowing?utm_source=chatgpt.com&hl=zh-cn

https://developer.android.com/develop/ui/compose/system/insets?hl=zh-cn

支持已连接的显示屏  |  Jetpack Compose  |  Android Developers

posted @ 2026-09-03 16:43  OpenFDE  阅读(28)  评论(0)    收藏  举报