4. 显示系统与窗口管理
在 OpenGL ES 多屏渲染工程中,真正决定“画面最终出现在哪块屏幕、以什么层级和方向出现”的,并不是 EGL 或 GL 本身,而是 Android 的显示与窗口管理体系。理解 Surface、SurfaceFlinger、WindowManager 以及多显示 API 之间的分工,是后续实现双屏异显、副屏独立渲染、跨屏拼接等能力的基础。
一、核心原理:从绘制缓冲到屏幕像素的四层结构
Android 的显示链路可以抽象为四层:应用层的绘制目标(Surface)、合成层的统一管理者(SurfaceFlinger)、窗口策略层(WindowManager / WindowManagerService)、以及物理显示抽象层(DisplayManager 与 Display)。应用通过 EGL 把 OpenGL ES 渲染结果写入 Surface 的图形缓冲区(GraphicBuffer),SurfaceFlinger 收集所有可见 Surface,按 Z 序和可见区域进行合成,再通过 Hardware Composer(HWC)或 GPU 输出到具体 Display。
关键点在于:Surface 是生产者,SurfaceFlinger 是消费者与合成者,WindowManager 决定窗口的位置与层级,Display 决定最终输出目标。多屏渲染的本质,就是让不同的 Surface 绑定到不同的 Display,或让同一 Surface 的内容被映射到多个显示区域。
二、方法流程:多显示 API 的调用路径
要在应用中操作多块屏幕,通常遵循以下流程:
- 获取显示列表:通过
DisplayManager.getDisplays()或getDisplays(DISPLAY_CATEGORY_PRESENTATION)拿到当前可用 Display。 - 选择目标显示:根据 Display 的 ID、名称、分辨率、刷新率判断主屏与副屏。
- 创建承载窗口:使用
Presentation(继承自 Dialog)在指定 Display 上创建窗口,或通过WindowManager.LayoutParams指定displayId。 - 绑定渲染表面:在窗口内放置 SurfaceView / TextureView,交给 EGL 创建 EGLSurface 进行 GL 渲染。
- 监听热插拔:注册
DisplayManager.DisplayListener,在onDisplayAdded/Removed/Changed中动态调整渲染目标。
DisplayManager dm = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
Display[] displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
for (Display d : displays) {
Log.i("MultiDisplay", "id=" + d.getDisplayId()
+ " name=" + d.getName()
+ " size=" + d.getMode().getPhysicalWidth() + "x" + d.getMode().getPhysicalHeight()
+ " refresh=" + d.getRefreshRate());
}
// 在副屏上创建 Presentation
Presentation pres = new Presentation(context, displays[0]);
pres.setContentView(R.layout.second_screen);
pres.show();
三、案例/场景:双屏异显的典型实现
以车载双屏为例:中控主屏显示导航与媒体,副屏(后排娱乐屏)独立播放视频。实现时,主屏使用 Activity 默认窗口,副屏通过 Presentation 创建独立窗口,并在其中放置 SurfaceView。两个 SurfaceView 各自持有独立的 EGLContext 与 EGLSurface,渲染线程互不阻塞。若需要共享纹理,可通过 eglCreateContext 的 share_context 参数共享资源,但要注意跨 Display 的同步问题。
四、误区与复盘
| 常见误区 | 正确理解 |
|---|---|
| 认为 Surface 就是一块显存 | Surface 是生产者接口,背后是 BufferQueue,实际缓冲由 GraphicBuffer 管理 |
| 直接操作 SurfaceFlinger 做多屏 | 应用层应通过 WindowManager / DisplayManager / Presentation 等公开 API |
| 忽略 Display 热插拔 | 必须监听 DisplayListener,动态创建或销毁副屏渲染资源 |
| 主副屏共用一个 EGLSurface | 不同 Display 需要各自的窗口 Surface,EGLSurface 不能跨 Display 复用 |
| 认为副屏分辨率与主屏一致 | 应通过 Display.Mode 查询实际物理分辨率与刷新率,动态适配视口 |
复盘时建议重点检查三点:一是 Display 列表是否在配置变更后重新获取;二是每个 Surface 是否绑定了正确的 Display;三是 EGLContext 的共享与同步是否引入额外开销。只有把显示系统与窗口管理理解清楚,后续的多屏渲染架构才能稳定落地。