Android GLSurfaceView 完全指南

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 开发的基础,正确理解和使用它对于构建高性能的图形应用至关重要。开发使用有以下几点建议:

  1. 始终遵循生命周期 :在 onPause()/onResume() 中管理 GLSurfaceView
  2. 资源管理 :在 onSurfaceCreated() 中创建资源,在 Context 销毁时及时清理
  3. 线程安全:所有 OpenGL 操作应在渲染线程执行,避免阻塞主线程
  4. 设备兼容 :可以使用 setEGLContextClientVersion() 指定 OpenGL ES 版本
  5. 错误处理 :可以使用 GLES20.glGetError() 获取错误返回值,应对异常
相关推荐
Flynt1 小时前
升级Flutter 3.44,我踩了HCPP和AGP 9的坑
android·flutter·dart
白色牙膏2 小时前
Cocos Creator 2.4.x 接入 AdMob 插件的迁移实践
android
我命由我123454 小时前
C++ - 面向对象 - 常成员函数
android·java·linux·c语言·开发语言·c++·算法
tryqaaa_4 小时前
学习日志(四)【php反序列化魔术方法以及pop构造配实战】
android
Java小学生丶6 小时前
记录一下我的 Gradle 开发环境配置过程
android·java·gradle·maven·安卓
问心无愧05137 小时前
ctf show web 入门256
android·前端·笔记
霸道流氓气质7 小时前
MySQL 索引设计实战指南
android·数据库·mysql
R语言爱好者7 小时前
叠氮酸介绍
android
方白羽7 小时前
Android WebView 中实现第三方 QQ 登录的架构与流程详解
android·app