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 的内容被映射到多个显示区域。

Android 显示系统分层与多屏数据流 应用进程(App Process) GLSurfaceView / SurfaceView EGLSurface + GL 渲染 Surface(生产者) GraphicBuffer 队列 系统服务层(System Server) SurfaceFlinger 合成 / 图层管理 / VSync WindowManagerService 窗口层级 / 位置 / 焦点 DisplayManagerService 显示设备枚举 / 逻辑显示 BufferQueue 硬件抽象与显示设备 Hardware Composer HWC / 叠加合成 Display 0(主屏) 内置 / 默认显示 Display 1(副屏) HDMI / DP / 虚拟屏 Display N 扩展显示 多屏关键:Surface 通过 SurfaceControl 绑定到指定 Display,WindowManager 负责窗口在目标 Display 上的布局与层级。 应用侧通过 DisplayManager 获取 Display 列表,再结合 Presentation / MediaProjection / VirtualDisplay 实现副屏输出。

二、方法流程:多显示 API 的调用路径

要在应用中操作多块屏幕,通常遵循以下流程:

  1. 获取显示列表:通过 DisplayManager.getDisplays() 或 getDisplays(DISPLAY_CATEGORY_PRESENTATION) 拿到当前可用 Display。
  2. 选择目标显示:根据 Display 的 ID、名称、分辨率、刷新率判断主屏与副屏。
  3. 创建承载窗口:使用 Presentation(继承自 Dialog)在指定 Display 上创建窗口,或通过 WindowManager.LayoutParams 指定 displayId。
  4. 绑定渲染表面:在窗口内放置 SurfaceView / TextureView,交给 EGL 创建 EGLSurface 进行 GL 渲染。
  5. 监听热插拔:注册 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 的同步问题。

提示:副屏渲染建议使用独立的 Choreographer 或渲染线程,避免主屏 VSync 与副屏刷新率不一致导致卡顿。

四、误区与复盘

常见误区 正确理解
认为 Surface 就是一块显存 Surface 是生产者接口,背后是 BufferQueue,实际缓冲由 GraphicBuffer 管理
直接操作 SurfaceFlinger 做多屏 应用层应通过 WindowManager / DisplayManager / Presentation 等公开 API
忽略 Display 热插拔 必须监听 DisplayListener,动态创建或销毁副屏渲染资源
主副屏共用一个 EGLSurface 不同 Display 需要各自的窗口 Surface,EGLSurface 不能跨 Display 复用
认为副屏分辨率与主屏一致 应通过 Display.Mode 查询实际物理分辨率与刷新率,动态适配视口
注意:在 Android 10 及以上版本,后台启动 Activity 受到限制,副屏 Presentation 的创建需确保应用处于前台或拥有相应权限,否则可能无法显示。

复盘时建议重点检查三点:一是 Display 列表是否在配置变更后重新获取;二是每个 Surface 是否绑定了正确的 Display;三是 EGLContext 的共享与同步是否引入额外开销。只有把显示系统与窗口管理理解清楚,后续的多屏渲染架构才能稳定落地。