Android分屏功能实现机制深度解析:Task组织与管理 副标题:从WindowContainer到Task Reparenting的完整技术剖析
日期:2025年10月 目标读者:Android Framework开发者 预计阅读时间:25-30分钟
第1章:引言 想象这样一个场景:用户正在使用微信聊天,突然需要参考邮件中的信息,于是长按最近任务中的Gmail图标,拖拽到屏幕顶部,屏幕瞬间一分为二——微信占据下半屏继续显示对话,Gmail在上半屏展开邮件列表。整个过程行云流水,仿佛两个应用天生就该这样并存。
但在这个看似简单的交互背后,Android Framework经历了一系列复杂而精密的内部操作:
Task是如何从全屏模式重新组织到分屏模式的?
两个独立的应用如何在同一个屏幕上各自运行,互不干扰?
系统如何确保每个应用都能获得正确的显示边界和配置?
当用户拖动分隔线调整比例时,Task的边界是如何实时更新的?
这些问题的答案,都隐藏在Android分屏功能的核心实现机制中——Task组织与管理系统 。
为什么关注Task组织机制? 在Android的多窗口体系中,Task是应用运行的基本单元。理解Task的组织机制,就是理解分屏功能的本质:
架构层面 :Task组织反映了WindowManager Shell的设计哲学
实现层面 :Reparenting、Bounds管理等机制是分屏的技术基石
扩展层面 :掌握Task管理对开发自定义多窗口功能至关重要
调试层面 :Task层次结构是排查分屏问题的第一入口
本文价值 本文将深入Android Framework源码,从Task的视角完整剖析分屏功能的实现机制。你将获得:
✅ 完整的Task层次结构知识 :从DisplayArea到RootTask的组织逻辑
✅ 核心的Reparenting机制理解 :Task如何在不同容器间迁移
✅ 详尽的边界管理分析 :Bounds、AppBounds、MaxBounds的作用
✅ 实用的调试技巧 :如何使用dumpsys追踪Task状态
✅ 关键的设计原则 :WindowContainerTransaction的原子性保证
让我们开始这段深入Framework核心的技术之旅。
【图1-1:分屏界面示意图】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 用户视角: ┌─────────────────────┐ │ Gmail App │ ← 上半屏 │ (邮件列表界面) │ ├─────────────────────┤ ← 分隔线(可拖动) │ WeChat App │ ← 下半屏 │ (聊天界面) │ └─────────────────────┘ 系统视角(Task组织): TaskDisplayArea └── SplitScreen RootTask ├── MainStage RootTask (上半屏区域) │ └── Gmail Task (taskId=123) └── SideStage RootTask (下半屏区域) └── WeChat Task (taskId=456)
第2章:核心概念铺垫 在深入分屏的Task组织机制之前,我们需要建立一些核心概念的认知。这些概念构成了理解分屏实现的知识基础。
2.1 WindowContainer体系:Android窗口管理的骨架 Android的窗口管理系统建立在一个树形的容器体系之上,称为WindowContainer体系 。这个体系的核心思想是:一切皆容器 。
WindowContainer的层次关系 1 2 3 4 5 6 DisplayContent (物理显示设备) └── TaskDisplayArea (Task显示区域,可以有多个) └── RootTask (根任务,也叫TaskFragment) └── Task (应用任务) └── ActivityRecord (Activity实例) └── WindowState (窗口)
每个层级都是一个WindowContainer,既可以作为容器包含子节点,也可以作为被管理的对象存在于父容器中。这种递归的结构设计带来了极大的灵活性。
WindowContainer的核心接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 abstract class WindowContainer <E extends WindowContainer > extends ConfigurationContainer { protected final WindowList<E> mChildren = new WindowList <>(); private WindowContainer mParent; private final WindowConfiguration mWindowConfiguration = new WindowConfiguration (); void addChild (E child, int index) { mChildren.add(index, child); child.setParent(this ); child.onParentChanged(mParent, this ); } void removeChild (E child) { mChildren.remove(child); child.setParent(null ); } @Override public void onConfigurationChanged (Configuration newParentConfig) { super .onConfigurationChanged(newParentConfig); for (int i = mChildren.size() - 1 ; i >= 0 ; i--) { mChildren.get(i).onConfigurationChanged(mMergedOverrideConfiguration); } } }
关键特性 :
配置继承 :子容器默认继承父容器的WindowConfiguration
配置覆盖 :子容器可以覆盖特定配置项(如边界、窗口模式)
变化传播 :父容器的配置变化会自动传播到所有子容器
2.2 Task与RootTask:应用运行的载体 Task的定义 Task 是Android中应用运行的基本单元,它代表了一组相关Activity的集合。从用户角度看,一个Task对应最近任务列表中的一个卡片;从系统角度看,Task是WindowContainer树中的一个重要节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Task extends WindowContainer <WindowContainer> { final int mTaskId; final int mUserId; Intent baseIntent; ComponentName baseActivity; ActivityRecord topRunningActivity; private final WindowConfiguration mWindowConfiguration; private Rect mBounds = new Rect (); boolean mVisible; Task getRootTask () { WindowContainer parent = getParent(); if (parent == null ) return this ; Task parentTask = parent.asTask(); return parentTask == null ? this : parentTask.getRootTask(); } }
RootTask的特殊性 RootTask (根任务)是一种特殊的Task,它不包含Activity,而是作为其他Task的容器存在。可以将RootTask理解为”Task的Task”。
RootTask的作用 :
作为Task的直接父容器,组织Task的层次结构
定义Task的显示区域和窗口模式
在分屏模式中,每个Stage都有自己的RootTask
普通Task vs RootTask :
1 2 3 4 5 6 RootTask (只作为容器) ├── Task A (包含Activity) │ ├── MainActivity │ └── DetailActivity └── Task B (包含Activity) └── SettingsActivity
2.3 分屏相关的关键组件 Stage:分屏的舞台概念 Stage 是分屏模式引入的抽象概念,代表屏幕上的一个独立区域。Android分屏有两个Stage:
MainStage(主舞台) :通常是用户先打开的应用所在区域
SideStage(侧舞台) :通常是用户后拖入的应用所在区域
每个Stage内部都有一个RootTask,用于容纳分配到该Stage的所有Task。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class StageTaskListener implements ShellTaskOrganizer .TaskListener { protected RunningTaskInfo mRootTaskInfo; private final SparseArray<RunningTaskInfo> mChildrenTaskInfo = new SparseArray <>(); private final StageListenerCallbacks mCallbacks; int getChildCount () { return mChildrenTaskInfo.size(); } boolean containsTask (int taskId) { return mChildrenTaskInfo.contains(taskId); } }
StageCoordinator:分屏的总指挥 StageCoordinator 是分屏模式的核心协调者,负责管理MainStage和SideStage的生命周期、Task分配、边界计算等所有分屏相关的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class StageCoordinator implements SplitScreen { private final MainStage mMainStage; private final SideStage mSideStage; private final SplitLayout mSplitLayout; boolean isSplitScreenActive () { return mSideStage.getChildCount() > 0 ; } void startTask (int taskId, @SplitPosition int position, ...) { } }
TaskOrganizer:Task的组织者 TaskOrganizer 是WindowManager Shell提供的接口,允许系统UI进程(SystemUI)组织和管理Task。它是连接Framework层和Shell层的桥梁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ShellTaskOrganizer extends TaskOrganizer { public RunningTaskInfo createRootTask (int displayId, int windowingMode, TaskListener listener) { } public void applyTransaction (WindowContainerTransaction wct) { } }
WindowContainerTransaction:原子操作的保证 WindowContainerTransaction (简称WCT)是一个事务容器,用于批量执行多个WindowContainer操作,保证原子性。
1 2 3 4 5 6 7 8 9 10 11 WindowContainerTransaction wct = new WindowContainerTransaction ();wct.reparent(taskToken, newParentToken, true ) .setBounds(taskToken, newBounds) .setWindowingMode(taskToken, WINDOWING_MODE_MULTI_WINDOW) .reorder(taskToken, true ); mTaskOrganizer.applyTransaction(wct);
WCT的核心价值 :
原子性 :要么全部成功,要么全部失败,不会出现中间状态
批量优化 :减少Binder调用次数,提高性能
一致性 :所有操作在同一个事务中完成,避免状态不一致
2.4 核心概念关系总览 【图2-1:WindowContainer继承体系图】
classDiagram
ConfigurationContainer <|-- WindowContainer
WindowContainer <|-- DisplayContent
WindowContainer <|-- TaskDisplayArea
WindowContainer <|-- Task
WindowContainer <|-- ActivityRecord
class ConfigurationContainer {
+Configuration mConfiguration
+onConfigurationChanged()
}
class WindowContainer~E~ {
+E mParent
+WindowList~E~ mChildren
+addChild()
+removeChild()
+reparent()
}
class DisplayContent {
+int mDisplayId
+DisplayArea
}
class TaskDisplayArea {
+List~Task~ mRootTasks
+创建和管理RootTask
}
class Task {
+TaskInfo
+WindowConfiguration
+Task是RootTask的基类
}
class ActivityRecord {
+ActivityInfo
+Intent
+表示单个Activity
}
TaskDisplayArea --> Task : 包含
Task --> ActivityRecord : 包含
【图2-2:分屏模式关键组件关系图】
graph TB
subgraph SC["StageCoordinator (协调者)"]
subgraph MS["MainStage"]
MSR["RootTask"]
MST1["Task A"]
MST2["Task B"]
MSR --> MST1
MSR --> MST2
end
subgraph SS["SideStage"]
SSR["RootTask"]
SST1["Task C"]
SST2["Task D"]
SSR --> SST1
SSR --> SST2
end
SL["SplitLayout (计算两个Stage的边界)"]
MS --> SL
SS --> SL
end
STO["ShellTaskOrganizer (执行Task操作)"]
WCT["WindowContainerTransaction (事务机制)"]
WMS["WindowManagerService (Framework层)"]
SC --> STO
STO --> WCT
WCT --> WMS
style SC fill:#e1f5ff
style MS fill:#fff4e1
style SS fill:#e8f5e9
style SL fill:#f3e5f5
小结 通过本章,我们建立了理解分屏Task组织机制所需的核心概念:
WindowContainer体系 提供了树形的容器架构
Task和RootTask 是应用运行和组织的基本单元
Stage 将屏幕划分为独立的应用区域
StageCoordinator 统筹分屏的所有逻辑
WindowContainerTransaction 保证操作的原子性
有了这些基础,我们可以深入分析Task在分屏模式下的具体组织方式了。
第3章:Task层次结构详解 理解了核心概念后,让我们深入探究Task在不同窗口模式下的层次结构,以及这种结构在进入分屏模式时如何发生变化。
3.1 正常模式下的Task组织 在全屏模式下,Task的组织相对简单扁平:
【图3-1:全屏模式下的Task层次结构】
graph TB
DC["DisplayContent (displayId=0)"]
TDA["TaskDisplayArea (TDA)"]
RT1["RootTask (Home, ACTIVITY_TYPE_HOME)"]
LT["Launcher Task"]
RT2["RootTask (App1, ACTIVITY_TYPE_STANDARD)"]
WT["WeChat Task (全屏运行)"]
CA["ChatActivity"]
CoA["ContactActivity"]
RT3["RootTask (App2, ACTIVITY_TYPE_STANDARD)"]
GT["Gmail Task (后台,不可见)"]
MA["MailListActivity"]
DC --> TDA
TDA --> RT1
TDA --> RT2
TDA --> RT3
RT1 --> LT
RT2 --> WT
WT --> CA
WT --> CoA
RT3 --> GT
GT --> MA
style DC fill:#e1f5ff
style TDA fill:#fff4e1
style RT1 fill:#e8f5e9
style RT2 fill:#e8f5e9
style RT3 fill:#e8f5e9
style WT fill:#fff9c4
关键特征 :
每个应用Task都有自己独立的RootTask作为父容器
Task的windowingMode为WINDOWING_MODE_FULLSCREEN
Task的bounds等于屏幕的bounds
只有前台Task可见,其他Task被隐藏
3.2 分屏模式下的Task层次变化 当进入分屏模式后,Task的组织结构发生了显著变化:
【图3-2:分屏模式下的Task层次结构】
graph TB
DC["DisplayContent (displayId=0)"]
TDA["TaskDisplayArea (TDA)"]
SSR["RootTask (SplitScreen, WINDOWING_MODE_FULLSCREEN) ← 分屏根容器"]
MSR["MainStage RootTask (WINDOWING_MODE_MULTI_WINDOW)"]
WT["WeChat Task (bounds=[0,0,1080,1080])"]
CA["ChatActivity"]
CoA["ContactActivity"]
BT["Browser Task (后台,bounds继承)"]
BA["BrowserActivity"]
SSTR["SideStage RootTask (WINDOWING_MODE_MULTI_WINDOW)"]
GT["Gmail Task (bounds=[0,1080,1080,2160])"]
MA["MailListActivity"]
HRT["RootTask (Home, ACTIVITY_TYPE_HOME)"]
LT["Launcher Task (被遮挡,不可见)"]
DC --> TDA
TDA --> SSR
TDA --> HRT
SSR --> MSR
SSR --> SSTR
MSR --> WT
MSR --> BT
WT --> CA
WT --> CoA
BT --> BA
SSTR --> GT
GT --> MA
HRT --> LT
style DC fill:#e1f5ff
style TDA fill:#fff4e1
style SSR fill:#ffccbc
style MSR fill:#c8e6c9
style SSTR fill:#c8e6c9
style WT fill:#fff9c4
style GT fill:#fff9c4
关键变化 :
新增分屏根容器 :创建一个single-top的RootTask作为分屏的根容器
引入Stage层级 :MainStage和SideStage成为子容器
Task重新组织 :原本独立的Task被reparent到相应的Stage下
边界分割 :每个Stage有自己的bounds,其下的Task继承这些bounds
窗口模式变化 :Stage的windowingMode变为MULTI_WINDOW
3.3 Task的关键属性详解 3.3.1 WindowConfiguration:Task的窗口配置核心 WindowConfiguration是Task最重要的配置类,它决定了Task的显示方式和行为模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class WindowConfiguration implements Parcelable , Comparable<WindowConfiguration> { public static final int WINDOWING_MODE_UNDEFINED = 0 ; public static final int WINDOWING_MODE_FULLSCREEN = 1 ; public static final int WINDOWING_MODE_PINNED = 2 ; public static final int WINDOWING_MODE_FREEFORM = 5 ; public static final int WINDOWING_MODE_MULTI_WINDOW = 6 ; public static final int ACTIVITY_TYPE_UNDEFINED = 0 ; public static final int ACTIVITY_TYPE_STANDARD = 1 ; public static final int ACTIVITY_TYPE_HOME = 2 ; public static final int ACTIVITY_TYPE_RECENTS = 3 ; public static final int ACTIVITY_TYPE_ASSISTANT = 4 ; public static final int ACTIVITY_TYPE_DREAM = 5 ; private Rect mBounds = new Rect (); private Rect mAppBounds; private Rect mMaxBounds = new Rect (); private int mWindowingMode = WINDOWING_MODE_UNDEFINED; private int mActivityType = ACTIVITY_TYPE_UNDEFINED; private int mRotation = ROTATION_UNDEFINED; private WindowContainerToken mDisplayWindowingMode; public void setBounds (Rect rect) { if (rect == null ) { mBounds.setEmpty(); return ; } mBounds.set(rect); } public void setAppBounds (Rect rect) { if (rect == null ) { mAppBounds = null ; return ; } if (mAppBounds == null ) { mAppBounds = new Rect (); } mAppBounds.set(rect); } public void setWindowingMode (int windowingMode) { mWindowingMode = windowingMode; } public long diff (WindowConfiguration other, boolean compareUndefined) { long changes = 0 ; if (!mBounds.equals(other.mBounds)) { changes |= WINDOW_CONFIG_BOUNDS; } if (mWindowingMode != other.mWindowingMode) { changes |= WINDOW_CONFIG_WINDOWING_MODE; } return changes; } }
三种边界的区别 :
边界类型
含义
使用场景
mBounds
Task在屏幕上的显示区域
分屏时,主舞台的bounds可能是[0,0,1080,1080]
mAppBounds
应用实际可用的绘制区域(扣除系统栏)
如果有状态栏,appBounds可能是[0,75,1080,1080]
mMaxBounds
Task可以扩展到的最大区域
旋转屏幕、折叠屏展开时使用
3.3.2 TaskInfo:Task的完整信息描述 TaskInfo是Task信息的对外表示,通过Binder传递给SystemUI进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public class RunningTaskInfo extends TaskInfo { public int taskId; public boolean isRunning; public boolean isVisible; public Intent baseIntent; public ComponentName baseActivity; public ComponentName topActivity; public boolean topActivityVisible; public final WindowConfiguration configuration = new WindowConfiguration (); public WindowContainerToken token; public Rect bounds = new Rect (); public int displayId; public int userId; public int parentTaskId = INVALID_TASK_ID; public boolean supportsMultiWindow; public int getWindowingMode () { return configuration.windowingMode; } public int getActivityType () { return configuration.activityType; } }
TaskInfo在分屏中的应用 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public void onTaskInfoChanged (RunningTaskInfo taskInfo) { if (!taskInfo.isVisible) { } if (!mChildrenTaskInfo.get(taskInfo.taskId).bounds.equals(taskInfo.bounds)) { } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); }
3.4 层次结构的设计原则 Android分屏的Task层次结构设计遵循几个重要原则:
原则1:配置继承与覆盖 子Task默认继承父容器的WindowConfiguration,但可以选择性覆盖某些属性:
1 2 3 4 5 6 7 wct.setBounds(stageRootToken, new Rect (0 , 0 , 1080 , 1080 )) .setWindowingMode(stageRootToken, WINDOWING_MODE_MULTI_WINDOW); wct.setBounds(childTaskToken, null ) .setWindowingMode(childTaskToken, WINDOWING_MODE_UNDEFINED);
为什么这样设计?
简化管理:只需设置Stage的配置,所有子Task自动生效
动态调整:拖动分隔线时,只需更新Stage边界,子Task自动更新
避免冲突:子Task不保存自己的边界,不会与父容器冲突
原则2:Single-Top根容器 分屏的根RootTask使用single-top模式,保证MainStage和SideStage在同一个父容器下:
好处 :
统一管理:两个Stage作为兄弟节点,方便协调
事件分发:触摸事件可以正确分发到两个Stage
动画同步:进入/退出分屏的动画可以同步执行
原则3:激活条件控制 分屏的激活遵循明确的规则:
1 2 3 4 5 6 7 8 9 10 11 12 boolean isSplitScreenActive () { return mSideStage.getChildCount() > 0 ; }
为什么SideStage优先?
用户意图明确:拖入SideStage是主动进入分屏的信号
避免误触发:防止MainStage自动吸收Task导致意外进入分屏
退出简洁:当SideStage清空时,自动退出分屏,逻辑清晰
小结 本章深入分析了Task的层次结构:
全屏到分屏 的结构演变:从扁平到分层
WindowConfiguration :Task配置的核心数据结构
TaskInfo :Task信息的对外表示
设计原则 :配置继承、single-top容器、激活条件控制
下一章,我们将探究Task如何通过Reparenting机制 在不同容器间迁移,这是分屏组织的核心技术。
第4章:Task重新组织机制 Task在分屏中的重新组织是通过**Reparenting(重新父级化)**机制实现的。这是Android多窗口体系中最核心的技术之一。
4.1 Reparenting机制的核心思想 什么是Task Reparenting? Reparenting 字面意思是”重新指定父级”,在WindowContainer体系中,指的是将一个Task从当前的父容器移动到另一个父容器的操作。
类比理解 :
1 2 3 4 就像孩子从一个班级转到另一个班级: - 孩子本身(Task)没有变化 - 但所属的班级(Parent Container)变了 - 孩子需要遵守新班级的规则(继承新父容器的配置)
技术层面的变化 :
1 2 3 4 5 6 7 8 9 10 TDA └── RootTask A (全屏) └── Task X (WeChat) TDA └── SplitScreen RootTask └── MainStage RootTask └── Task X (WeChat) ← Task X的父容器变了
为什么需要Reparenting? 场景1:进入分屏
用户拖动Task进入分屏区域
Task需要从独立的RootTask移动到Stage的RootTask下
移动后,Task自动继承Stage的边界和窗口模式
场景2:退出分屏
用户关闭分屏模式
Task需要从Stage的RootTask移回独立的RootTask
恢复全屏显示
场景3:Stage间切换
用户将Task从MainStage拖到SideStage
Task需要在两个Stage的RootTask间移动
边界随之改变
核心价值 :
✅ 无需重启应用 :Task迁移过程中,应用保持运行状态
✅ 配置自动继承 :Task自动适应新父容器的配置
✅ 状态完整保留 :Activity栈、返回栈等状态完整保留
4.2 WindowContainerTransaction事务机制 在深入Reparenting实现前,我们需要理解WindowContainerTransaction (WCT)这个关键的事务机制。
为什么需要事务机制? 设想一个场景:进入分屏需要执行以下操作:
创建Stage的RootTask
将Task A reparent到MainStage
设置Task A的边界为上半屏
设置Task A的窗口模式为MULTI_WINDOW
将Task B reparent到SideStage
设置Task B的边界为下半屏
显示分隔线
如果这些操作逐个执行,用户会看到:
Task A先移动,边界还是全屏(闪烁)
然后Task A边界变化(跳变)
Task B出现(延迟)
整个过程不流畅,甚至可能出现中间状态错误
WindowContainerTransaction解决了这个问题 :将多个操作打包成一个原子事务,要么全部成功,要么全部失败。
WindowContainerTransaction的核心实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 public final class WindowContainerTransaction implements Parcelable { private final ArrayMap<IBinder, Change> mChanges = new ArrayMap <>(); private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList <>(); public static class Change { public static final int CHANGE_BOUNDS = 1 ; public static final int CHANGE_WINDOWING_MODE = 1 << 1 ; public static final int CHANGE_FOCUSABLE = 1 << 2 ; public static final int CHANGE_HIDDEN = 1 << 3 ; private int mChangeMask = 0 ; private Rect mBounds; private int mWindowingMode; private boolean mHidden; } public static class HierarchyOp { public static final int HIERARCHY_OP_TYPE_REPARENT = 0 ; public static final int HIERARCHY_OP_TYPE_REORDER = 1 ; public static final int HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT = 2 ; private int mType; private IBinder mContainer; private IBinder mReparent; private boolean mToTop; private int [] mWindowingModes; private int [] mActivityTypes; } @NonNull public WindowContainerTransaction reparent ( @NonNull WindowContainerToken child, @Nullable WindowContainerToken newParent, boolean onTop) { HierarchyOp hop = new HierarchyOp (); hop.mType = HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; hop.mContainer = child.asBinder(); hop.mReparent = newParent != null ? newParent.asBinder() : null ; hop.mToTop = onTop; mHierarchyOps.add(hop); return this ; } @NonNull public WindowContainerTransaction reparentTasks ( @Nullable WindowContainerToken currentParent, @NonNull WindowContainerToken newParent, @Nullable int [] windowingModes, @Nullable int [] activityTypes, boolean onTop, boolean reparentTopOnly) { HierarchyOp hop = new HierarchyOp (); hop.mType = HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; hop.mContainer = currentParent != null ? currentParent.asBinder() : null ; hop.mReparent = newParent.asBinder(); hop.mWindowingModes = windowingModes != null ? windowingModes.clone() : null ; hop.mActivityTypes = activityTypes != null ? activityTypes.clone() : null ; hop.mToTop = onTop; hop.mReparentTopOnly = reparentTopOnly; mHierarchyOps.add(hop); return this ; } @NonNull public WindowContainerTransaction setBounds ( @NonNull WindowContainerToken container, @Nullable Rect bounds) { Change chg = getOrCreateChange(container.asBinder()); if (bounds != null ) { chg.mBounds = new Rect (bounds); } else { chg.mBounds = null ; } chg.mChangeMask |= Change.CHANGE_BOUNDS; return this ; } @NonNull public WindowContainerTransaction setAppBounds ( @NonNull WindowContainerToken container, @Nullable Rect appBounds) { Change chg = getOrCreateChange(container.asBinder()); if (appBounds != null ) { chg.mAppBounds = new Rect (appBounds); } else { chg.mAppBounds = null ; } chg.mChangeMask |= Change.CHANGE_APP_BOUNDS; return this ; } @NonNull public WindowContainerTransaction setWindowingMode ( @NonNull WindowContainerToken container, @WindowingMode int windowingMode) { Change chg = getOrCreateChange(container.asBinder()); chg.mWindowingMode = windowingMode; chg.mChangeMask |= Change.CHANGE_WINDOWING_MODE; return this ; } @NonNull public WindowContainerTransaction reorder ( @NonNull WindowContainerToken container, boolean onTop) { HierarchyOp hop = new HierarchyOp (); hop.mType = HierarchyOp.HIERARCHY_OP_TYPE_REORDER; hop.mContainer = container.asBinder(); hop.mToTop = onTop; mHierarchyOps.add(hop); return this ; } private Change getOrCreateChange (IBinder token) { Change chg = mChanges.get(token); if (chg == null ) { chg = new Change (); mChanges.put(token, chg); } return chg; } }
WindowContainerTransaction的使用模式 模式1:链式构建事务
1 2 3 4 5 6 7 8 9 10 11 WindowContainerTransaction wct = new WindowContainerTransaction ();wct.reparent(taskToken, stageRootToken, true ) .setBounds(taskToken, null ) .setAppBounds(taskToken, null ) .setWindowingMode(taskToken, WINDOWING_MODE_UNDEFINED) .reorder(taskToken, true ); mTaskOrganizer.applyTransaction(wct);
模式2:批量操作
1 2 3 4 5 6 7 8 9 10 11 12 13 WindowContainerTransaction wct = new WindowContainerTransaction ();wct.reparentTasks( null , stageRootToken, new int []{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_MULTI_WINDOW}, new int []{ACTIVITY_TYPE_STANDARD}, true , false ); mTaskOrganizer.applyTransaction(wct);
模式3:事务复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 WindowContainerTransaction wct = new WindowContainerTransaction ();wct.setBounds(mainStageToken, bounds1) .setAppBounds(mainStageToken, bounds1); wct.setBounds(sideStageToken, bounds2) .setAppBounds(sideStageToken, bounds2); wct.setBounds(dividerToken, dividerBounds); mTaskOrganizer.applyTransaction(wct);
【图4-1:WindowContainerTransaction执行流程】
sequenceDiagram
participant UI as SystemUI (Shell进程)
participant TO as TaskOrganizer
participant IPC as Binder IPC
participant WMS as WindowManagerService (System进程)
participant WC as WindowContainer
Note over UI: 构建WCT事务
UI->>UI: reparent(Task A → MainStage)
UI->>UI: setBounds(MainStage, [0,0,1080,1080])
UI->>UI: setBounds(SideStage, [0,1080,1080,2160])
UI->>TO: applyTransaction(wct)
TO->>IPC: Binder调用
IPC->>WMS: 传输WCT数据
Note over WMS: 验证事务合法性
WMS->>WMS: 检查权限
WMS->>WMS: 检查容器存在性
Note over WMS: 开始事务
WMS->>WMS: beginTransaction()
Note over WMS: 执行所有HierarchyOps(按顺序)
WMS->>WC: Task.reparent()
Note right of WC: 修改容器树结构
Note over WMS: 应用所有Changes(批量)
WMS->>WC: WindowContainer.onConfigurationChanged()
Note right of WC: 更新配置
Note over WMS: 提交事务
WMS->>WMS: commitTransaction()
WMS->>WMS: 触发Layout
WMS->>WMS: 触发Surface更新
WMS->>WMS: 通知应用配置变化
WMS->>TO: 回调TaskOrganizer通知结果
TO->>UI: 操作完成
4.3 Task Reparenting核心实现 有了WCT的基础,让我们深入Task.reparent()的实现。
Framework层的reparent()方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 boolean reparent (Task preferredRootTask, int position, @ReparentMoveRootTaskMode int moveRootTaskMode, boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange, String reason) { final Task sourceRootTask = getRootTask(); final ActivityTaskSupervisor supervisor = mAtmService.mTaskSupervisor; final Task toRootTask = supervisor.getReparentTargetRootTask( this , preferredRootTask, position == MAX_VALUE); if (toRootTask == sourceRootTask) { return false ; } ProtoLog.i(WM_DEBUG_TASKS, "reparent: moving taskId=%d from rootTask=%d to rootTask=%d " + "at position=%d reason=%s" , mTaskId, sourceRootTask.mTaskId, toRootTask.mTaskId, position, reason); final boolean moveRootTaskToFront = moveRootTaskMode == REPARENT_MOVE_ROOT_TASK_TO_FRONT || (moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT && (sourceRootTask.isTopRootTaskInDisplayArea() || toRootTask == preferredRootTask)); reparent(toRootTask, position, moveRootTaskToFront, reason); if (moveRootTaskToFront) { toRootTask.moveToFront(reason); if (getTopResumedActivity() != null ) { supervisor.scheduleUpdatePictureInPictureModeIfNeeded(this , sourceRootTask); } } else if (animate) { toRootTask.mTransitionController.requestTransitionIfNeeded(...); } if (!deferResume) { supervisor.ensureActivitiesVisible(null , 0 , PRESERVE_WINDOWS); supervisor.resumeFocusedTasksTopActivities(); } return true ; } @Override void reparent (WindowContainer newParent, int position, boolean moveParents, String reason) { if (newParent == null ) { throw new IllegalArgumentException ("reparent: can't reparent to null" ); } if (getParent() == newParent) { throw new IllegalArgumentException ("Task=" + this + " already child of newParent=" + newParent); } synchronized (mWmService.mGlobalLock) { final WindowContainer oldParent = getParent(); if (oldParent != null ) { oldParent.removeChild(this ); } newParent.addChild(this , position); onParentChanged(oldParent, newParent); getDisplayContent().setLayoutNeeded(); getDisplayContent().getDisplayPolicy().adjustWindowsForEnsureVisible(); mWmService.scheduleAnimationLocked(); } } @Override void onParentChanged (ConfigurationContainer oldParent, ConfigurationContainer newParent) { super .onParentChanged(oldParent, newParent); mRootTask = null ; if (newParent != null ) { onConfigurationChanged(newParent.getConfiguration()); updateDisplayInfo(newParent.getDisplayContent()); } for (int i = mChildren.size() - 1 ; i >= 0 ; --i) { final ActivityRecord activity = (ActivityRecord) mChildren.get(i); activity.onParentChanged(oldParent, newParent); } }
Reparent的核心步骤总结 :
验证目标 :确定实际的目标RootTask
检查必要性 :如果已在目标中,直接返回
修改树结构 :从旧父容器移除,添加到新父容器
配置重算 :触发onParentChanged,重新计算WindowConfiguration
配置传播 :新配置向下传播到所有子Activity
通知应用 :发送配置变化回调给应用进程
更新显示 :请求重新layout和绘制
4.4 Shell层的Task组织操作 在WindowManager Shell中,StageTaskListener使用WCT来组织Task。
添加Task到Stage 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void addTask (ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "StageTaskListener.addTask: taskId=%d" , task.taskId); wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) .setBounds(task.token, null ); wct.reparent(task.token, mRootTaskInfo.token, true ); }
为什么要清除Task的配置?
Task之前可能是全屏模式,有自己的bounds和windowingMode
清除后,Task自动继承Stage的配置
这样当拖动分隔线调整Stage边界时,Task自动跟随变化
批量重组所有符合条件的Task 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void reparentAllEligibleTasks (WindowContainerTransaction wct) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "StageTaskListener.reparentAllEligibleTasks: starting reparent operation" ); wct.reparentTasks( null , mRootTaskInfo.token, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, true , false ); } public static final int [] CONTROLLED_WINDOWING_MODES = { WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_MULTI_WINDOW }; public static final int [] CONTROLLED_ACTIVITY_TYPES = { ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_UNDEFINED };
为什么需要筛选条件?
排除系统UI :Home、Recents等不应该进入分屏
排除特殊模式 :PIP模式的Task不能进入分屏
确保兼容性 :只有明确支持多窗口的Task才能进入
【图4-2:reparentAllEligibleTasks的筛选逻辑】
flowchart TB
Start([系统中的所有Task])
HT["Home Task (ACTIVITY_TYPE_HOME)"]
RT["Recents Task (ACTIVITY_TYPE_RECENTS)"]
PT["PIP Task (WINDOWING_MODE_PINNED)"]
WT["WeChat Task (STANDARD, FULLSCREEN)"]
BT["Browser Task (STANDARD, FULLSCREEN)"]
GT["Gmail Task (STANDARD, MULTI_WINDOW)"]
Filter{"筛选条件 windowingMode in [FULLSCREEN, MULTI_WINDOW] AND activityType in [STANDARD, UNDEFINED]"}
Excluded([✗ 被排除])
ToStage([✓ reparent到Stage])
Start --> HT
Start --> RT
Start --> PT
Start --> WT
Start --> BT
Start --> GT
HT --> Filter
RT --> Filter
PT --> Filter
WT --> Filter
BT --> Filter
GT --> Filter
Filter -- "不符合" --> HT
Filter -- "不符合" --> RT
Filter -- "不符合" --> PT
HT --> Excluded
RT --> Excluded
PT --> Excluded
Filter -- "符合条件" --> WT
Filter -- "符合条件" --> BT
Filter -- "符合条件" --> GT
WT --> ToStage
BT --> ToStage
GT --> ToStage
style HT fill:#ffcdd2
style RT fill:#ffcdd2
style PT fill:#ffcdd2
style WT fill:#c8e6c9
style BT fill:#c8e6c9
style GT fill:#c8e6c9
style Excluded fill:#ffcdd2
style ToStage fill:#c8e6c9
4.5 实际分屏场景的Reparenting流程 让我们通过一个完整场景,串联Reparenting的全过程。
场景:用户将Gmail从最近任务拖入分屏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 void startTask (int taskId, @SplitPosition int position, Bundle options) { WindowContainerTransaction wct = new WindowContainerTransaction (); prepareEnterSplitScreen(wct); final StageTaskListener targetStage = position == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; options.putInt(KEY_LAUNCH_ROOT_TASK_TOKEN, targetStage.mRootTaskInfo.token); mTaskOrganizer.startTask(taskId, options); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { updateSurfaceBounds(null , t, false ); }); } void prepareEnterSplitScreen (WindowContainerTransaction wct) { if (isSplitScreenActive()) { return ; } mMainStage.activate(wct, false ); mSideStage.activate(wct, false ); updateWindowBounds(mSplitLayout, wct); mSplitLayout.update(wct); } void activate (WindowContainerTransaction wct, boolean reparentAllTasks) { if (reparentAllTasks) { reparentAllEligibleTasks(wct); } wct.setHidden(mRootTaskInfo.token, false ); wct.setWindowingMode(mRootTaskInfo.token, WINDOWING_MODE_MULTI_WINDOW); wct.reorder(mRootTaskInfo.token, true ); }
【图4-3:完整的分屏启动流程时序图(含动画与Surface管理)】
sequenceDiagram
participant UI as SystemUI
participant SC as StageCoordinator
participant MS as MainStage
participant SS as SideStage
participant WMS as WindowManagerService
participant SF as SurfaceFlinger
participant App1 as EZWRITE
participant App2 as Maps
Note over UI,App2: 阶段1: 启动准备 (Preparation Phase)
UI->>SC: handleRequest(enter split)
SC->>SC: prepareEnterSplitScreen()
SC->>MS: activate(includeTopTask=true)
MS->>MS: reparentTopTask()
MS-->>SC: 主舞台激活完成
SC->>SC: updateWindowBounds()
Note right of SC: 计算Stage边界 Main: [0,0,1080,1200] Side: [0,1200,1080,2400]
Note over UI,App2: 阶段2: 过渡动画 (Transition Animation Phase)
WMS->>SC: startPendingAnimation()
SC->>SC: 分析过渡变化
Note right of SC: • 识别主舞台任务 • 识别侧舞台任务
SC->>SF: 创建分割线Surface
SC->>SC: setDividerVisibility(true)
SC->>SC: playInternalAnimation()
Note right of SC: • Task位置动画 • 边界缩放动画 • 透明度渐变
Note over UI,App2: 阶段3: 任务同步 (Task Synchronization Phase)
App1->>SC: onTaskAppeared(taskId=69)
Note right of SC: 缓存Task信息
App2->>SC: onTaskAppeared(taskId=70)
Note right of SC: 缓存Task信息
SC->>SC: updateRecentTasksSplitPair()
Note right of SC: 记录分屏组合 [69 <-> 70]
SC->>SC: onTransitionAnimationComplete()
Note right of SC: • 清理过渡状态 • 确认分屏激活 • 通知监听器
Note over UI,App2: 阶段4: 分割线调整 (Divider Adjustment Phase)
SC->>SC: onLayoutSizeChanged()
SC->>SC: updateWindowBounds()
SC->>WMS: 创建调整事务
WMS->>SC: playResizeAnimation()
SC->>SC: onFinish(resize)
SC->>SF: updateSurfaceBounds()
Note right of SF: • MainStage Surface • SideStage Surface • Divider Surface
Note over UI,App2: 分屏启动完成 - 用户可正常交互
时序图关键阶段说明 :
阶段1:启动准备(Preparation)
SystemUI接收用户操作,触发分屏请求
StageCoordinator调用prepareEnterSplitScreen()准备环境
MainStage激活并重新组织顶部Task(reparentTopTask)
计算并设置初始的窗口边界
阶段2:过渡动画(Transition Animation)
WMS触发startPendingAnimation()开始过渡
StageCoordinator分析需要动画的变化:识别主舞台和侧舞台的Task
在SurfaceFlinger中创建分割线Surface
设置分割线可见性并播放内部动画(playInternalAnimation)
阶段3:任务同步(Task Synchronization)
两个应用的Task通过onTaskAppeared回调通知StageCoordinator
更新最近任务列表的分屏配对信息(updateRecentTasksSplitPair)
过渡动画完成回调(onTransitionAnimationComplete)
阶段4:分割线调整(Divider Adjustment)
监听布局尺寸变化(onLayoutSizeChanged)
重新计算窗口边界(updateWindowBounds)
创建调整事务并播放调整动画(playResizeAnimation)
完成后更新Surface边界(updateSurfaceBounds)
小结 本章深入剖析了Task重新组织的核心机制:
Reparenting概念 :Task在容器间迁移,保持运行状态
WindowContainerTransaction :原子事务机制,保证操作一致性
Framework实现 :Task.reparent()修改容器树,触发配置重算
Shell层应用 :StageTaskListener使用WCT组织Task
完整流程 :从用户操作到应用响应的全链路
下一章,我们将分析分屏Task组织的完整生命周期,从创建到销毁的全过程。
第5章:分屏Task组织完整流程 理解了Reparenting机制后,让我们从头到尾追踪一个完整的分屏流程,看看Task组织的各个环节是如何协作的。
5.1 分屏环境初始化 StageCoordinator的创建 分屏功能的初始化发生在SystemUI启动时,SplitScreenController创建Stage Coordinator。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 protected StageCoordinator (Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController imeController, DisplayInsetsController insetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor, Optional<RecentTasksController> recentTasksOptional) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; mTaskOrganizer = taskOrganizer; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this ); mMainStage = new MainStage ( context, mTaskOrganizer, mDisplayId, mMainStageListener, mSyncQueue, mSurfaceSession, iconProvider, mWindowDecorViewModel ); mSideStage = new SideStage ( context, mTaskOrganizer, mDisplayId, mSideStageListener, mSyncQueue, mSurfaceSession, iconProvider, mWindowDecorViewModel ); mSplitLayout = splitLayout; mSplitLayout.init(); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "StageCoordinator initialized for display=%d" , displayId); }
Stage的创建 每个Stage(MainStage和SideStage)在创建时会向WMS请求创建自己的RootTask。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected StageTaskListener (Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, SurfaceSession surfaceSession, IconProvider iconProvider) { mContext = context; mCallbacks = callbacks; mSyncQueue = syncQueue; mSurfaceSession = surfaceSession; taskOrganizer.registerListener(this ); mRootTaskInfo = taskOrganizer.createRootTask( displayId, WINDOWING_MODE_UNDEFINED, this ); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Stage RootTask created: taskId=%d" , mRootTaskInfo.taskId); }
【图5-1:分屏环境初始化结构】
flowchart TB
Boot([系统启动])
SUI[SystemUI启动]
SSC[SplitScreenController初始化]
SC[StageCoordinator创建]
MS[MainStage创建]
SS[SideStage创建]
CRMS[createRootTask WINDOWING_MODE_UNDEFINED]
CRSS[createRootTask WINDOWING_MODE_UNDEFINED]
MSRT[mMainStageRootTask]
SSRT[mSideStageRootTask]
subgraph Initial["初始状态:TaskDisplayArea"]
HRT[Home RootTask 前台可见]
IMSRT[MainStage RootTask 隐藏,无Task]
ISSRT[SideStage RootTask 隐藏,无Task]
end
Boot --> SUI
SUI --> SSC
SSC --> SC
SC --> MS
SC --> SS
MS --> CRMS
SS --> CRSS
CRMS --> MSRT
CRSS --> SSRT
MSRT -.-> IMSRT
SSRT -.-> ISSRT
style Boot fill:#e1f5ff
style SUI fill:#fff4e1
style SSC fill:#e8f5e9
style SC fill:#f3e5f5
style MS fill:#c8e6c9
style SS fill:#c8e6c9
style HRT fill:#fff9c4
style IMSRT fill:#e0e0e0
style ISSRT fill:#e0e0e0
5.2 进入分屏模式 触发进入分屏 用户通过多种方式触发进入分屏:
从最近任务拖拽应用到分屏区域
长按应用多任务按钮选择”分屏”
通过Launcher的分屏快捷操作
这些操作最终都会调用StageCoordinator.startTask()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 void startTask (int taskId, @SplitPosition int position, @Nullable Bundle options) { final WindowContainerTransaction wct = new WindowContainerTransaction (); if (options == null ) { options = new Bundle (); } prepareEnterSplitScreen(wct, null , position); final StageTaskListener targetStage = position == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; options.putBinder(KEY_LAUNCH_ROOT_TASK_TOKEN, targetStage.mRootTaskInfo.token.asBinder()); wct.startTask(taskId, options); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { updateSurfaceBounds(mSplitLayout, t, false ); setDividerVisibility(true , t); }); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: taskId=%d position=%d" , taskId, position); }
准备分屏环境 prepareEnterSplitScreen()负责激活Stage并设置初始布局。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 void prepareEnterSplitScreen (WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) { if (isSplitScreenActive()) { return ; } ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: preparing split screen" ); if (taskInfo != null ) { final WindowContainerToken targetStageToken = startPosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage.mRootTaskInfo.token : mSideStage.mRootTaskInfo.token; wct.reparent(taskInfo.token, targetStageToken, true ); wct.setBounds(taskInfo.token, null ); wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); } mMainStage.activate(wct, false ); mSideStage.activate(wct, false ); updateWindowBounds(mSplitLayout, wct); mSplitLayout.update(wct); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: stages activated" ); }
Stage激活 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void activate (WindowContainerTransaction wct, boolean reparentAllTasks) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Stage.activate: reparentAllTasks=%b" , reparentAllTasks); if (reparentAllTasks) { reparentAllEligibleTasks(wct); } wct.setHidden(mRootTaskInfo.token, false ); wct.setWindowingMode(mRootTaskInfo.token, WINDOWING_MODE_MULTI_WINDOW); wct.reorder(mRootTaskInfo.token, true ); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Stage.activate: stage is now active" ); }
过渡动画的启动 在激活Stage后,系统会启动过渡动画以提供流畅的用户体验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 void startPendingAnimation (IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: analyzing transition" ); final TransitionInfo.Change mainStageChange = identifyMainStageChange(info); final TransitionInfo.Change sideStageChange = identifySideStageChange(info); if (mDividerLeash == null ) { mDividerLeash = createDividerSurface(startT); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: divider surface created" ); } setDividerVisibility(true , startT); playInternalAnimation(mainStageChange, sideStageChange, startT, finishT); } private void playInternalAnimation (TransitionInfo.Change mainChange, TransitionInfo.Change sideChange, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { final long ANIMATION_DURATION = 336 ; final Interpolator INTERPOLATOR = new PathInterpolator (0.2f , 0f , 0f , 1f ); final SurfaceControl mainLeash = mainChange.getLeash(); final SurfaceControl sideLeash = sideChange.getLeash(); ValueAnimator animator = ValueAnimator.ofFloat(0f , 1f ); animator.setDuration(ANIMATION_DURATION); animator.setInterpolator(INTERPOLATOR); animator.addUpdateListener(animation -> { float progress = (float ) animation.getAnimatedValue(); SurfaceControl.Transaction t = new SurfaceControl .Transaction(); animateTaskToStage(t, mainLeash, mSplitLayout.getBounds1(), progress); animateTaskToStage(t, sideLeash, mSplitLayout.getBounds2(), progress); t.setAlpha(mDividerLeash, progress); t.apply(); }); animator.addListener(new AnimatorListenerAdapter () { @Override public void onAnimationEnd (Animator animation) { finishT.apply(); onTransitionAnimationComplete(); } }); animator.start(); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playInternalAnimation: animation started" ); } private void animateTaskToStage (SurfaceControl.Transaction t, SurfaceControl leash, Rect targetBounds, float progress) { float x = lerp(startBounds.left, targetBounds.left, progress); float y = lerp(startBounds.top, targetBounds.top, progress); float width = lerp(startBounds.width(), targetBounds.width(), progress); float height = lerp(startBounds.height(), targetBounds.height(), progress); t.setPosition(leash, x, y); t.setWindowCrop(leash, (int ) width, (int ) height); } private void onTransitionAnimationComplete () { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionAnimationComplete: cleaning up" ); mTransitionState = TRANSITION_STATE_IDLE; if (isSplitScreenActive()) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Split screen is now fully active" ); } for (SplitScreen.SplitScreenListener listener : mListeners) { listener.onSplitBoundsChanged(mSplitLayout.getBounds1(), mSplitLayout.getBounds2()); } }
动画的关键点 :
平滑过渡 :使用PathInterpolator提供流畅的缓动效果
多元素同步 :MainStage、SideStage、Divider的动画同步进行
硬件加速 :直接操作SurfaceControl,充分利用GPU加速
状态管理 :通过onTransitionAnimationComplete()确保动画完成后的状态一致
5.3 Task加入Stage 当Task被启动或reparent到Stage后,StageTaskListener会收到回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 @Override public void onTaskAppeared (ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d isVisible=%b" , taskInfo.taskId, taskInfo.isVisible); mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); mChildrenLeashes.put(taskInfo.taskId, leash); updateChildTaskSurface(taskInfo, leash, true ); mCallbacks.onTaskAppeared(taskInfo); if (mChildrenTaskInfo.size() == 1 ) { mCallbacks.onStageHasChildrenChanged(this ); } } @Override public void onTaskInfoChanged (ActivityManager.RunningTaskInfo taskInfo) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d isVisible=%b bounds=%s" , taskInfo.taskId, taskInfo.isVisible, taskInfo.configuration.windowConfiguration.getBounds()); mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); SurfaceControl leash = mChildrenLeashes.get(taskInfo.taskId); updateChildTaskSurface(taskInfo, leash, false ); mCallbacks.onTaskInfoChanged(taskInfo); } @Override public void onTaskVanished (ActivityManager.RunningTaskInfo taskInfo) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: taskId=%d" , taskInfo.taskId); mChildrenTaskInfo.remove(taskInfo.taskId); SurfaceControl leash = mChildrenLeashes.remove(taskInfo.taskId); if (leash != null ) { SurfaceControl.Transaction t = new SurfaceControl .Transaction(); t.remove(leash); t.apply(); } mCallbacks.onTaskVanished(taskInfo); if (mChildrenTaskInfo.size() == 0 ) { mCallbacks.onStageHasChildrenChanged(this ); } }
StageCoordinator响应Task变化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 private final StageListenerImpl mMainStageListener = new StageListenerImpl () { @Override public void onTaskAppeared (RunningTaskInfo taskInfo) { if (!isSplitScreenActive() && mSideStage.getChildCount() > 0 ) { onSplitScreenEnter(); } } @Override public void onStageHasChildrenChanged (StageTaskListener stage) { if (stage.getChildCount() == 0 && mSideStage.getChildCount() == 0 ) { onSplitScreenExit(); } } }; private final StageListenerImpl mSideStageListener = new StageListenerImpl () { @Override public void onTaskAppeared (RunningTaskInfo taskInfo) { if (mSideStage.getChildCount() == 1 ) { onSplitScreenEnter(); } } @Override public void onStageHasChildrenChanged (StageTaskListener stage) { if (mSideStage.getChildCount() == 0 ) { onSplitScreenExit(); } } }; private void onSplitScreenEnter () { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSplitScreenEnter: split screen is now active" ); for (SplitScreen.SplitScreenListener listener : mListeners) { listener.onSplitVisibilityChanged(true ); } mRecentTasksOptional.ifPresent(recentTasks -> { recentTasks.notifySplitScreenActiveChanged(true ); }); } private void onSplitScreenExit () { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSplitScreenExit: exiting split screen" ); WindowContainerTransaction wct = new WindowContainerTransaction (); exitSplitScreen(null , EXIT_REASON_APP_FINISHED); for (SplitScreen.SplitScreenListener listener : mListeners) { listener.onSplitVisibilityChanged(false ); } }
【图5-2:分屏启动的四阶段流程详解】
flowchart TB
Start([用户操作触发])
subgraph Phase1["阶段1: 启动准备 (Preparation Phase)"]
A1[SystemUI.handleRequest enter split]
A2[StageCoordinator. prepareEnterSplitScreen]
A3[MainStage.activate]
A4[reparentTopTask 将前台Task移入MainStage]
A5[设置MULTI_WINDOW模式]
A6[updateWindowBounds 计算初始边界]
A7["Main: [0,0,1080,1200] Side: [0,1200,1080,2400]"]
end
subgraph Phase2["阶段2: 过渡动画 (Transition Animation Phase)"]
B1[WMS.startPendingAnimation]
B2[StageCoordinator 分析过渡变化]
B3[识别主舞台/侧舞台Task]
B4[SurfaceFlinger 创建分割线Surface]
B5[setDividerVisibility true]
B6[playInternalAnimation]
B7["• Task位置动画 • 边界缩放动画 • 透明度渐变"]
end
subgraph Phase3["阶段3: 任务同步 (Task Synchronization Phase)"]
C1[App1.onTaskAppeared taskId=69, EZWRITE]
C2[StageCoordinator 更新缓存]
C3[App2.onTaskAppeared taskId=70, Maps]
C4[updateRecentTasksSplitPair 记录分屏组合]
C5["[TaskId=69 <-> TaskId=70]"]
C6[onTransitionAnimationComplete]
C7["• 清理过渡状态 • 确认分屏激活 • 通知监听器"]
end
subgraph Phase4["阶段4: 分割线调整 (Divider Adjustment Phase)"]
D1[onLayoutSizeChanged 布局变化监听]
D2[updateWindowBounds 重新计算边界]
D3[创建WCT调整事务]
D4[WMS.playResizeAnimation 播放调整动画]
D5[onFinish resize]
D6[updateSurfaceBounds 同步Surface]
D7["• MainStage Surface • SideStage Surface • Divider Surface"]
end
Complete([分屏启动完成 用户可正常交互])
Start --> A1
A1 --> A2 --> A3 --> A4 --> A5 --> A6 --> A7
A7 --> B1
B1 --> B2 --> B3 --> B4 --> B5 --> B6 --> B7
B7 --> C1
C1 --> C2 --> C3 --> C2
C2 --> C4 --> C5 --> C6 --> C7
C7 --> D1
D1 --> D2 --> D3 --> D4 --> D5 --> D6 --> D7
D7 --> Complete
style Phase1 fill:#e1f5ff
style Phase2 fill:#fff4e1
style Phase3 fill:#e8f5e9
style Phase4 fill:#f3e5f5
style Complete fill:#ffebee
流程关键点说明 :
阶段1的核心 :reparentTopTask()是关键操作,它将当前前台运行的Task重新组织到MainStage,为第二个应用进入SideStage做准备。
阶段2的动画 :使用playInternalAnimation()而不是直接应用配置变化,这样用户看到的是平滑的过渡,而不是突兀的跳变。动画包括:
Task从全屏位置移动到分屏位置
Task边界从全屏尺寸缩放到分屏尺寸
分隔线从透明渐变到可见
阶段3的同步 :onTaskAppeared是异步回调,两个应用的Task可能不会同时到达。StageCoordinator需要:
缓存每个Task的信息
等待两个Task都出现
更新最近任务的分屏配对关系
阶段4的优化 :updateSurfaceBounds()直接操作Surface层,比通过WCT更新配置更高效,适合频繁的边界调整场景。
5.4 动态调整分屏比例 用户拖动分隔线时,Task的边界需要实时更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 void onDraggingDivider (int position) { mDividePosition = position; Rect bounds1 = new Rect (); Rect bounds2 = new Rect (); calculateBounds(position, bounds1, bounds2); SurfaceControl.Transaction t = mTransactionPool.acquire(); applySurfaceChanges(t, bounds1, bounds2, false ); t.apply(); mTransactionPool.release(t); } void onDraggingDividerFinished (int position) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDraggingDividerFinished: position=%d" , position); WindowContainerTransaction wct = new WindowContainerTransaction (); updateWindowBounds(mSplitLayout, wct); mSyncQueue.queue(wct); mSplitLayout.setDividePosition(position, true ); }
Task边界的更新传播 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private boolean updateWindowBounds (SplitLayout layout, WindowContainerTransaction wct) { final Rect bounds1 = layout.getBounds1(); final Rect bounds2 = layout.getBounds2(); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: bounds1=%s bounds2=%s" , bounds1, bounds2); wct.setBounds(mMainStage.mRootTaskInfo.token, bounds1); wct.setAppBounds(mMainStage.mRootTaskInfo.token, bounds1); wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token, getSmallestWidthDpForBounds(bounds1)); wct.setBounds(mSideStage.mRootTaskInfo.token, bounds2); wct.setAppBounds(mSideStage.mRootTaskInfo.token, bounds2); wct.setSmallestScreenWidthDp(mSideStage.mRootTaskInfo.token, getSmallestWidthDpForBounds(bounds2)); return true ; }
边界变化传播链 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 拖动分隔线 ↓ SplitLayout.onDraggingDivider() ↓ 计算新的bounds1, bounds2 ↓ applySurfaceChanges()(实时Surface变换) ↓ 拖动结束:onDraggingDividerFinished() ↓ updateWindowBounds(wct) ↓ wct.setBounds(MainStage, bounds1) wct.setBounds(SideStage, bounds2) ↓ Framework: Stage.onConfigurationChanged() ↓ Framework: Task.onConfigurationChanged() ↓ 应用进程: Activity.onConfigurationChanged() ↓ 应用重新布局和绘制
5.5 退出分屏模式 退出分屏有多种触发方式:
用户将分隔线拖到边缘
关闭某个Stage的最后一个Task
启动不支持分屏的全屏应用
系统进入其他模式(如PIP)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 void exitSplitScreen (@Nullable StageTaskListener childrenToTop, int exitReason) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: reason=%d childrenToTop=%s" , exitReason, childrenToTop); final WindowContainerTransaction wct = new WindowContainerTransaction (); if (childrenToTop == null ) { childrenToTop = mMainStage.getChildCount() > 0 ? mMainStage : mSideStage; } childrenToTop.evictOtherChildren(wct); mMainStage.deactivate(wct); mSideStage.deactivate(wct); wct.setHidden(mSplitLayout.mDividerLeash, true ); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { t.hide(mMainStage.mRootLeash); t.hide(mSideStage.mRootLeash); t.hide(mSplitLayout.mDividerLeash); }); onSplitScreenExit(); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: split screen exited" ); } void deactivate (WindowContainerTransaction wct) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Stage.deactivate" ); evictAllChildren(wct); wct.setHidden(mRootTaskInfo.token, true ); wct.setBounds(mRootTaskInfo.token, null ); wct.setAppBounds(mRootTaskInfo.token, null ); wct.setWindowingMode(mRootTaskInfo.token, WINDOWING_MODE_UNDEFINED); } void evictAllChildren (WindowContainerTransaction wct) { for (int i = mChildrenTaskInfo.size() - 1 ; i >= 0 ; i--) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); wct.reparent(taskInfo.token, null , false ); wct.setBounds(taskInfo.token, null ); wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); } }
【图5-3:退出分屏的Task重组流程】
flowchart TB
Start([触发退出分屏])
Exit[exitSplitScreen]
Decide[确定要保留的Stage]
MS[MainStage.deactivate]
SS[SideStage.deactivate]
MSEC[evictAllChildren]
SSEC[evictAllChildren]
MSR[reparent task → null]
SSR[reparent task → null]
MSTDA[Task移回TDA根部]
SSTDA[Task移回TDA根部]
Create[系统为Task创建独立RootTask]
Restore[Task恢复全屏显示]
Notify[onSplitScreenExit]
Complete([通知系统UI,分屏已退出])
Start --> Exit
Exit --> Decide
Decide --> MS
Decide --> SS
MS --> MSEC
SS --> SSEC
MSEC --> MSR
SSEC --> SSR
MSR --> MSTDA
SSR --> SSTDA
MSTDA --> Create
SSTDA --> Create
Create --> Restore
Restore --> Notify
Notify --> Complete
style Start fill:#ffebee
style Exit fill:#e1f5ff
style MS fill:#fff4e1
style SS fill:#e8f5e9
style Create fill:#f3e5f5
style Restore fill:#c8e6c9
style Complete fill:#fff9c4
小结 本章完整追踪了分屏Task组织的生命周期:
初始化 :StageCoordinator创建Stage,Stage创建RootTask
进入分屏 :激活Stage,reparent Task,设置边界
Task加入 :onTaskAppeared回调,更新状态
动态调整 :拖动分隔线,更新边界,应用响应配置变化
退出分屏 :deactivate Stage,Task移回独立RootTask
下一章将深入分析Task边界管理的细节,包括bounds、appBounds、maxBounds的计算和应用。
第6章:Task边界管理深度解析 Task的边界管理是分屏功能中最精细的部分之一,它直接决定了应用的显示区域和布局计算。
6.1 三种边界类型详解 在WindowConfiguration中,存在三种不同的边界概念,理解它们的区别对于掌握Task边界管理至关重要。
Bounds:任务的显示边界 1 2 3 4 5 6 7 8 9 10 11 private Rect mBounds = new Rect ();
示例 :
1 2 3 4 屏幕分辨率:1080x2400 分屏模式下: - MainStage bounds: [0, 0, 1080, 1200] // 上半屏 - SideStage bounds: [0, 1200, 1080, 2400] // 下半屏
AppBounds:应用可用边界 1 2 3 4 5 6 7 8 9 10 11 private Rect mAppBounds;
示例 :
1 2 3 假设MainStage有状态栏(高度75px): - MainStage bounds: [0, 0, 1080, 1200] - MainStage appBounds: [0, 75, 1080, 1200] // 顶部扣除状态栏
MaxBounds:最大可扩展边界 1 2 3 4 5 6 7 8 9 10 11 private Rect mMaxBounds = new Rect ();
【图6-1:三种边界类型的关系】
%%{init: {'theme':'base', 'themeVariables': { 'fontSize':'14px'}}}%%
flowchart TB
subgraph Screen["屏幕物理区域"]
StatusBar["状态栏 (系统UI) ← 75px"]
subgraph Upper["bounds.top = 0"]
subgraph AppContent["AppBounds appBounds.top = 75"]
Content["应用内容绘制区域 (contentView)"]
end
end
BoundsBottom["bounds.bottom = 1200"]
Divider["分隔线"]
subgraph Lower["下半屏Stage"]
LowerContent[" "]
end
end
Note1["bounds (当前显示区域)"]
Note2["MaxBounds (可扩展到的最大区域)"]
StatusBar --> Upper
Upper --> BoundsBottom
BoundsBottom --> Divider
Divider --> Lower
Upper -.-> Note1
Screen -.-> Note2
style StatusBar fill:#ffccbc
style AppContent fill:#c8e6c9
style Content fill:#a5d6a7
style Divider fill:#90a4ae
style Lower fill:#e1f5ff
6.2 边界的计算与设置 SplitLayout:分屏边界计算器 SplitLayout负责根据屏幕尺寸、方向、分隔线位置计算两个Stage的边界。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 public class SplitLayout { private final Rect mRootBounds = new Rect (); private final Rect mBounds1 = new Rect (); private final Rect mBounds2 = new Rect (); private final Rect mDividerBounds = new Rect (); private int mDividePosition; private boolean mIsHorizontalSplit; public void init () { final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mDisplayId); mRootBounds.set(0 , 0 , displayLayout.width(), displayLayout.height()); mIsHorizontalSplit = mRootBounds.width() < mRootBounds.height(); mDividePosition = mIsHorizontalSplit ? mRootBounds.height() / 2 : mRootBounds.width() / 2 ; updateBounds(mDividePosition); } public void updateBounds (int dividePosition) { mDividePosition = dividePosition; if (mIsHorizontalSplit) { mBounds1.set( mRootBounds.left, mRootBounds.top, mRootBounds.right, dividePosition - mDividerSize / 2 ); mBounds2.set( mRootBounds.left, dividePosition + mDividerSize / 2 , mRootBounds.right, mRootBounds.bottom ); mDividerBounds.set( mRootBounds.left, dividePosition - mDividerSize / 2 , mRootBounds.right, dividePosition + mDividerSize / 2 ); } else { mBounds1.set( mRootBounds.left, mRootBounds.top, dividePosition - mDividerSize / 2 , mRootBounds.bottom ); mBounds2.set( dividePosition + mDividerSize / 2 , mRootBounds.top, mRootBounds.right, mRootBounds.bottom ); mDividerBounds.set( dividePosition - mDividerSize / 2 , mRootBounds.top, dividePosition + mDividerSize / 2 , mRootBounds.bottom ); } ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateBounds: bounds1=%s bounds2=%s dividerBounds=%s" , mBounds1, mBounds2, mDividerBounds); } public Rect getBounds1 () { return new Rect (mBounds1); } public Rect getBounds2 () { return new Rect (mBounds2); } }
【图6-2:分屏边界计算示意图】
flowchart TB
subgraph Portrait["竖屏模式(水平分隔,上下分屏)"]
direction TB
P_Main["Main Stage Bounds1 [0,0,1080,1188] 高度 = dividePosition - dividerSize/2"]
P_Divider["Divider (12px) [0,1188,1080,1212] ← dividePosition = 1200"]
P_Side["Side Stage Bounds2 [0,1212,1080,2400] 高度 = 屏幕高度 - dividePosition - dividerSize/2"]
P_Main --> P_Divider
P_Divider --> P_Side
end
subgraph Landscape["横屏模式(垂直分隔,左右分屏)"]
direction LR
L_Main["Main Stage Bounds1 [0,0,1188,1080]"]
L_Divider["分 隔 线 (12px)"]
L_Side["Side Stage Bounds2 [1212,0,2400,1080]"]
L_Note["dividePosition = 1200"]
L_Main ---|""|L_Divider
L_Divider ---|""|L_Side
L_Divider -.-> L_Note
end
style P_Main fill:#c8e6c9
style P_Divider fill:#90a4ae
style P_Side fill:#e1f5ff
style L_Main fill:#c8e6c9
style L_Divider fill:#90a4ae
style L_Side fill:#e1f5ff
style L_Note fill:#fff9c4
边界的应用 计算好边界后,通过WindowContainerTransaction应用到Stage。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private boolean updateWindowBounds (SplitLayout layout, WindowContainerTransaction wct) { final Rect bounds1 = layout.getBounds1(); final Rect bounds2 = layout.getBounds2(); wct.setBounds(mMainStage.mRootTaskInfo.token, bounds1) .setAppBounds(mMainStage.mRootTaskInfo.token, bounds1) .setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token, getSmallestWidthDp(bounds1)); wct.setBounds(mSideStage.mRootTaskInfo.token, bounds2) .setAppBounds(mSideStage.mRootTaskInfo.token, bounds2) .setSmallestScreenWidthDp(mSideStage.mRootTaskInfo.token, getSmallestWidthDp(bounds2)); return true ; } private int getSmallestWidthDp (Rect bounds) { final int width = bounds.width(); final int height = bounds.height(); final int smallest = Math.min(width, height); final float density = mContext.getResources().getDisplayMetrics().density; return (int ) (smallest / density); }
6.3 边界继承机制 子Task默认继承父容器(Stage RootTask)的边界配置。
清除Task自身边界 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void addTask (RunningTaskInfo task, WindowContainerTransaction wct) { wct.setBounds(task.token, null ); wct.setAppBounds(task.token, null ); wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED); wct.reparent(task.token, mRootTaskInfo.token, true ); }
继承的好处 :
简化管理 :只需设置Stage边界,所有Task自动生效
自动同步 :拖动分隔线时,Task边界自动更新
避免冲突 :Task不保存自己的边界,不会与Stage冲突
边界继承的计算过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void onConfigurationChanged (Configuration newParentConfig) { final Configuration parentConfig = newParentConfig; if (mBounds.isEmpty()) { mBounds.set(parentConfig.windowConfiguration.getBounds()); } if (mAppBounds == null ) { mAppBounds = new Rect (parentConfig.windowConfiguration.getAppBounds()); } if (mWindowingMode == WINDOWING_MODE_UNDEFINED) { mWindowingMode = parentConfig.windowConfiguration.getWindowingMode(); } mMergedOverrideConfiguration.setTo(parentConfig); mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration); for (int i = mChildren.size() - 1 ; i >= 0 ; i--) { mChildren.get(i).onConfigurationChanged(mMergedOverrideConfiguration); } }
【配置继承示意图】
1 2 3 4 5 6 7 8 9 10 11 Stage RootTask bounds = [0, 0, 1080, 1200] windowingMode = MULTI_WINDOW ↓ (继承) Task A bounds = null (继承) → 实际使用 [0, 0, 1080, 1200] windowingMode = UNDEFINED (继承) → 实际使用 MULTI_WINDOW ↓ (继承) Activity Record bounds = null (继承) → 实际使用 [0, 0, 1080, 1200] windowingMode = UNDEFINED (继承) → 实际使用 MULTI_WINDOW
6.4 边界重置时机 在特定场景下,需要重置Task的边界配置。
退出分屏时重置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void resetBounds (WindowContainerTransaction wct) { wct.setBounds(mRootTaskInfo.token, null ); wct.setAppBounds(mRootTaskInfo.token, null ); wct.setSmallestScreenWidthDp(mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); for (int i = 0 ; i < mChildrenTaskInfo.size(); i++) { final RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); wct.setBounds(taskInfo.token, null ); wct.setAppBounds(taskInfo.token, null ); wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); } }
Task移出Stage时重置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void evictChild (RunningTaskInfo taskInfo, WindowContainerTransaction wct) { wct.setBounds(taskInfo.token, null ); wct.setAppBounds(taskInfo.token, null ); wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); wct.reparent(taskInfo.token, null , false ); }
6.5 屏幕旋转时的边界处理 屏幕旋转是边界管理中的复杂场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 void onDisplayConfigurationChanged (int displayId, Configuration newConfig) { if (displayId != mDisplayId) { return ; } ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDisplayConfigurationChanged: rotation=%d" , newConfig.windowConfiguration.getRotation()); final Rect newRootBounds = newConfig.windowConfiguration.getBounds(); mSplitLayout.updateRoot Bounds (newRootBounds) ; final float ratio = (float ) mSplitLayout.getDividePosition() / (mSplitLayout.isHorizontalSplit() ? mSplitLayout.getRootBounds().height() : mSplitLayout.getRootBounds().width()); final int newDividePosition = mSplitLayout.isHorizontalSplit() ? (int ) (newRootBounds.height() * ratio) : (int ) (newRootBounds.width() * ratio); mSplitLayout.updateBounds(newDividePosition); WindowContainerTransaction wct = new WindowContainerTransaction (); updateWindowBounds(mSplitLayout, wct); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { updateSurfaceBounds(mSplitLayout, t, false ); }); }
小结 本章深入分析了Task边界管理:
三种边界 :bounds、appBounds、maxBounds的含义和区别
边界计算 :SplitLayout根据屏幕和分隔线位置计算边界
边界继承 :Task继承Stage配置的机制
边界重置 :退出分屏和Task迁移时的边界重置
旋转处理 :屏幕旋转时保持分屏比例
下一章将分析Task状态管理,包括可见性、焦点、Z-order等状态的维护。
第7章:Task状态管理 Task在分屏模式下不仅需要管理边界,还需要管理各种运行时状态。
7.1 Task可见性管理 可见性的判断 1 2 3 4 5 6 7 8 9 10 11 12 public boolean isVisible;
清理不可见Task 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void evictInvisibleChildren (WindowContainerTransaction wct) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictInvisibleChildren: checking visibility" ); for (int i = mChildrenTaskInfo.size() - 1 ; i >= 0 ; i--) { final RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); if (!taskInfo.isVisible) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictInvisibleChildren: evicting invisible task=%d" , taskInfo.taskId); wct.reparent(taskInfo.token, null , false ); wct.setBounds(taskInfo.token, null ); wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); } } }
何时调用evictInvisibleChildren?
用户切换到其他Task
进入最近任务界面
从分屏返回全屏模式
7.2 Task Z-order管理 Z-order决定了Task的叠加顺序,影响事件分发和显示优先级。
重新排序Task 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void reorderChild (int taskId, boolean onTop, WindowContainerTransaction wct) { if (!containsTask(taskId)) { ProtoLog.w(WM_SHELL_SPLIT_SCREEN, "reorderChild: task=%d not found in stage" , taskId); return ; } final RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "reorderChild: task=%d onTop=%b" , taskId, onTop); wct.reorder(taskInfo.token, onTop); if (onTop) { wct.setFocusable(taskInfo.token, true ); } }
用户切换Task时的Z-order调整 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void onTaskTapped (int taskId) { final StageTaskListener stage = mMainStage.containsTask(taskId) ? mMainStage : mSideStage; if (stage == null ) { return ; } WindowContainerTransaction wct = new WindowContainerTransaction (); stage.reorderChild(taskId, true , wct); mTaskOrganizer.applyTransaction(wct); }
7.3 Task焦点管理 在分屏模式下,两个Stage可以同时可见,但只有一个可以拥有输入焦点。
焦点切换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void setFocusedStage (StageTaskListener stage) { if (stage == null ) { return ; } ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setFocusedStage: stage=%s" , stage); WindowContainerTransaction wct = new WindowContainerTransaction (); wct.reorder(stage.mRootTaskInfo.token, true ); final StageTaskListener otherStage = stage == mMainStage ? mSideStage : mMainStage; wct.setFocusable(otherStage.mRootTaskInfo.token, false ); wct.setFocusable(stage.mRootTaskInfo.token, true ); mTaskOrganizer.applyTransaction(wct); }
焦点变化的影响 有焦点的Stage :
可以接收键盘输入
可以接收触摸事件
Activity处于Resumed状态
标题栏高亮显示(如果有)
无焦点的Stage :
不接收键盘输入
可以接收触摸事件(点击后会获得焦点)
Activity处于Paused状态
标题栏灰化显示
7.4 Task生命周期状态同步 StageTaskListener需要实时同步Task的生命周期状态。
监听Task状态变化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Override public void onTaskInfoChanged (RunningTaskInfo taskInfo) { final RunningTaskInfo oldInfo = mChildrenTaskInfo.get(taskInfo.taskId); mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); if (oldInfo != null ) { if (oldInfo.isVisible != taskInfo.isVisible) { onTaskVisibilityChanged(taskInfo); } if (!oldInfo.configuration.windowConfiguration.getBounds() .equals(taskInfo.configuration.windowConfiguration.getBounds())) { onTaskBoundsChanged(taskInfo); } if (!Objects.equals(oldInfo.topActivity, taskInfo.topActivity)) { onTaskTopActivityChanged(taskInfo); } } mCallbacks.onTaskInfoChanged(taskInfo); } private void onTaskVisibilityChanged (RunningTaskInfo taskInfo) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVisibilityChanged: task=%d visible=%b" , taskInfo.taskId, taskInfo.isVisible); if (!taskInfo.isVisible) { } else { } } private void onTaskBoundsChanged (RunningTaskInfo taskInfo) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskBoundsChanged: task=%d bounds=%s" , taskInfo.taskId, taskInfo.configuration.windowConfiguration.getBounds()); final SurfaceControl leash = mChildrenLeashes.get(taskInfo.taskId); if (leash != null ) { SurfaceControl.Transaction t = new SurfaceControl .Transaction(); final Rect bounds = taskInfo.configuration.windowConfiguration.getBounds(); t.setPosition(leash, bounds.left, bounds.top); t.setWindowCrop(leash, bounds.width(), bounds.height()); t.apply(); } }
7.5 Task多任务栈管理 在分屏模式下,每个Stage可以包含多个Task,形成Task栈。
Task栈的维护 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 private final SparseArray<RunningTaskInfo> mChildrenTaskInfo = new SparseArray <>();RunningTaskInfo getTopChildTaskInfo () { if (mChildrenTaskInfo.size() == 0 ) { return null ; } RunningTaskInfo topTask = null ; for (int i = 0 ; i < mChildrenTaskInfo.size(); i++) { final RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); if (topTask == null || taskInfo.isVisible) { topTask = taskInfo; } } return topTask; } List<RunningTaskInfo> getVisibleChildTasks () { final List<RunningTaskInfo> visibleTasks = new ArrayList <>(); for (int i = 0 ; i < mChildrenTaskInfo.size(); i++) { final RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); if (taskInfo.isVisible) { visibleTasks.add(taskInfo); } } return visibleTasks; }
Task栈的操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void startTaskInStage (int taskId, StageTaskListener stage, Bundle options) { options.putBinder(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token.asBinder()); WindowContainerTransaction wct = new WindowContainerTransaction (); wct.startTask(taskId, options); mTaskOrganizer.applyTransaction(wct); } void removeTaskFromStage (int taskId, StageTaskListener stage) { if (!stage.containsTask(taskId)) { return ; } WindowContainerTransaction wct = new WindowContainerTransaction (); final RunningTaskInfo taskInfo = stage.mChildrenTaskInfo.get(taskId); wct.reparent(taskInfo.token, null , false ); wct.setBounds(taskInfo.token, null ); wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); mTaskOrganizer.applyTransaction(wct); }
小结 本章分析了Task状态管理的各个方面:
可见性管理 :判断和清理不可见Task
Z-order管理 :调整Task叠加顺序
焦点管理 :控制输入焦点的Stage
状态同步 :实时监听和响应Task状态变化
任务栈管理 :维护Stage中的多Task栈
下一章将总结分屏Task组织的关键设计原则和约束。
第8章:关键设计原则与约束 分屏功能的Task组织机制建立在一系列精心设计的原则和约束之上,理解这些原则对于掌握分屏实现至关重要。
8.1 受控的窗口模式和活动类型 并非所有Task都适合进入分屏,系统通过窗口模式和活动类型进行筛选。
受控的窗口模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static final int [] CONTROLLED_WINDOWING_MODES = { WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_MULTI_WINDOW };
受控的活动类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static final int [] CONTROLLED_ACTIVITY_TYPES = { ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_UNDEFINED };
筛选逻辑的应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void reparentAllEligibleTasks (WindowContainerTransaction wct) { wct.reparentTasks( null , mRootTaskInfo.token, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, true , false ); }
实际效果 :
1 2 3 4 5 6 7 系统中的Task: ├─ Launcher (HOME) → 不进入分屏 ✗ ├─ Recents (RECENTS) → 不进入分屏 ✗ ├─ YouTube PIP (PINNED) → 不进入分屏 ✗ ├─ WeChat (STANDARD, FULLSCREEN) → 进入分屏 ✓ ├─ Gmail (STANDARD, FULLSCREEN) → 进入分屏 ✓ └─ Browser (STANDARD, MULTI_WINDOW) → 进入分屏 ✓
8.2 Stage协调器的高级规则 StageCoordinator遵循严格的规则来管理分屏状态。
核心规则文档 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
激活条件的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 boolean isSplitScreenActive () { return mSideStage.getChildCount() > 0 ; } private void ensureStateConsistency () { if (!isSplitScreenActive()) { if (mMainStage.getChildCount() > 0 ) { ProtoLog.w(WM_SHELL_SPLIT_SCREEN, "ensureStateConsistency: split not active but MainStage has tasks, cleaning up" ); WindowContainerTransaction wct = new WindowContainerTransaction (); mMainStage.evictAllChildren(wct); mTaskOrganizer.applyTransaction(wct); } } }
分隔线可见性规则 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void setDividerVisibility (boolean visible, SurfaceControl.Transaction t) { final boolean mainVisible = mMainStage.isVisible(); final boolean sideVisible = mSideStage.isVisible(); final boolean shouldVisible = visible && mainVisible && sideVisible; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setDividerVisibility: requested=%b main=%b side=%b actual=%b" , visible, mainVisible, sideVisible, shouldVisible); if (mDividerLeash != null ) { if (shouldVisible) { t.show(mDividerLeash); t.setAlpha(mDividerLeash, 1.0f ); } else { t.hide(mDividerLeash); } } }
8.3 Single-Top RootTask的意义 分屏根RootTask采用single-top模式,这有深刻的设计考量。
Single-Top的含义
为什么需要Single-Top? 原因1:统一的父容器
MainStage和SideStage作为兄弟节点,方便协调
配置变化可以同时影响两个Stage
Z-order管理更简单
原因2:事件分发
1 2 3 4 5 Split Root Task ├─ MainStage → 如果触摸在bounds1内,分发给MainStage └─ SideStage → 如果触摸在bounds2内,分发给SideStage
原因3:动画协调
1 2 3 4 5 6 7 Transition.animate(mSplitRootTask, () -> { mMainStage.animateEnter(); mSideStage.animateEnter(); });
原因4:资源隔离
分屏相关的资源(Surface、Leash等)都在Split Root Task管理下
退出分屏时,统一清理资源
8.4 事务原子性的重要性 WindowContainerTransaction的原子性是分屏稳定性的关键保证。
原子性的价值 问题场景 (如果没有原子性):
1 2 3 4 5 6 7 8 9 10 11 reparent(taskA, mainStage); setBounds(taskA, bounds1); reparent(taskB, sideStage); setBounds(taskB, bounds2); showDivider();
使用WCT后 :
1 2 3 4 5 6 7 8 9 10 11 WindowContainerTransaction wct = new WindowContainerTransaction ();wct.reparent(taskA, mainStage, true ) .setBounds(taskA, bounds1) .reparent(taskB, sideStage, true ) .setBounds(taskB, bounds2) .setHidden(divider, false ); mTaskOrganizer.applyTransaction(wct);
原子性的实现机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void applyTransaction (WindowContainerTransaction wct) { synchronized (mGlobalLock) { beginTransaction(); try { for (HierarchyOp op : wct.getHierarchyOps()) { applyHierarchyOp(op); } for (Map.Entry<IBinder, Change> entry : wct.getChanges()) { applyChange(entry.getKey(), entry.getValue()); } commitTransaction(); } catch (Exception e) { rollbackTransaction(); throw e; } } }
分屏开发的最佳实践 规则1:总是使用WCT
1 2 3 4 5 6 7 8 9 WindowContainerTransaction wct = new WindowContainerTransaction ();wct.setBounds(stage1, bounds1) .setBounds(stage2, bounds2); mTaskOrganizer.applyTransaction(wct); mTaskOrganizer.setBounds(stage1, bounds1); mTaskOrganizer.setBounds(stage2, bounds2);
规则2:批量操作使用单个WCT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 WindowContainerTransaction wct = new WindowContainerTransaction ();for (Task task : tasksToMove) { wct.reparent(task.token, newParent, true ); wct.setBounds(task.token, null ); } mTaskOrganizer.applyTransaction(wct); for (Task task : tasksToMove) { WindowContainerTransaction wct = new WindowContainerTransaction (); wct.reparent(task.token, newParent, true ); wct.setBounds(task.token, null ); mTaskOrganizer.applyTransaction(wct); }
规则3:配置和Surface更新分离
1 2 3 4 5 6 7 8 9 10 11 WindowContainerTransaction wct = new WindowContainerTransaction ();wct.setBounds(stage1, bounds1) .setBounds(stage2, bounds2); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { t.setPosition(stage1Leash, bounds1.left, bounds1.top); t.setPosition(stage2Leash, bounds2.left, bounds2.top); });
小结 本章总结了分屏Task组织的关键设计原则:
受控筛选 :通过窗口模式和活动类型筛选适合分屏的Task
协调器规则 :SideStage优先、Stage联动、分隔线可见性
Single-Top架构 :统一管理、简化事件分发、协调动画
事务原子性 :保证操作一致性,避免中间状态
这些原则共同保证了分屏功能的稳定性和用户体验。下一章将提供实践建议和调试技巧。
第9章:实践建议与调试技巧 掌握了原理后,让我们看看如何在实际开发中应用这些知识,以及如何调试分屏相关问题。
9.1 通过代码启动分屏 了解如何通过代码启动分屏对于Framework开发和调试非常重要。本节介绍多种启动分屏的方法。
9.1.1 从SystemUI启动分屏 SystemUI是启动分屏的主要入口,通过SplitScreenController提供的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class SplitScreenController implements SplitScreen { @Override public void startTask (int taskId, @SplitPosition int position, @Nullable Bundle options) { mMainExecutor.execute(() -> { mStageCoordinator.startTask(taskId, position, options); }); } @Override public void startIntent (PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { mMainExecutor.execute(() -> { mStageCoordinator.startIntent(intent, fillInIntent, position, options); }); } @Override public void startShortcut (String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user) { mMainExecutor.execute(() -> { mStageCoordinator.startShortcut(packageName, shortcutId, position, options, user); }); } }
实际使用示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class RecentTasksController { @Inject SplitScreenController mSplitScreenController; public void onTaskDroppedToSplitScreen (int taskId, int position) { Bundle options = ActivityOptions.makeBasic().toBundle(); mSplitScreenController.startTask(taskId, position, options); Log.d(TAG, "Task " + taskId + " started in split position " + position); } public void onAppLongPressSelectSplit (String packageName, int userId) { Intent intent = new Intent (Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setPackage(packageName); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pendingIntent = PendingIntent.getActivity( mContext, 0 , intent, PendingIntent.FLAG_IMMUTABLE); mSplitScreenController.startIntent( pendingIntent, null , SplitScreen.SPLIT_POSITION_TOP_OR_LEFT, null ); } }
9.1.2 从应用内启动分屏 应用也可以请求以分屏模式启动另一个Activity。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public class MainActivity extends Activity { private void launchAdjacentActivity () { Intent intent = new Intent (this , SecondActivity.class); ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK); } startActivity(intent, options.toBundle()); Log.d(TAG, "Adjacent activity launched in split screen" ); } private void launchAdjacentWithFlag () { Intent intent = new Intent (this , SecondActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); startActivity(intent); } @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) private void launchViaSplitScreenService () { try { IActivityTaskManager atm = ActivityTaskManager.getService(); int currentTaskId = getTaskId(); Intent intent = new Intent (Intent.ACTION_VIEW); intent.setData(Uri.parse("https://www.google.com" )); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); } catch (Exception e) { Log.e(TAG, "Failed to launch split screen" , e); } } }
应用Manifest配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <application > <activity android:name =".MainActivity" android:resizeableActivity ="true" android:supportsPictureInPicture ="false" android:configChanges ="screenSize|smallestScreenSize|screenLayout|orientation" > <meta-data android:name ="android.supports_size_changes" android:value ="true" /> </activity > </application >
9.1.3 通过ADB命令启动分屏 在开发和调试阶段,可以使用ADB命令快速测试分屏功能。
方法1:使用am命令启动分屏
1 2 3 4 5 6 7 8 9 10 11 12 adb shell am start -n com.example.app1/.MainActivity --windowingMode 6 --displayId 0 adb shell am start -n com.example.app2/.MainActivity --windowingMode 6 --displayId 0 adb shell am stack start --display 0 --windowingMode 6 com.example.app1/.MainActivity
方法2:使用TaskOrganizer Shell命令
1 2 3 4 5 6 7 8 9 10 11 12 13 adb shell dumpsys activity activities | grep "TaskRecord" adb shell wm split-screen enter adb shell wm task move --task-id 101 --split-position top adb shell wm task move --task-id 102 --split-position bottom
方法3:通过SystemUI的广播启动
1 2 3 4 5 6 7 8 adb shell am broadcast -a com.android.systemui.action.ENTER_SPLIT_SCREEN \ --ei task_id 101 \ --ei position 0
方法4:使用Shell脚本自动化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #!/bin/bash APP1_PACKAGE="com.example.app1" APP1_ACTIVITY=".MainActivity" APP2_PACKAGE="com.example.app2" APP2_ACTIVITY=".MainActivity" echo "Starting split screen mode..." echo "Launching $APP1_PACKAGE ..." adb shell am start -n $APP1_PACKAGE /$APP1_ACTIVITY sleep 2TASK_ID_1=$(adb shell dumpsys activity activities | \ grep "TaskRecord.*$APP1_PACKAGE " | \ sed -n 's/.*#\([0-9]*\).*/\1/p' | \ head -1) echo "App1 Task ID: $TASK_ID_1 " adb shell am broadcast -a com.android.systemui.action.ENTER_SPLIT_SCREEN \ --ei task_id $TASK_ID_1 \ --ei position 0 sleep 1echo "Launching $APP2_PACKAGE ..." adb shell am start -n $APP2_PACKAGE /$APP2_ACTIVITY echo "Split screen launched successfully!"
9.1.4 通过Framework API启动(需要系统权限) 如果你在开发系统级应用或Framework服务,可以直接调用Framework的API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 public class SplitScreenHelper { private final Context mContext; private final IActivityTaskManager mActivityTaskManager; public SplitScreenHelper (Context context) { mContext = context; mActivityTaskManager = ActivityTaskManager.getService(); } @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSplitScreen (int task1Id, int task2Id) throws RemoteException { WindowContainerTransaction wct = new WindowContainerTransaction (); List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(Integer.MAX_VALUE); ActivityManager.RunningTaskInfo task1 = findTaskById(tasks, task1Id); ActivityManager.RunningTaskInfo task2 = findTaskById(tasks, task2Id); if (task1 == null || task2 == null ) { Log.e(TAG, "Task not found" ); return ; } ITaskOrganizer organizer = ITaskOrganizer.Stub.asInterface( ServiceManager.getService("task_organizer" )); if (organizer != null ) { wct.setWindowingMode(task1.token, WINDOWING_MODE_MULTI_WINDOW); wct.setWindowingMode(task2.token, WINDOWING_MODE_MULTI_WINDOW); DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); int screenWidth = metrics.widthPixels; int screenHeight = metrics.heightPixels; Rect bounds1 = new Rect (0 , 0 , screenWidth, screenHeight / 2 ); Rect bounds2 = new Rect (0 , screenHeight / 2 , screenWidth, screenHeight); wct.setBounds(task1.token, bounds1); wct.setBounds(task2.token, bounds2); mActivityTaskManager.applyContainerTransaction(wct, null ); Log.d(TAG, "Split screen started successfully" ); } } private ActivityManager.RunningTaskInfo findTaskById ( List<ActivityManager.RunningTaskInfo> tasks, int taskId) { for (ActivityManager.RunningTaskInfo task : tasks) { if (task.taskId == taskId) { return task; } } return null ; } @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void exitSplitScreen () throws RemoteException { List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(Integer.MAX_VALUE); WindowContainerTransaction wct = new WindowContainerTransaction (); for (ActivityManager.RunningTaskInfo task : tasks) { if (task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { wct.setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN); wct.setBounds(task.token, null ); } } mActivityTaskManager.applyContainerTransaction(wct, null ); } }
9.1.5 启动分屏的完整示例 以下是一个完整的工具类,整合了多种启动分屏的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 public class SplitScreenLauncher { private final Context mContext; private final SplitScreenController mSplitScreenController; public SplitScreenLauncher (Context context) { mContext = context; mSplitScreenController = Dependency.get(SplitScreenController.class); } public void launchTwoAppsInSplitScreen (String package1, String package2) { launchAppInSplit(package1, SplitScreen.SPLIT_POSITION_TOP_OR_LEFT); new Handler (Looper.getMainLooper()).postDelayed(() -> { launchAppInSplit(package2, SplitScreen.SPLIT_POSITION_BOTTOM_OR_RIGHT); }, 500 ); } private void launchAppInSplit (String packageName, @SplitPosition int position) { Intent intent = mContext.getPackageManager() .getLaunchIntentForPackage(packageName); if (intent == null ) { Log.e(TAG, "Cannot find launch intent for package: " + packageName); return ; } intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pendingIntent = PendingIntent.getActivity( mContext, 0 , intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); mSplitScreenController.startIntent(pendingIntent, null , position, null ); } public void moveTaskToSplitScreen (int taskId, @SplitPosition int position) { mSplitScreenController.startTask(taskId, position, null ); } public void exitSplitScreen () { mSplitScreenController.exitSplitScreen( SplitScreenController.EXIT_REASON_UNKNOWN); } public boolean isSplitScreenActive () { return mSplitScreenController.isSplitScreenVisible(); } } public class SplitScreenTestActivity extends Activity { private SplitScreenLauncher mLauncher; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_test); mLauncher = new SplitScreenLauncher (this ); findViewById(R.id.btn_launch_split).setOnClickListener(v -> { mLauncher.launchTwoAppsInSplitScreen( "com.android.chrome" , "com.google.android.gm" ); }); findViewById(R.id.btn_exit_split).setOnClickListener(v -> { mLauncher.exitSplitScreen(); }); } }
启动分屏的注意事项 :
权限要求 :
普通应用只能使用FLAG_ACTIVITY_LAUNCH_ADJACENT
系统应用需要MANAGE_ACTIVITY_TASKS权限
SystemUI可以直接使用SplitScreenController
应用兼容性 :
目标应用必须声明android:resizeableActivity="true"
应用需要正确处理onConfigurationChanged回调
避免启动不支持分屏的应用(如固定方向的应用)
时序控制 :
两个应用的启动需要适当的延迟
等待第一个应用完全启动后再启动第二个
监听分屏状态变化以确保操作成功
错误处理 :
检查应用是否存在和可启动
处理分屏启动失败的情况
提供回退方案(如全屏启动)
9.2 调试工具与方法 使用dumpsys查看Task层次结构 基础命令 :
1 2 3 4 5 6 7 8 9 10 11 adb shell dumpsys activity containers
筛选特定Task :
1 2 3 4 5 6 7 8 9 10 adb shell dumpsys activity containers | grep -A 20 "Task id=101"
【图9-1:dumpsys输出解读】
1 2 3 4 5 6 7 8 9 Task id=101 WeChat ← Task标识和应用名 type=standard ← 活动类型 windowingMode=multi-window ← 窗口模式(分屏的关键标志) bounds=[0,0,1080,1200] ← Task边界(注解:上半屏) appBounds=[0,75,1080,1200] ← 应用可用边界 isVisible=true ← 可见性 topActivity=...LauncherUI ← 顶部Activity numActivities=2 ← Activity数量 parent=Task id=11 (MainStage) ← 父容器(注解:在MainStage中)
使用ProtoLog追踪分屏事件 启用ProtoLog :
1 2 3 4 5 6 7 8 adb shell wm logging enable-text WM_SHELL_SPLIT_SCREEN adb shell wm logging enable-text WM_SHELL_TASK_ORG adb logcat | grep "WM_SHELL"
关键日志点 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # Task进入分屏 WM_SHELL_SPLIT_SCREEN: StageTaskListener.addTask: taskId=101 WM_SHELL_SPLIT_SCREEN: Stage.activate: reparentAllTasks=false WM_SHELL_SPLIT_SCREEN: updateWindowBounds: bounds1=[0,0,1080,1200] bounds2=[0,1200,1080,2400] # Task被reparent WM_SHELL_TASK_ORG: onTaskAppeared: taskId=101 isVisible=true WM_SHELL_SPLIT_SCREEN: onSplitScreenEnter: split screen is now active # 边界更新 WM_SHELL_SPLIT_SCREEN: onDraggingDivider: position=1000 WM_SHELL_SPLIT_SCREEN: onDraggingDividerFinished: position=1000 WM_SHELL_SPLIT_SCREEN: updateWindowBounds: bounds1=[0,0,1080,1000] bounds2=[0,1000,1080,2400] # 退出分屏 WM_SHELL_SPLIT_SCREEN: exitSplitScreen: reason=APP_FINISHED WM_SHELL_SPLIT_SCREEN: Stage.deactivate WM_SHELL_SPLIT_SCREEN: onSplitScreenExit: exiting split screen
使用WM Shell调试命令 1 2 3 4 5 6 7 8 9 10 11 12 adb shell dumpsys activity service SystemUIService WMShell | grep -A 50 "SplitScreen"
9.2 常见问题与解决方案 问题1:Task丢失或消失 现象 : 进入分屏后,某个Task不可见或消失
可能原因 :
Task被错误地reparent到了null
Task的bounds设置错误(如空Rect)
Task被标记为hidden
Task不符合CONTROLLED筛选条件
调试步骤 :
1 2 3 4 5 6 7 8 9 10 11 adb shell dumpsys activity containers | grep "Task id=101" adb logcat | grep "taskId=101"
解决方案 :
1 2 3 4 5 6 7 wct.reparent(task.token, stage.mRootTaskInfo.token, true ); wct.reparent(task.token, null , true ); wct.setBounds(task.token, null );
问题2:边界计算错误 现象 : Task显示区域不正确,或与预期的分屏比例不符
可能原因 :
SplitLayout计算错误
分隔线大小未考虑
边界未正确传播到Task
配置变化未触发
调试步骤 :
1 2 3 4 5 6 adb shell dumpsys activity containers | grep -E "(Stage|Task id)" -A 5
解决方案 :
1 2 3 4 5 6 7 8 9 10 11 mBounds1.set( mRootBounds.left, mRootBounds.top, mRootBounds.right, dividePosition - mDividerSize / 2 ); wct.setBounds(stage.token, stageBounds); wct.setBounds(task.token, null );
问题3:状态不同步 现象 : Task的可见性、焦点等状态与实际不符
可能原因 :
onTaskInfoChanged回调未正确处理
状态缓存未更新
并发问题导致状态不一致
调试步骤 :
1 2 3 4 5 6 7 8 9 10 adb logcat | grep "onTaskInfoChanged" ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d cached_visible=%b new_visible=%b" , taskInfo.taskId, mChildrenTaskInfo.get(taskInfo.taskId).isVisible, taskInfo.isVisible);
解决方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void onTaskInfoChanged (RunningTaskInfo taskInfo) { mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); handleTaskInfoChange(taskInfo); } private synchronized void handleTaskInfoChange (RunningTaskInfo taskInfo) { }
9.3 修改分屏逻辑的注意事项 注意事项1:保持事务原子性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void enterSplitScreen () { WindowContainerTransaction wct = new WindowContainerTransaction (); prepareStages(wct); reparentTasks(wct); updateBounds(wct); mTaskOrganizer.applyTransaction(wct); } void enterSplitScreen () { WindowContainerTransaction wct1 = new WindowContainerTransaction (); prepareStages(wct1); mTaskOrganizer.applyTransaction(wct1); WindowContainerTransaction wct2 = new WindowContainerTransaction (); reparentTasks(wct2); mTaskOrganizer.applyTransaction(wct2); }
注意事项2:处理边界情况 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (!isSplitScreenActive()) { return ; } if (!mChildrenTaskInfo.contains(taskId)) { ProtoLog.w(WM_SHELL_SPLIT_SCREEN, "Task not found: taskId=%d" , taskId); return ; } if (bounds == null || bounds.isEmpty()) { ProtoLog.e(WM_SHELL_SPLIT_SCREEN, "Invalid bounds: %s" , bounds); return ; }
注意事项3:尊重Task可见性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void moveTaskToStage (int taskId, StageTaskListener targetStage) { final RunningTaskInfo taskInfo = getTaskInfo(taskId); if (!taskInfo.isVisible && targetStage == getCurrentFocusedStage()) { ProtoLog.w(WM_SHELL_SPLIT_SCREEN, "Attempting to move invisible task to focused stage" ); return ; } WindowContainerTransaction wct = new WindowContainerTransaction (); targetStage.addTask(taskInfo, wct); mTaskOrganizer.applyTransaction(wct); }
9.4 性能优化建议 优化1:减少不必要的WCT提交 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private WindowContainerTransaction mPendingWct;private Handler mHandler;void updateTaskBounds (int taskId, Rect bounds) { if (mPendingWct == null ) { mPendingWct = new WindowContainerTransaction (); mHandler.postDelayed(this ::flushPendingTransaction, 16 ); } mPendingWct.setBounds(getTaskToken(taskId), bounds); } private void flushPendingTransaction () { if (mPendingWct != null ) { mTaskOrganizer.applyTransaction(mPendingWct); mPendingWct = null ; } }
优化2:使用Surface Transaction处理动画 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void onDraggingDivider (int position) { Rect bounds1 = calculateBounds1(position); Rect bounds2 = calculateBounds2(position); SurfaceControl.Transaction t = new SurfaceControl .Transaction(); t.setPosition(mMainStageLeash, bounds1.left, bounds1.top); t.setWindowCrop(mMainStageLeash, bounds1.width(), bounds1.height()); t.setPosition(mSideStageLeash, bounds2.left, bounds2.top); t.setWindowCrop(mSideStageLeash, bounds2.width(), bounds2.height()); t.apply(); } void onDraggingDividerFinished (int position) { WindowContainerTransaction wct = new WindowContainerTransaction (); updateWindowBounds(wct, position); mTaskOrganizer.applyTransaction(wct); }
优化3:缓存Task信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private final SparseArray<RunningTaskInfo> mTaskInfoCache = new SparseArray <>();RunningTaskInfo getTaskInfo (int taskId) { return mTaskInfoCache.get(taskId); } @Override public void onTaskInfoChanged (RunningTaskInfo taskInfo) { mTaskInfoCache.put(taskInfo.taskId, taskInfo); } RunningTaskInfo getTaskInfo (int taskId) { return mTaskOrganizer.getTaskInfo(taskId); }
小结 本章提供了实用的开发和调试建议:
调试工具 :dumpsys、ProtoLog、WM Shell命令
常见问题 :Task丢失、边界错误、状态不同步的诊断和解决
开发注意事项 :原子性、边界情况、可见性检查
性能优化 :减少WCT提交、使用Surface Transaction、缓存优化
下一章将对全文进行总结。
第10章:总结与展望 经过前面九章的深入分析,我们完整地剖析了Android分屏功能中Task组织与管理的实现机制。让我们回顾核心要点,并展望未来的发展方向。
10.1 核心要点回顾 WindowContainer体系:分屏的基础架构 Android的窗口管理建立在树形的WindowContainer体系之上:
DisplayContent → TaskDisplayArea → RootTask → Task → ActivityRecord
每个层级都是容器,既可包含子节点,也可被父节点管理
配置从父容器向下继承和传播,子容器可选择性覆盖
Task Reparenting:分屏的核心机制 Task在不同RootTask间迁移而无需重启应用:
Reparenting 将Task从当前父容器移到新父容器
Task保持运行状态,Activity栈完整保留
配置自动重算,应用收到onConfigurationChanged回调
WindowContainerTransaction:原子性的保证 所有Task操作都通过WCT事务机制执行:
批量操作打包成原子事务
要么全部成功,要么全部失败
避免中间状态暴露给用户,保证流畅体验
Stage架构:分屏的组织模式 MainStage和SideStage是分屏的两个独立区域:
每个Stage有自己的RootTask容器
Task被reparent到Stage下,继承Stage的边界和配置
StageCoordinator协调两个Stage的生命周期和交互
边界管理:精细的显示控制 三种边界类型服务不同目的:
Bounds :Task的显示区域
AppBounds :应用可用的绘制区域(扣除系统UI)
MaxBounds :可扩展到的最大区域
设计原则:稳定性的基石 严格的规则确保分屏功能的稳定:
只有特定窗口模式和活动类型的Task可进入分屏
SideStage优先:有Task才激活分屏
Single-Top架构:统一管理、简化事件分发
事务原子性:保证操作一致性
10.2 技术价值与应用 对Framework开发者 掌握Task组织机制后,你可以:
理解多窗口体系 :不仅是分屏,还有PIP、Freeform等
调试复杂问题 :快速定位Task层次、边界、状态问题
扩展分屏功能 :实现自定义的多窗口模式
优化性能 :减少不必要的配置变化和重绘
对应用开发者 理解分屏机制有助于:
适配分屏模式 :正确处理配置变化,优化布局
测试多窗口场景 :了解Task状态变化,覆盖边界情况
利用分屏特性 :如启动Activity到特定Stage
对系统架构师 分屏实现体现了Android的设计哲学:
容器化架构 :递归的WindowContainer设计
配置继承机制 :高效的属性传播
事务模式 :保证系统状态一致性
关注点分离 :Framework层、Shell层、应用层各司其职
10.3 与其他多窗口模式的对比 画中画(Picture-in-Picture) 相似点 :
也使用RootTask组织Task
也通过reparent进入PIP模式
也有边界管理和配置变化
不同点 :
PIP是单Task模式,分屏是双Task模式
PIP窗口可拖动和调整大小,分屏的Task边界固定在Stage内
PIP有自己的WINDOWING_MODE_PINNED
相似点 :
同样基于Task和RootTask
同样需要边界管理
不同点 :
Freeform允许任意数量的窗口
每个Task有独立的边界和Z-order
没有Stage概念,Task直接在TDA下
使用WINDOWING_MODE_FREEFORM
桌面模式(Desktop Mode) 相似点 :
利用WindowContainer体系
支持多Task并行显示
不同点 :
更接近传统桌面操作系统
Task可最大化、最小化、关闭
有任务栏和窗口装饰
10.4 未来演进方向 更灵活的分屏布局 当前分屏限制为两个Stage,未来可能:
三分屏或四分屏 :支持更多Stage
可调整的Stage数量 :根据屏幕尺寸动态调整
嵌套分屏 :Stage内部再次分屏
更智能的Task分配 AI辅助的分屏体验:
智能推荐 :根据使用场景推荐分屏组合
自动分配 :新启动的Task自动进入合适的Stage
场景记忆 :记住用户的分屏习惯
跨设备的分屏协同 多设备联动:
跨屏分屏 :Task跨多个物理显示设备
设备间迁移 :Task在手机、平板、PC间迁移
统一管理 :云端同步分屏配置
性能和能效优化 技术改进:
更高效的边界计算 :减少配置变化开销
更好的动画性能 :利用GPU加速
更智能的资源管理 :后台Stage的Task进入休眠
10.5 结语 Android分屏功能的Task组织机制是一个精心设计的系统,它展现了:
清晰的架构设计 :WindowContainer体系提供灵活的容器模型
优雅的实现方式 :Reparenting和配置继承机制简洁高效
严格的工程规范 :原子事务和设计原则保证稳定性
对于Framework开发者而言,深入理解这套机制不仅有助于开发和调试分屏功能,更能从中学习到Android系统设计的精髓——如何在复杂性和可维护性之间找到平衡 。
希望这篇文章能成为你探索Android多窗口世界的起点。分屏只是开始,更广阔的多窗口生态等待我们去发现和创造。
附录:参考资料
源码路径 :
Framework层:frameworks/base/services/core/java/com/android/server/wm/
Shell层:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/
关键类索引 :
WindowContainer.java:容器基类
Task.java:Task实现
WindowContainerTransaction.java:事务机制
StageCoordinator.java:分屏协调器
SplitLayout.java:分屏布局计算
调试命令速查 :
1 2 3 4 5 6 7 8 adb shell dumpsys activity containers adb shell dumpsys activity service SystemUIService WMShell adb shell wm logging enable-text WM_SHELL_SPLIT_SCREEN
完
作者说明
本文基于Android 15源码分析,不同Android版本的实现可能有差异。文中的代码示例经过简化以突出核心逻辑,实际源码包含更多的边界检查和错误处理。
源码版本 :Android 15 (API Level 35)分析时间 :2025年10月