Android 显示系统横跨三大进程:system_server、surfaceflinger以及客户端应用,同时衔接两套编程语言(框架层 Java、原生合成器 C++)。它的职责包含物理面板枚举发现、在精准的 VSYNC 垂直同步间隔调度帧刷新、将数百个图形图层合成为单路输出图像。本章将剖析全部核心子系统:负责显示器生命周期的 Java 层DisplayManagerService;管控窗口 Z 轴顺序的DisplayArea层级;从硬件中断一路到Choreographer的 VSYNC 流水线;屏幕旋转与折叠屏管理;显示挖孔与圆角处理;SurfaceFlinger前端重构与CompositionEngine;基于BLASTBufferQueue的缓冲区管理;虚拟显示器与镜像;色彩管理;显示器功耗控制。
已经阅读过第 13 章图形渲染流水线、第 20 章system_server架构的读者,会发现本章是前述基础在显示领域的自然延伸。
24.1 显示系统架构
24.1.1 三层模型
Android 显示子系统划分为 3 个独立层级,每层运行在不同进程与地址空间:
第 1 层 —— 框架层(system_server)DisplayManagerService掌管每一台显示器的生命周期。它通过各类DisplayAdapter实现发现物理显示器,创建LogicalDisplay对象映射物理DisplayDevice实例,并向WindowManagerService通知显示器新增、移除、配置变更事件。
第 2 层 —— 原生合成器(surfaceflinger)SurfaceFlinger接收SurfaceControl.Transaction下发的缓冲区更新,基于 VSYNC 调度合成工作;实际像素混合可以交由硬件合成器 HAL(Overlay 图层),或者 GPU(通过RenderEngine做客户端合成)。
第 3 层 —— 内核(DRM/KMS)Linux DRM 子系统管理显示硬件:模式设置、CRTC / 编码器 / 连接器拓扑,以及触发合成帧缓冲区扫描输出的 page‑flip ioctl。
24.1.2 DisplayManagerService
DisplayManagerService(DMS)是system_server启动阶段注册的系统服务。Android17 版本代码量超 7300 行,属于框架中体量最大的服务之一。其 Javadoc 对架构说明如下:
DisplayManagerService 管理显示器全局生命周期,基于当前接入的物理显示设备决定逻辑显示器如何配置;显示器状态发生变化时,向系统以及应用发送通知。
DMS 运行在DisplayThread(优先级THREAD_PRIORITY_DISPLAY的共享HandlerThread)。全部内部状态受唯一锁SyncRoot保护,所有显示器适配器、逻辑显示器对象均使用同一把锁:
// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java private final SyncRoot mSyncRoot = new SyncRoot();锁顺序约束至关重要:DMS 持有mSyncRoot时可以调用SurfaceFlinger(经由SurfaceControl);绝对不能在持有mSyncRoot的情况下调用WindowManagerService,因为 WMS 持有自身mGlobalLock并且可能回调 DMS。所有存在重入风险的外部调用全部通过 Handler 异步派发。
24.1.3 DisplayAdapter 架构
DMS 通过一组DisplayAdapter实现完成显示器发现。
LocalDisplayAdapter处理SurfaceFlinger热插拔机制上报的物理显示器(内置屏、外接屏),接收EVENT_ADD、EVENT_REMOVE、EVENT_CHANGE通知,创建绑定SurfaceFlinger显示令牌的LocalDisplayDevice实例。
VirtualDisplayAdapter代表应用创建虚拟显示器,接收携带尺寸、密度、标记、生命周期回调IVirtualDisplayCallback的VirtualDisplayConfig。
WifiDisplayAdapter经由WifiDisplayController管理 Miracast(Wi‑Fi Display / WFD)连接。
OverlayDisplayAdapter读取Settings.Global.OVERLAY_DISPLAY_DEVICES配置,创建开发者叠加显示器。
全部适配器向DisplayDeviceRepository上报事件,该仓库保存权威的活跃DisplayDevice列表,并将变更通知 DMS。
24.1.4 LogicalDisplay 与物理映射
LogicalDisplay与DisplayDevice的分离是核心设计。
LogicalDisplay:系统其余模块(窗口管理器、应用)看到的显示器抽象。DisplayDevice:底层物理或者虚拟硬件实体。
LogicalDisplay文档关键设计描述:
逻辑显示器与显示设备是正交概念。逻辑显示器与显示设备之间存在映射关系,但可以是多对多,部分实例甚至可以不存在关联。
实际场景:普通单屏手机为 1:1 映射;折叠设备映射关系动态变化,折叠 / 展开过程中,同一个默认逻辑显示器(ID 0)可以在内屏、外屏物理设备之间切换;该切换逻辑由LogicalDisplayMapper管理。
24.1.5 显示器配置流程
显示器初次接入时,配置流程流经多个组件:
DMS 维护两套事件回调存储结构:
// 以调用进程pid为索引保存回调记录 private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>(); // 以[uid][pid]二维索引保存回调记录 private final SparseArray<SparseArray<CallbackRecord>> mCallbackRecordByPidByUid = new SparseArray<>();事件通过MSG_DELIVER_DISPLAY_EVENT投递到 Handler,保证异步下发,执行时不持有mSyncRoot锁。
24.1.6 DisplayGroup 显示组
显示器归入DisplayGroup,同一组共享电源状态与亮度。主显示组包含内置显示屏;虚拟显示器可通过VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP创建独立组,或通过VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP归属设备默认显示组。DisplayGroupAllocator分配组 ID。
// LogicalDisplayMapper显示组相关事件 public static final int DISPLAY_GROUP_EVENT_ADDED = 1; public static final int DISPLAY_GROUP_EVENT_CHANGED = 2; public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;显示组影响电源管理:默认显示组休眠时,组内所有显示器同时熄灭。
24.1.7 DisplayInfo 与覆盖机制
应用可见的DisplayInfo对象由多层覆盖机制逐层构建:
- 基础信息:取自主
DisplayDevice的DisplayDeviceInfo(物理尺寸、密度、硬件支持模式)。 - DMS 覆盖:显示模式选择、用户关闭的 HDR 类型、帧率覆盖。
- WMS 覆盖:窗口管理器设置应用可见尺寸(处理过扫描、挖孔、旋转);调用
setDisplayInfoOverrideFromWindowManagerLocked()写入。
DisplayInfoOverrides中常量WM_OVERRIDE_FIELDS明确定义 WMS 允许修改的字段,防止意外覆盖硬件原始数值。
24.1.8 DisplayBlanker:电源状态协调
DisplayBlanker接口作为DisplayPowerController与SurfaceFlinger之间的桥梁,用于显示器电源状态变更。DMS 实现匿名内部类DisplayBlanker,协调多显示器状态切换。
// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() { @Override public synchronized void requestDisplayState(int displayId, int state, float brightness, float sdrBrightness) { // Check if ALL displays are inactive or off boolean allInactive = true; boolean allOff = true; // ... iterate over mDisplayStates if (state == Display.STATE_OFF) { requestDisplayStateInternal(displayId, state, brightness, sdrBrightness); } if (stateChanged) { mDisplayPowerCallbacks.onDisplayStateChange(allInactive, allOff); } if (state != Display.STATE_OFF) { requestDisplayStateInternal(displayId, state, brightness, sdrBrightness); } } };执行顺序有严格要求:关闭显示器流程,先设置显示器状态,再通知 PowerManager;点亮显示器流程,先通知 PowerManager,再设置显示器状态。避免系统认为屏幕已经点亮,但硬件实际还在掉电的竞态问题。
24.1.9 DisplayModeDirector 与投票系统
display/mode包下的DisplayModeDirector是框架侧策略引擎。接收多来源高层模式请求(应用setFrameRate调用、用户最大刷新率设置、性能提示、接近传感器、设备壳温),输出DesiredDisplayModeSpecs交给 DMS 下发给SurfaceFlinger。
整体基于投票抽象:每个输入源在VotesStorage以固定优先级注册Vote;VoteSummary对单台显示器的全部投票做冲突消解,输出一套尺寸、刷新率约束集合。
投票以数字优先级作为 key;VoteSummary做冲突仲裁,高优先级系统约束(热控、低功耗)可以缩小甚至否决应用这类低优先级源的请求。所有投票实体类(SizeVote、RefreshRateVote、SupportedRefreshRatesVote、RequestedRefreshRateVote、WorkDurationsVote、HdrPreferenceVote等)与DisplayModeDirector位于同一源码目录。
注意:真正基于该规格选择最终硬件模式的
SurfaceFlinger侧 C++ 类RefreshRateSelector(24.3.6 节),框架层不会直接引用它。
24.1.10 Handler 消息协议
DMS 基于 Handler 消息完成异步操作:
| 消息 | 常量 | 用途 |
|---|---|---|
| 注册默认适配器 | MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS (1) | 开机阶段初始化适配器 |
| 注册额外适配器 | MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS (2) | 开机后动态注册适配器 |
| 下发显示器事件 | MSG_DELIVER_DISPLAY_EVENT (3) | 通知回调显示器变更 |
| 请求遍历更新 | MSG_REQUEST_TRAVERSAL (4) | 触发 SurfaceFlinger 显示器配置生效 |
| 更新视口 | MSG_UPDATE_VIEWPORT (5) | 更新输入视口映射 |
| 加载亮度配置 | MSG_LOAD_BRIGHTNESS_CONFIGURATIONS (6) | 加载亮度曲线参数 |
| 帧率覆盖事件 | MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE (7) | 通知帧率覆盖变更 |
| 显示组事件 | MSG_DELIVER_DISPLAY_GROUP_EVENT (8) | 通知显示组新增 / 移除 |
| 设备状态上报 | MSG_RECEIVED_DEVICE_STATE (9) | 处理折叠设备状态变更 |
| 批量派发待处理事件 | MSG_DISPATCH_PENDING_PROCESS_EVENTS (10) | 批量事件分发 |
| 下发显示器快照 | MSG_DELIVER_DISPLAY_SNAPSHOT (11) | 新注册回调一次性获取全部显示器当前状态 |
MSG_DELIVER_DISPLAY_SNAPSHOT用于让新注册监听器一次性拿到完整显示器集合,定义在DisplayManagerService.java第 314 行。
MSG_REQUEST_TRAVERSAL尤为重要:显示器配置变更后,DMS 必须调度SurfaceFlinger执行一次 traversal,应用新显示器参数(图层栈分配、显示器投影、显示模式)。
24.2 DisplayArea 层级树
24.2.1 什么是 DisplayArea
DisplayContent是代表一整个逻辑显示器的WindowContainer;在它之下,Android 把窗口组织为DisplayArea容器树。每一个DisplayArea把具备相同特性、处于同一 Z 轴区间的窗口归为一组。
类继承关系:
DisplayArea文档说明三种类型,保障 Z 轴顺序正确性:
DisplayArea 分为三类:
- BELOW_TASKS:仅可存放 BELOW_TASKS 类型 DisplayArea 以及位于任务下层的 WindowToken
- ABOVE_TASKS:仅可存放 ABOVE_TASKS 类型 DisplayArea 以及位于任务上层的 WindowToken
- ANY:可以存放任意 DisplayArea、WindowToken 或者 Task 任务容器
24.2.2 Feature IDs 特性 ID
每个DisplayArea携带mFeatureId标识用途,标准 ID 定义于DisplayAreaOrganizer:
| Feature ID | 常量 | 数值 | 用途 |
|---|---|---|---|
| FEATURE_ROOT | FEATURE_SYSTEM_FIRST | 0 | 层级树根节点 |
| FEATURE_DEFAULT_TASK_CONTAINER | FEATURE_SYSTEM_FIRST + 1 | 1 | Task 默认容器 |
| FEATURE_WINDOW_TOKENS | FEATURE_SYSTEM_FIRST + 2 | 2 | 非 Task 类型 WindowToken 容器 |
| FEATURE_ONE_HANDED | FEATURE_SYSTEM_FIRST + 3 | 3 | 单手模式缩放 |
| FEATURE_TOP_LEVEL_ZOOM | FEATURE_SYSTEM_FIRST + 4 | 4 | 顶层缩放层(AOSP 该特性叫 WindowedMagnification 窗口放大) |
| FEATURE_FULLSCREEN_MAGNIFICATION | FEATURE_SYSTEM_FIRST + 5 | 5 | 全屏放大 |
| FEATURE_HIDE_DISPLAY_CUTOUT | FEATURE_SYSTEM_FIRST + 6 | 6 | 挖孔下方内容区域 |
| FEATURE_IME_PLACEHOLDER | FEATURE_SYSTEM_FIRST + 7 | 7 | 输入法占位容器位置 |
| FEATURE_IME | FEATURE_SYSTEM_FIRST + 8 | 8 | 输入法实际容器 |
| FEATURE_WINDOWING_LAYER | FEATURE_SYSTEM_FIRST + 9 | 9 | 兜底窗口层 |
| FEATURE_APP_ZOOM_OUT | FEATURE_SYSTEM_FIRST + 10 | 10 | 应用缩小显示支持 |
厂商自定义特性 ID 从FEATURE_VENDOR_FIRST(10001)到FEATURE_VENDOR_LAST(20001);OEM 可自定义节点,例如车载后排显示、双屏特性。
24.2.3 DisplayAreaPolicyBuilder
DisplayAreaPolicyBuilder接收一组 Feature 定义,构建中间DisplayArea节点,满足 Z 轴顺序约束。
AOSP 默认DefaultDisplayAreaPolicy构建层级示例:
构建器工作流程:
- 收集全部 Feature 定义,每个 Feature 面向一批窗口类型(例如 WindowedMagnification 覆盖到无障碍放大叠加层之下所有窗口)。
- 遍历每一个 Z 轴槽位(窗口常量定义的 36 层模型),判断哪些 Feature 生效。
- 在 Feature 边界与 Z 轴边界交叉位置创建中间
DisplayArea节点,拆分树维持正确层级顺序。
24.2.4 DisplayAreaGroup 多根层级
构建器通过DisplayAreaGroup支持多根层级,对车载、折叠设备至关重要。示例代码来自DisplayAreaPolicyBuilder文档,创建前后排显示器独立根节点:
// Example from DisplayAreaPolicyBuilder Javadoc: RootDisplayArea firstRoot = new RootDisplayArea(wmService, "FirstRoot", FEATURE_FIRST_ROOT); DisplayAreaPolicyBuilder.HierarchyBuilder firstGroupHierarchy = new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot) .setTaskDisplayAreas(firstTdaList); return new DisplayAreaPolicyBuilder() .setRootHierarchy(rootHierarchy) .addDisplayAreaGroupHierarchy(firstGroupHierarchy) .setSelectRootForWindowFunc(selectRootForWindowFunc) .build(wmService, content);selectRootForWindowFunc是BiFunction<Integer, Bundle, RootDisplayArea>,依据窗口类型与启动参数,将每一个WindowToken路由到对应根节点。
24.2.5 层级校验规则
DisplayAreaPolicyBuilder.validate()对层级施加严格结构约束:
- Root 与 TDA ID 全局唯一:每一个
RootDisplayArea、TaskDisplayArea必须拥有全局唯一 featureId。 - 同一根下 feature ID 唯一:同一个
RootDisplayArea子节点 ID 不可重复;不同根节点可以复用相同 ID,实现跨根组织者。 - 输入法容器唯一:层级构建器必须存在且仅有一个输入法容器。
- 默认 TDA 唯一:有且仅有一个
TaskDisplayAreaID 等于FEATURE_DEFAULT_TASK_CONTAINER。 - ID 范围上限:ID 不能超过
FEATURE_VENDOR_LAST(20001)。 - 合法窗口层:根层级顶层必须包含窗口层(
FEATURE_TOP_LEVEL_ZOOM或FEATURE_WINDOWING_LAYER);缺失则构建器自动插入FEATURE_WINDOWING_LAYER。
// frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java if (!mRootHierarchyBuilder.hasValidWindowingLayer()) { mRootHierarchyBuilder.mFeatures.add(0 /* top level index */, new Feature.Builder(wmService.mPolicy, "WindowingLayer", FEATURE_WINDOWING_LAYER) .setExcludeRoundedCornerOverlay(false).all().build()); }24.2.6 Feature 定义与窗口类型匹配
每一个 Feature 使用 Builder 模式指定作用窗口类型,支持区间与排除:
// Example: WindowedMagnification targets everything below // the accessibility magnification overlay new Feature.Builder(wmService.mPolicy, "WindowedMagnification", FEATURE_TOP_LEVEL_ZOOM) .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY) .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY) .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new) .build()Feature.Builder 方法说明:
all():匹配全部窗口类型upTo(type):匹配从最低到该类型(包含该类型)except(type):从集合排除指定类型and(type):向集合追加指定窗口类型setNewDisplayAreaSupplier():自定义 DisplayArea 工厂(例如 Dimmable 用于放大变暗)setExcludeRoundedCornerOverlay():是否排除圆角叠加窗口
24.2.7 构建算法
HierarchyBuilder.build()实现生成 DisplayArea 树的核心算法:
算法保证:
- 连续 Z 轴区间、且 Feature 集合完全一致的范围,对应一个 DisplayArea。
- 只覆盖部分 Z 轴区间的 Feature 生成嵌套 DisplayArea 节点。
- TaskDisplayArea 精确插入在
APPLICATION_LAYER(任务下层窗口与任务上层窗口之间 Z 轴位置)。
24.2.8 DefaultSelectRootForWindowFunction
多根场景(车载前排 / 后排屏),DefaultSelectRootForWindowFunction完成窗口 token 路由:
// frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java public RootDisplayArea apply(Integer windowType, Bundle options) { if (mDisplayAreaGroupRoots.isEmpty()) { return mDisplayRoot; } if (options != null) { final int rootId = options.getInt(KEY_ROOT_DISPLAY_AREA_ID, FEATURE_UNDEFINED); if (rootId != FEATURE_UNDEFINED) { for (RootDisplayArea root : mDisplayAreaGroupRoots) { if (root.mFeatureId == rootId) return root; } } } return mDisplayRoot; }路由 key 为ActivityOptionsbundle 中的KEY_ROOT_DISPLAY_AREA_ID,启动器、系统组件可以通过该参数将窗口定向到指定根节点。
24.2.9 DisplayArea Organizers
Shell、SystemUI 可以注册IDisplayAreaOrganizer,接收特定 Feature 的 DisplayArea 出现、变更、消失回调。依靠该机制实现:
- 单手模式:注册
FEATURE_ONE_HANDED,对 DisplayArea 做缩放、平移。 - 窗口放大:绑定
FEATURE_TOP_LEVEL_ZOOM的 WindowedMagnification 特性。 - 应用缩小:注册
FEATURE_APP_ZOOM_OUT。
DisplayAreaOrganizerController管理注册,分发onDisplayAreaAppeared、onDisplayAreaInfoChanged、onDisplayAreaVanished回调。Organizer 会拿到SurfaceControlleash,可以重父或者做变换。
24.2.10 DisplayArea 中的方向处理
DisplayArea通过mSetIgnoreOrientationRequest标志在方向管理中起到关键作用。开启后,该 DisplayArea 会忽略下层应用固定方向请求,应用以黑边(letterbox)形式展示,而不去旋转整块屏幕。
// frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java boolean setIgnoreOrientationRequest(boolean ignoreOrientationRequest) { if (mSetIgnoreOrientationRequest == ignoreOrientationRequest) { return false; } mSetIgnoreOrientationRequest = ignoreOrientationRequest; // Check whether we should notify Display to update orientation // ... }该逻辑用于大屏设