一、背景:为何需要GLSurfaceView?
在应用开发中,图形绘制通常通过各种高级API如Android Canvas的drawXXX方法完成。但这些接口通常经过了层层封装,底层复杂的图形库调用对开发者透明,我们不知道Canvas从哪来,也不知道绘制方法到底做了啥。
有没有办法可以窥探app底层调用图形库接口呢?------答案就是GLSurfaceView。
GLSurfaceView虽然继承自SurfaceView,但两者却有本质区别:
-
SurfaceView: 通过Canvas高级接口绘制,经历HWUI收集指令→Skia图形库转换→OpenGL/Vulkan底层调用的复杂流程
-
GLSurfaceView: 提供直接的OpenGL原始绘制指令调用,让开发者能够直接操控底层图形渲染
本文将深入分析GLSurfaceView如何架起应用层与OpenGL ES之间的桥梁。
二、GLSurfaceView基础使用
在深入之前先看一下GLSurfaceView使用的简单例子,GLSurfaceView作为一个普通的View可以嵌入到Android的View树上,通过setRenderer接口设置一个Renderer对象,Renderer接口有3个方法:onSurfaceCrated、onSurfaceChanged、onDrawFrame,名字很清晰,分别表示:Surface初始化完成、Surface大小改变、绘制一帧,所以在应用端调用就是通过在onDrawFrame里直接调用GLE20提供的绘制方法进行绘制。
java
// GLSurfaceView使用的简单例子
class GLSurfaceViewDemo : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val glSurfaceView = GLSurfaceView(this)
// 关键配置
glSurfaceView.setEGLContextClientVersion(2) // 使用OpenGL ES 2.0
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY // 按需渲染
glSurfaceView.setRenderer(object : GLSurfaceView.Renderer {
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 设置清屏颜色为黑色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 设置视口大小
GLES20.glViewport(0, 0, width, height)
}
override fun onDrawFrame(gl: GL10?) {
// 执行清屏操作
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
...
// 其他GL绘制方法
}
})
setContentView(glSurfaceView)
}
}
核心疑问: 为何GLES20的静态方法调用能够直接绘制到View上?答案隐藏在GLSurfaceView的内部实现中。
三、OpenGL ES渲染基础:从应用到屏幕的8个关键步骤
在深入GLSurfaceView源码前,先了解标准的OpenGL ES应用渲染流程。
同时我们还需要回顾一个名词:句柄,掌握这个名词的含义对后面内容的理解有很大的帮助。句柄一般表示一个系统资源返回给了用户端,但用户端只能获取其标识(一般为一个整形变量),但是每办法获取其类型,也不能访问内部属性,比如C里的FILE*、Java里的File Descriptor等都是句柄。OpenGL 这种深度和系统打交道的交互会多次使用句柄的概念。
从应用发起绘制到画面显示到屏幕,需要经历下面8个关键步骤:
c
// 1. 获取显示设备句柄
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY)
// 2. 初始化显示连接
eglInitialize(display, nullptr, nullptr)
// 3. 选择配置(颜色、深度等参数)
eglChooseConfig(display, attributes, &config, 1, &num_config)
// 4. 创建渲染上下文(分配资源,维护状态机)
EGLContext context = eglCreateContext(display, config, nullptr, attrs)
// 5. 创建绘制表面(连接原生窗口系统)
EGLSurface surface = eglCreateWindowSurface(display, config, window, nullptr)
// 6. 绑定上下文到当前线程
eglMakeCurrent(display, surface, surface, context)
// 7. 执行OpenGL绘制命令
glClear(GL_COLOR_BUFFER_BIT)
glDrawArrays(...)
// 像素数据写入后缓冲区(Back Buffer)
// 8. 交换缓冲区,显示到屏幕
eglSwapBuffers(display, surface)
这8个步骤构成了任何OpenGL ES应用的基础框架,GLSurfaceView正是在Java层实现了这一流程。
四、GLSurfaceView源码深度解析
4.1 初始化入口:setRenderer方法
java
public void setRenderer(Renderer renderer) {
checkRenderThreadState() // 确保只设置一次
// 初始化默认配置工厂
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true)
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory()
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory()
}
mRenderer = renderer
mGLThread = new GLThread(mThisWeakRef) // 创建专属渲染线程
mGLThread.start()
}
这里创建了三个核心工厂对象,分别负责EGL配置选择、上下文创建和表面管理。
4.2 GLThread:渲染线程的核心引擎
GLThread的guardedRun()方法是整个渲染流程的调度中心:
java
private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
mHaveEglContext = false;
mHaveEglSurface = false;
mWantRenderNotification = false;
try {
...
while (true) { // 渲染循环
synchronized (sGLThreadManager) {
while (true) { // 控制循环
...
if (readyToDraw()) { // 准备绘制
...
//创建EGLContext上下文
mEglHelper.start();
...
}
...
}
...
//创建EGLSurface,把View的Surface封装成EGLSurface给OpenGL绘制
if (mEglHelper.createSurface()) {
...
}
// 在这之后,当前线程便可以执行绘制操作了
...
//获取GL对象,包装OpenGL API环境,这里使用GL10
gl = (GL10) mEglHelper.createGL();
...
//回调外部Renderer对象的`onSurfaceCreated()`方法
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
...
if (sizeChanged) {
...
//回调外部Renderer对象的`onSurfaceChanged()`方法
view.mRenderer.onSurfaceChanged(gl, w, h);
...
}
...
//回调外部Renderer对象的`onDrawFrame()`方法
view.mRenderer.onDrawFrame(gl);
...
//Egl交互内存,opengl使用的双内存缓冲,一个进行显示,另一个则后台进行绘制,绘制OK后,交互内存进行显示
int swapError = mEglHelper.swap();
...
}
} finally {
synchronized (sGLThreadManager) {
stopEglSurfaceLocked();
stopEglContextLocked();
}
}
}
// 是否可以绘制内容了
private boolean readyToDraw() {
return (!mPaused) // 没有暂停
&& mHasSurface // Surface是否已经创建(注意Surface和EGLSurface是不一样的)
&& (!mSurfaceIsBad) // EGLSurface是否可用
&& (mWidth > 0) // Surface的大小
&& (mHeight > 0)
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY) // 手动刷新或者自动刷新
);
}
guardedRun方法比较复杂,内部启动两个循环,外循环是渲染循环,每个循环都会绘制一帧, 内部循环状态检查各种状态。从readyToDraw()方法开始看,这个方法表示当前准备好 可以开始绘制了。readToDraw()检查当前是否可以进行绘制,如果可以进行绘制,则开始OpenGL上下文的初始化。
EglHelper.start()
java
public void start() {
...
mEgl = (EGL10) EGLContext.getEGL() // 获取EGL实例
...
// 步骤1:获取显示设备
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
...
// 步骤2:初始化显示连接
mEgl.eglInitialize(mEglDisplay, version)
...
// 步骤3:选择配置
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay)
...
// 步骤4:创建渲染上下文
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig)
}
这里的步骤就和前面介绍的app使用OpenGL的前步一样,先获取显示设备的句柄,然后初始化、接着进行配置,最后创建GL环境。
到这里GL上下文就创建好了, 下一步就是创建EGLSurface, 这里先重点介绍下EGLSurface和Android系统里的Surface的区别,首先它们都是绘制表面的意思,也就是绘制目标,内部都包含了BufferQueue用来保存绘制的结果。Surface是Android自身的概念,EGLSurface是OpenGL的概念,是夸平台的和平台无关,所以在Android系统上想要通过OpenGL绘制内容的话,要将原生窗口(如 Android 的 ANativeWindow)包装成 EGL Surface,作为 OpenGL 的渲染目标,连接 OpenGL 和平台窗口系统。通俗点的理解就是OpenGL内部会创建一个EGLSurface,但是FrameBuffer SurfaceView已经有了,所以需要将SurfaceView里Surface的FrameBuffer设置给EGLSurface, 用来作为OpenGL的绘制目标。
Surface创建与绑定:EglHelper.createSurface()
java
public boolean createSurface() {
// 步骤5:创建EGLSurface(包装Android的Surface)
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(
mEgl, mEglDisplay, mEglConfig, view.getHolder())
// 步骤6:绑定到当前线程
return mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
}
接下来mEgl.eglMakeCurrent将EGLSurface和EGLContext绑定到当前线程,用来在当前线程做绘制, 这步在内部会把Context和Surface设置到ThreadLocalStorage, 这样在后面的OpenGL绘制命令就不需要每次都传入context参数了。
这一步执行完后,前面介绍app使用OpenGL渲染8步的前六步就做完了,剩下的就是绘制和缓存交换。
回调Renderer业务绘制内容
接下来就是调用了外部设置的Renderer三个接口:
java
//回调外部Renderer对象的`onSurfaceCreated()`方法
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
...
if (sizeChanged) {
...
//回调外部Renderer对象的`onSurfaceChanged()`方法
view.mRenderer.onSurfaceChanged(gl, w, h);
...
}
...
//回调外部Renderer对象的`onDrawFrame()`方法
view.mRenderer.onDrawFrame(gl);
这时用户一般就在onDrawFrame()里调用OpenGL的绘制,方法执行完之后绘制数据到了Surface里BufferQueue里后端,接着调用mEglHelper.swap()把数据显示给前端。
缓冲区交换:EglHelper.swap()
java
public int swap() {
// 步骤8:交换前后缓冲区
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError()
}
return EGL10.EGL_SUCCESS
}
mEglHelper.swap()的代码 SurfaceView使用双内存缓冲机制,内部有两个Frambuffer,一个进行前台显示,另一个则后台进行绘制,绘制OK后,交互内存进行显示。eglSwapBuffers的目的是渲染好的FrameBuffer和前台的Framebuffer交互后显示出来
4.3 生命周期管理:暂停与恢复机制
- 暂停实现
java
mRequestPaused = true
sGLThreadManager.notifyAll() // 唤醒等待的渲染线程
在渲染循环中检测到状态变化:
java
if (mPaused != mRequestPaused) {
pausing = mRequestPaused
mPaused = mRequestPaused
if (pausing) {
// 释放EGLSurface和EGLContext(可选)
stopEglSurfaceLocked()
if (!preserveEglContextOnPause) {
stopEglContextLocked()
}
}
}
暂停后,readyToDraw()返回false,渲染线程在内部循环中等待。
*恢复实现
onResume()将状态重置:
java
mRequestPaused = false
mRequestRender = true
sGLThreadManager.notifyAll()
渲染线程检测到状态变化后重新初始化EGL环境并继续渲染循环。
4.4 线程退出机制
GLThread通过mShouldExit标志控制退出:
java
public void requestExitAndWait() {
synchronized(sGLThreadManager) {
mShouldExit = true
sGLThreadManager.notifyAll()
while (!mExited) {
sGLThreadManager.wait() // 等待线程完全退出
}
}
}
退出触发点在onDetachedFromWindow()中调用,确保在View脱离窗口时安全停止渲染线程。
五、核心原理总结
-
线程分离:GLSurfaceView创建专用渲染线程,避免UI线程阻塞
-
EGL桥梁作用:通过EGL连接OpenGL ES API与Android原生窗口系统
-
双缓冲机制:前缓冲区显示,后缓冲区渲染,通过交换实现流畅显示
-
状态机管理:完善的暂停/恢复/退出生命周期管理
-
Surface封装:将Android Surface包装为EGLSurface,实现平台无关的OpenGL渲染
GLSurfaceView的本质是在Java层完整实现了OpenGL ES的标准渲染流程,为Android应用提供了直接调用底层图形库的能力。通过理解其内部机制,开发者能够更好地优化渲染性能,处理复杂的图形需求。