Android GLSurfaceView 完全指南
1. 概述
GLSurfaceView 是 Android 平台上专门用于 OpenGL ES 渲染的 View 组件。它继承自 SurfaceView,提供了以下核心功能:
- 专用的 OpenGL 渲染线程:自动创建和管理 EGL 上下文
- 渲染生命周期管理 :通过
Renderer接口控制渲染回调 - 渲染模式控制:支持连续渲染和按需渲染
- EGL 配置管理:管理显示表面和上下文
2. 核心架构
2.1 组件关系
scss
GLSurfaceView
├── SurfaceView (父类)
│ └── Surface (底层显示表面)
├── EGL (管理 OpenGL 上下文)
│ ├── EGLDisplay
│ ├── EGLSurface
│ └── EGLContext
├── Renderer (渲染回调接口)
├── RenderThread (独立渲染线程)
└── Queue (消息队列,用于线程间通信)
2.2 核心类
classDiagram
class GLSurfaceView {
+setRenderer(Renderer)
+setRenderMode(int)
+queueEvent(Runnable)
+requestRender()
+onPause()
+onResume()
}
class Renderer {
<>
+onSurfaceCreated(GL10, EGLConfig)
+onSurfaceChanged(GL10, int, int)
+onDrawFrame(GL10)
}
class EGLConfig {
+getConfigAttributes()
+chooseConfig()
}
GLSurfaceView --> Renderer
GLSurfaceView --> EGLConfig
3. Renderer 接口详解
Renderer 是 GLSurfaceView 中最核心的接口,包含三个回调方法:
3.1 onSurfaceCreated(GL10 gl, EGLConfig config)
- 调用时机:首次创建 EGL 表面时,或 EGL 上下文丢失重新创建时
- 主要用途 :
- 初始化 OpenGL 状态
- 加载纹理
- 编译着色器
- 设置背景颜色
3.2 onSurfaceChanged(GL10 gl, int width, int height)
- 调用时机:视图尺寸发生变化时(包括首次创建)
- 主要用途 :
- 设置视口 (Viewport)
- 更新投影矩阵
- 适配屏幕比例
3.3 onDrawFrame(GL10 gl)
- 调用时机:每次需要绘制新帧时
- 主要用途 :
- 清除颜色和深度缓冲
- 绘制场景
- 交换缓冲
4. 渲染模式
GLSurfaceView 支持两种渲染模式:
4.1 RENDERMODE_CONTINUOUSLY (默认值)
java
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
- 行为 :每 16ms(60fps)自动回调
onDrawFrame() - 适用场景:动画、游戏、实时渲染
4.2 RENDERMODE_WHEN_DIRTY
java
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- 行为 :仅在调用
requestRender()时回调onDrawFrame() - 适用场景:静态场景、触控交互更新
5. 生命周期管理
java
public class GLActivity extends Activity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setRenderer(new MyRenderer());
setContentView(glSurfaceView);
}
@Override
protected void onPause() {
super.onPause();
glSurfaceView.onPause(); // 暂停渲染线程
}
@Override
protected void onResume() {
super.onResume();
glSurfaceView.onResume(); // 恢复渲染线程
}
}
⚠️ 重要 :必须在
onPause()和onResume()中调用 GLSurfaceView 的对应方法,否则可能导致应用崩溃或内存泄漏。
6. 完整示例代码
6.1 旋转三角形示例
java
// MainActivity.java
public class MainActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 检查 OpenGL ES 支持
if (!isOpenGLES2Supported()) {
Toast.makeText(this, "设备不支持 OpenGL ES 2.0", Toast.LENGTH_LONG).show();
finish();
return;
}
glSurfaceView = new GLSurfaceView(this);
// 设置 OpenGL ES 2.0
glSurfaceView.setEGLContextClientVersion(2);
// 设置渲染器
glSurfaceView.setRenderer(new TriangleRenderer());
// 设置为按需渲染(节省电量)
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(glSurfaceView);
}
private boolean isOpenGLES2Supported() {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
return info.reqGlEsVersion >= 0x20000;
}
@Override
protected void onPause() {
super.onPause();
glSurfaceView.onPause();
}
@Override
protected void onResume() {
super.onResume();
glSurfaceView.onResume();
}
}
// TriangleRenderer.java
public class TriangleRenderer implements GLSurfaceView.Renderer {
private FloatBuffer vertexBuffer;
private int program;
private int positionHandle;
private int colorHandle;
private int mvpMatrixHandle;
private float[] mvpMatrix = new float[16];
private float[] projectionMatrix = new float[16];
private float[] viewMatrix = new float[16];
private float rotationAngle = 0;
// 三角形顶点坐标(归一化坐标)
static final float[] TRIANGLE_COORDS = {
0.0f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
// 颜色值(RGBA)
private final float[] triangleColor = {0.6f, 0.2f, 0.8f, 1.0f};
// 顶点着色器
private final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = uMVPMatrix * vPosition;" +
"}";
// 片段着色器
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
public TriangleRenderer() {
// 初始化顶点缓冲区
ByteBuffer bb = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(TRIANGLE_COORDS);
vertexBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置背景色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 编译着色器并创建程序
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
GLES20.glLinkProgram(program);
// 获取句柄
positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
colorHandle = GLES20.glGetUniformLocation(program, "vColor");
mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
// 启用深度测试
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// 设置投影矩阵
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
// 设置相机位置
Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
}
@Override
public void onDrawFrame(GL10 gl) {
float[] scratch = new float[16];
// 计算旋转
long time = SystemClock.uptimeMillis() % 4000L;
float angle = 0.090f * ((int) time);
// 组合矩阵
Matrix.multiplyMM(scratch, 0, projectionMatrix, 0, viewMatrix, 0);
Matrix.rotateM(scratch, 0, angle, 0, 0, 1.0f);
// 使用程序
GLES20.glUseProgram(program);
// 启用位置属性
GLES20.glEnableVertexAttribArray(positionHandle);
// 设置顶点数据
GLES20.glVertexAttribPointer(positionHandle, 3,
GLES20.GL_FLOAT, false, 0, vertexBuffer);
// 设置颜色
GLES20.glUniform4fv(colorHandle, 1, triangleColor, 0);
// 设置 MVP 矩阵
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, scratch, 0);
// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
// 禁用顶点数组
GLES20.glDisableVertexAttribArray(positionHandle);
// 请求下一帧渲染
if (glSurfaceView != null) {
glSurfaceView.requestRender();
}
}
private int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
7. 高级用法
7.1 自定义 EGL 配置
java
glSurfaceView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
// 配置 EGL 属性
int[] attributes = {
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 8,
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_STENCIL_SIZE, 8,
EGL10.EGL_SAMPLE_BUFFERS, 1,
EGL10.EGL_SAMPLES, 4,
EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfig = new int[1];
egl.eglChooseConfig(display, attributes, configs, 1, numConfig);
return configs[0];
}
});
7.2 线程间通信
java
// 从 UI 线程向渲染线程发送任务
glSurfaceView.queueEvent(new Runnable() {
@Override
public void run() {
// 此代码在渲染线程中执行
updateSomeGLState();
}
});
7.3 多点触控交互
java
public class TouchGLSurfaceView extends GLSurfaceView {
private Renderer renderer;
private ScaleGestureDetector scaleDetector;
public TouchGLSurfaceView(Context context) {
super(context);
scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
scaleDetector.onTouchEvent(event);
final int pointerCount = event.getPointerCount();
final int action = event.getActionMasked();
// 处理触摸事件
queueEvent(new Runnable() {
@Override
public void run() {
handleTouchEvent(action, pointerCount, event);
}
});
return true;
}
}
8. 性能优化建议
8.1 渲染优化
- 使用 VBO (Vertex Buffer Objects) 减少 CPU-GPU 数据传输
- 批量绘制 减少绘制调用次数
- 对象池 减少内存分配
- 纹理图集 减少纹理绑定次数
8.2 内存优化
- 在
onPause()中释放 OpenGL 资源 - 使用
BitmapFactory.Options控制纹理内存 - 及时回收不再使用的纹理
8.3 功耗优化
java
// 静态场景使用按需渲染
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
// 降低帧率(非游戏场景)
// 使用 Handler 控制 requestRender() 调用频率
9. 常见问题与陷阱
9.1 EGL 上下文丢失
- 场景:Activity 重建或设备配置变化
- 解决 :在
onSurfaceCreated()中重新创建所有 OpenGL 资源
9.2 线程安全问题
- 规则:OpenGL 调用仅在渲染线程执行
- 通信 :使用
queueEvent()与渲染线程交互
9.3 内存泄漏
- 正确管理 Activity 生命周期
- 在
onPause()中停止动画循环 - 使用弱引用避免持有 Activity 引用
9.4 兼容性问题
java
// 检查 GPU 能力
String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS);
if (extensions.contains("GL_OES_texture_npot")) {
// 支持非2的幂次纹理
}
10. 其他对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| GLSurfaceView | 简单易用,自动管理上下文 | 灵活性受限 | 大多数 OpenGL 应用 |
| TextureView | 支持变换动画,可与其他 View 组合 | 性能略低 | 需要与其他 UI 元素交互 |
| SurfaceView + EGL 手动管理 | 完全控制 | 实现复杂 | 高性能游戏,自定义渲染管线 |
| Vulkan API | 最高性能,低开销 | 开发成本高 | 高端游戏,专业应用 |
11. 实践总结
这个组件是 Android OpenGL 开发的基础,正确理解和使用它对于构建高性能的图形应用至关重要。开发使用有以下几点建议:
- 始终遵循生命周期 :在
onPause()/onResume()中管理 GLSurfaceView - 资源管理 :在
onSurfaceCreated()中创建资源,在 Context 销毁时及时清理 - 线程安全:所有 OpenGL 操作应在渲染线程执行,避免阻塞主线程
- 设备兼容 :可以使用
setEGLContextClientVersion()指定 OpenGL ES 版本 - 错误处理 :可以使用
GLES20.glGetError()获取错误返回值,应对异常