11.1 DisplayManager服务:屏幕的“大管家”
DisplayManager是什么?说白了,它就是Android系统里管理所有显示设备的服务。你插拔HDMI、切换主副屏、调整分辨率,背后都是它在干活。
在MTK8678上,DisplayManager需要处理三种屏幕:
- 主屏(Primary Display):系统默认的显示设备,通常是内嵌的LCD或OLED
- 副屏(Secondary Display):通过MIPI DSI、eDP等接口扩展的屏幕
- 外接屏(External Display):HDMI、DP、USB-C等热插拔设备
我个人习惯把DisplayManager看作一个“屏幕注册中心”。每个屏幕启动时,都会向它注册自己的物理参数:
// 伪代码:DisplayManager注册屏幕 DisplayManagerService dms = new DisplayManagerService(context); dms.registerDisplay(displayId, DisplayDeviceInfo.builder() .setWidth(1920) .setHeight(1080) .setRefreshRate(60f) .setType(Display.TYPE_EXTERNAL) .build());嗯,这里要注意:MTK平台有个特殊之处,它支持虚拟显示(Virtual Display)。比如投屏功能,其实就是在DisplayManager里注册了一个虚拟的Display,然后通过MediaCodec编码输出。我在做车机项目时,就利用这个特性实现了“仪表盘+中控+副驾娱乐”三屏独立显示。
核心要点:DisplayManager负责屏幕的生命周期管理,包括添加、移除、配置变更。驱动层只需要上报EDID或硬件参数,剩下的逻辑由它处理。
11.2 SurfaceFlinger多屏策略:合成器的“调度艺术”
SurfaceFlinger是Android的图形合成引擎。单屏时它很简单,把所有图层叠在一起输出就行。但多屏呢?每个屏幕的合成策略完全不同。
我遇到过最头疼的问题:副屏显示内容总是延迟半秒。后来发现是SurfaceFlinger的同步点(Vsync)没对齐。MTK8678的硬件支持多个独立的Vsync源,但软件层默认只用一个。
多屏策略主要有三种模式:
| 模式 | 描述 | 适用场景 |
|---|---|---|
| 克隆模式(Clone) | 所有屏幕显示相同内容 | 演示、教学 |
| 扩展模式(Extend) | 每个屏幕显示独立内容 | 多任务、仪表盘+中控 |
| 镜像模式(Mirror) | 主屏内容复制到副屏,但分辨率自适应 | 投屏、车载后座娱乐 |
在代码层面,SurfaceFlinger通过DisplayDevice对象管理每个屏幕:
// SurfaceFlinger多屏配置示例 sp<DisplayDevice> primaryDisplay = new DisplayDevice( DisplayDevice::DISPLAY_PRIMARY, hwcDisplayId, isSecure, canAllotLayer); sp<DisplayDevice> secondaryDisplay = new DisplayDevice( DisplayDevice::DISPLAY_EXTERNAL, hwcDisplayId2, isSecure, canAllotLayer); // 设置独立合成策略 primaryDisplay->setCompositionStrategy(CompositionStrategy::DIRECT); secondaryDisplay->setCompositionStrategy(CompositionStrategy::CLIENT);避坑指南:我曾经在扩展模式下,副屏的触摸坐标一直映射不对。后来发现是SurfaceFlinger的displayId和Input系统的displayId没同步。记住:每个屏幕的ID必须全局唯一,且与硬件端口绑定。
11.3 HWC(硬件合成器)多屏支持:让GPU喘口气
HWC是硬件合成器,它的任务是把多个图层合并成一个,然后交给显示控制器。没有HWC的话,所有合成都得GPU来做,功耗和性能都扛不住。
MTK8678的HWC支持最多4个物理显示通道,每个通道可以独立处理图层。我调试时发现,HWC的prepare()函数会返回每个图层的合成方式:
- HWC_OVERLAY:硬件直接合成,性能最好
- HWC_GLES:需要GPU辅助,性能次之
- HWC_CURSOR:鼠标光标,特殊处理
- HWC_SIDEBAND:视频流直通,比如HDMI输入
多屏场景下,HWC需要为每个屏幕维护独立的图层列表(Layer List)。举个例子:
// HWC多屏图层配置 hwc_display_contents_1_t* displayContents[2]; // 主屏:3个图层(状态栏、应用、导航栏) displayContents[0]->numHwLayers = 3; displayContents[0]->hwLayers[0].compositionType = HWC_OVERLAY; displayContents[0]->hwLayers[1].compositionType = HWC_OVERLAY; displayContents[0]->hwLayers[2].compositionType = HWC_GLES; // 副屏:2个图层(视频、UI) displayContents[1]->numHwLayers = 2; displayContents[1]->hwLayers[0].compositionType = HWC_SIDEBAND; // 视频直通 displayContents[1]->hwLayers[1].compositionType = HWC_OVERLAY;警告:HWC的set()函数调用顺序很重要!必须先设置主屏,再设置副屏。我曾经调换了顺序,结果副屏显示正常,主屏却黑屏了。MTK的HWC驱动对显示通道的初始化顺序有严格要求。
你想想看,为什么HWC要区分这么多合成方式?其实就是为了减少GPU负载。视频流用SIDEBAND直通,GPU根本不用碰;静态UI用OVERLAY,硬件自己搞定。只有复杂的3D渲染才需要GPU介入。我在优化车载多屏系统时,把副屏的导航地图从GLES改成OVERLAY,帧率直接从30fps飙到60fps。
11.4 三者的协作流程
最后,我画个简单的流程图(用文字描述):
- DisplayManager检测到新屏幕插入,创建
DisplayDevice并通知SurfaceFlinger - SurfaceFlinger为每个屏幕创建独立的
LayerStack,并调用HWC的prepare() - HWC分析每个屏幕的图层,决定哪些用硬件合成,哪些交给GPU
- SurfaceFlinger执行GPU合成(如果有),然后调用HWC的
set()提交最终结果 - HWC把合成后的图像送到对应的显示控制器,刷新到屏幕上
嗯,这里有个细节:MTK8678支持异步合成。主屏在刷新时,副屏可以同时合成,互不干扰。但前提是HWC驱动必须正确配置displaySync属性。我记得有次客户反馈副屏闪烁,查了半天发现是同步信号没对齐,改了一个寄存器就解决了。
总结:DisplayManager管注册,SurfaceFlinger管策略,HWC管合成。三者配合好了,多屏显示才能流畅稳定。下一章我们会深入HWC的驱动层实现,包括MTK特有的disp_session和disp_buffer管理。