惊爆!深入剖析 Android GLSurfaceView 使用原理

惊爆!深入剖析 Android GLSurfaceView 使用原理

一、引言

在 Android 开发的广袤领域中,图形和游戏开发始终占据着重要的地位。为了实现高质量的图形渲染和流畅的动画效果,开发者们需要借助强大的工具和组件。Android 系统提供了多种用于图形处理的视图,其中 GLSurfaceView 以其高效、灵活的特点,成为了开发者进行 OpenGL 渲染的首选视图。

GLSurfaceView 是 Android 平台上用于实现 OpenGL ES 渲染的核心视图组件。它封装了 OpenGL ES 的底层操作,为开发者提供了一个便捷的接口,使得开发者可以专注于图形渲染的逻辑实现,而无需过多关注 OpenGL ES 的复杂细节。通过 GLSurfaceView,开发者可以轻松地创建 2D 或 3D 图形界面,实现各种炫酷的视觉效果,如游戏场景、动画演示、数据可视化等。

本文将从源码级别深入分析 GLSurfaceView 的使用原理,涵盖其继承体系、构造函数、生命周期管理、渲染机制、线程管理等多个方面,带领读者逐步揭开 GLSurfaceView 的神秘面纱,帮助开发者更好地理解和运用这一强大的工具。

二、GLSurfaceView 概述

2.1 什么是 GLSurfaceView

GLSurfaceView 是 Android 系统中专门用于 OpenGL ES 渲染的视图组件,它继承自 SurfaceViewSurfaceView 作为一种特殊的视图,拥有独立的绘图表面,能够在独立的线程中进行绘制操作,从而避免了主线程的阻塞,保证了界面的流畅性。GLSurfaceViewSurfaceView 的基础上,进一步封装了 OpenGL ES 的相关操作,提供了一个简单而强大的接口,使得开发者可以方便地进行 OpenGL ES 渲染。

2.2 常见应用场景

  • 游戏开发 :在 Android 游戏开发中,GLSurfaceView 被广泛应用于创建 2D 或 3D 游戏场景。通过 OpenGL ES 的强大功能,可以实现逼真的图形效果、流畅的动画和交互体验。例如,在一些角色扮演游戏、射击游戏和策略游戏中,GLSurfaceView 用于渲染游戏世界、角色模型、特效等。
  • 图形演示 :在一些需要展示复杂图形和动画的应用中,GLSurfaceView 可以发挥重要作用。例如,在科学教育应用中,用于展示分子结构、物理模型等;在广告宣传应用中,用于展示产品的 3D 模型和动画效果。
  • 数据可视化 :将数据以图形化的方式展示出来,能够更直观地传达信息。GLSurfaceView 可以用于创建各种数据可视化界面,如柱状图、折线图、饼图等,以及更复杂的 3D 数据可视化场景,如地理信息系统(GIS)中的地图展示、医学影像的 3D 重建等。

2.3 简单示例代码

以下是一个简单的 GLSurfaceView 使用示例,展示了如何创建一个基本的 GLSurfaceView 并进行简单的 OpenGL 渲染:

java 复制代码
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

// 自定义渲染器类,实现 GLSurfaceView.Renderer 接口
class MyRenderer implements GLSurfaceView.Renderer {
    @Override
    // 当 OpenGL ES 上下文被创建时调用,用于进行初始化操作
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 设置清屏颜色为黑色
        gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    @Override
    // 当 OpenGL ES 视图的大小发生变化时调用,用于设置视口和投影矩阵
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // 设置视口大小为整个视图的大小
        gl.glViewport(0, 0, width, height);
    }

    @Override
    // 每帧渲染时调用,用于进行实际的绘制操作
    public void onDrawFrame(GL10 gl) {
        // 清除颜色缓冲区
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    }
}

public class MainActivity extends Activity {
    private GLSurfaceView mGLSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 创建 GLSurfaceView 实例
        mGLSurfaceView = new GLSurfaceView(this);
        // 设置自定义的渲染器
        mGLSurfaceView.setRenderer(new MyRenderer());
        // 设置渲染模式为连续渲染
        mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
        // 将 GLSurfaceView 设置为当前 Activity 的内容视图
        setContentView(mGLSurfaceView);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 在 Activity 恢复时,恢复 GLSurfaceView 的渲染
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 在 Activity 暂停时,暂停 GLSurfaceView 的渲染
        mGLSurfaceView.onPause();
    }
}

在这个示例中,我们创建了一个自定义的渲染器 MyRenderer,实现了 GLSurfaceView.Renderer 接口的三个重要方法:onSurfaceCreatedonSurfaceChangedonDrawFrame。在 MainActivity 中,我们创建了 GLSurfaceView 实例,设置了渲染器和渲染模式,并将其设置为当前 Activity 的内容视图。同时,我们在 onResumeonPause 方法中分别调用了 mGLSurfaceView.onResume()mGLSurfaceView.onPause() 来管理 GLSurfaceView 的生命周期。

三、GLSurfaceView 的继承体系

3.1 继承关系

GLSurfaceView 的继承关系如下:

plaintext 复制代码
java.lang.Object
    ↳ android.view.View
        ↳ android.view.SurfaceView
            ↳ android.opengl.GLSurfaceView

从继承关系可以看出,GLSurfaceView 继承了 SurfaceView 的所有特性,同时在此基础上进行了扩展,增加了 OpenGL ES 渲染的功能。

3.2 继承带来的特性

  • 独立绘图表面 :继承自 SurfaceViewGLSurfaceView 拥有独立的绘图表面,能够在独立的线程中进行绘制操作。这使得 GLSurfaceView 可以实现高效的渲染,避免了主线程的阻塞,保证了界面的流畅性。
  • 视图特性 :作为 View 的子类,GLSurfaceView 拥有 View 的所有视图特性,如布局参数、事件处理、动画效果等。开发者可以像使用普通 View 一样对 GLSurfaceView 进行布局和操作。
  • 生命周期管理GLSurfaceView 继承了 View 的生命周期管理机制,开发者可以在 ActivityonResumeonPause 方法中分别调用 GLSurfaceViewonResumeonPause 方法,来管理 GLSurfaceView 的渲染状态,确保资源的合理使用。

四、GLSurfaceView 的构造函数

4.1 构造函数种类

GLSurfaceView 提供了多个构造函数,以满足不同的初始化需求:

  • GLSurfaceView(Context context):这是最简单的构造函数,只需要传入一个上下文对象。常用于在代码中动态创建 GLSurfaceView 实例。
java 复制代码
// 创建一个新的 GLSurfaceView 实例,传入当前活动的上下文
GLSurfaceView glSurfaceView = new GLSurfaceView(this);
  • GLSurfaceView(Context context, AttributeSet attrs):除了上下文对象,还可以传入一个属性集。这个属性集通常来自 XML 布局文件,用于从布局文件中获取 GLSurfaceView 的属性设置。
java 复制代码
// 从 XML 布局文件中解析属性集
AttributeSet attrs = ...; 
// 创建 GLSurfaceView 实例,传入上下文和属性集
GLSurfaceView glSurfaceView = new GLSurfaceView(this, attrs);

4.2 构造函数源码分析

GLSurfaceView(Context context, AttributeSet attrs) 构造函数为例,其源码如下:

java 复制代码
public GLSurfaceView(Context context, AttributeSet attrs) {
    // 调用父类 SurfaceView 的构造函数,传入上下文和属性集
    super(context, attrs);
    // 初始化 GLSurfaceView 的内部状态
    init(context, attrs, 0);
}

private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    // 获取 OpenGL ES 的配置选择器,用于选择合适的 EGL 配置
    mEGLConfigChooser = new SimpleEGLConfigChooser(true);
    // 获取 EGL 上下文工厂,用于创建和管理 EGL 上下文
    mEGLContextFactory = new DefaultContextFactory();
    // 获取 EGL 显示工厂,用于创建和管理 EGL 显示
    mEGLDisplayFactory = new DefaultDisplayFactory();
    // 创建一个新的渲染器线程,用于进行 OpenGL ES 渲染
    mGLThread = new GLThread(mThisWeakRef);
    // 设置渲染模式为连续渲染
    mRenderMode = RENDERMODE_CONTINUOUSLY;
    // 从属性集中获取一些自定义属性
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GLSurfaceView, defStyleAttr, 0);
    try {
        // 获取渲染模式属性
        int renderMode = a.getInt(R.styleable.GLSurfaceView_renderMode, RENDERMODE_CONTINUOUSLY);
        setRenderMode(renderMode);
        // 获取是否保留 EGL 上下文属性
        boolean preserveEGLContextOnPause = a.getBoolean(R.styleable.GLSurfaceView_preserveEGLContextOnPause, false);
        setPreserveEGLContextOnPause(preserveEGLContextOnPause);
    } finally {
        // 回收属性集,避免内存泄漏
        a.recycle();
    }
}

在这个构造函数中,首先通过 super 关键字调用父类 SurfaceView 的构造函数,将上下文和属性集传递给父类进行初始化。然后调用 init 方法进行一些内部变量的初始化工作,包括创建 EGLConfigChooserEGLContextFactoryEGLDisplayFactory 实例,创建渲染器线程 mGLThread,设置默认的渲染模式为连续渲染。接着从属性集中获取一些自定义属性,如渲染模式和是否保留 EGL 上下文,并进行相应的设置。最后,使用 a.recycle() 方法回收属性集,避免内存泄漏。

五、GLSurfaceView 的属性设置

5.1 基本属性

5.1.1 android:layout_widthandroid:layout_height

用于设置 GLSurfaceView 在布局中的宽度和高度。可以设置为具体的像素值、match_parent(填充父容器)或 wrap_content(根据内容自适应大小)。

xml 复制代码
<android.opengl.GLSurfaceView
    android:id="@+id/glSurfaceView"
    android:layout_width="300dp"
    android:layout_height="200dp" />
5.1.2 android:layout_gravity

用于设置 GLSurfaceView 在父容器中的对齐方式。可以设置为 leftrightcenter 等。

xml 复制代码
<android.opengl.GLSurfaceView
    android:id="@+id/glSurfaceView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />
5.1.3 android:alpha

用于设置 GLSurfaceView 的透明度。取值范围为 0.0(完全透明)到 1.0(完全不透明)。

xml 复制代码
<android.opengl.GLSurfaceView
    android:id="@+id/glSurfaceView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:alpha="0.5" />

5.2 OpenGL 相关属性

5.2.1 android:renderMode

用于设置 GLSurfaceView 的渲染模式。有两种可选值:

  • continuous:连续渲染模式,GLSurfaceView 会不断地进行渲染,帧率较高,但会消耗较多的 CPU 和 GPU 资源。
  • when_dirty:按需渲染模式,只有当调用 requestRender() 方法时才会进行渲染,适合对帧率要求不高的场景,可以节省资源。
xml 复制代码
<android.opengl.GLSurfaceView
    android:id="@+id/glSurfaceView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:renderMode="when_dirty" />
5.2.2 android:preserveEGLContextOnPause

用于设置在 Activity 暂停时是否保留 EGL 上下文。如果设置为 true,则在 Activity 暂停时会保留 EGL 上下文,恢复时可以更快地继续渲染;如果设置为 false,则在 Activity 暂停时会释放 EGL 上下文,恢复时需要重新创建。

xml 复制代码
<android.opengl.GLSurfaceView
    android:id="@+id/glSurfaceView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:preserveEGLContextOnPause="true" />

5.3 属性解析源码分析

在 Android 系统中,当创建 GLSurfaceView 实例时,会从属性集中解析各种属性并应用到 GLSurfaceView 上。以 GLSurfaceView 中解析 android:renderMode 属性为例,其源码如下:

java 复制代码
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    // 初始化其他变量...
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GLSurfaceView, defStyleAttr, 0);
    try {
        // 解析 android:renderMode 属性
        int renderMode = a.getInt(R.styleable.GLSurfaceView_renderMode, RENDERMODE_CONTINUOUSLY);
        setRenderMode(renderMode);
        // 解析其他属性...
    } finally {
        // 回收属性集,避免内存泄漏
        a.recycle();
    }
}

public void setRenderMode(int renderMode) {
    if (renderMode < RENDERMODE_CONTINUOUSLY || renderMode > RENDERMODE_WHEN_DIRTY) {
        throw new IllegalArgumentException("renderMode");
    }
    mRenderMode = renderMode;
    // 如果渲染器线程已经启动,通知线程更新渲染模式
    if (mGLThread != null) {
        mGLThread.setRenderMode(renderMode);
    }
}

init 方法中,首先通过 context.obtainStyledAttributes 方法获取属性集,然后从属性集中解析出 android:renderMode 属性,并将其应用到 GLSurfaceView 上。在 setRenderMode 方法中,会对传入的渲染模式进行合法性检查,然后将其赋值给 mRenderMode 变量。如果渲染器线程已经启动,还会通知线程更新渲染模式。

六、GLSurfaceView 的生命周期管理

6.1 初始化阶段

GLSurfaceView 的初始化阶段,主要完成一些内部变量的初始化和渲染器线程的创建。在构造函数中,会调用 init 方法进行初始化操作:

java 复制代码
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    // 获取 OpenGL ES 的配置选择器,用于选择合适的 EGL 配置
    mEGLConfigChooser = new SimpleEGLConfigChooser(true);
    // 获取 EGL 上下文工厂,用于创建和管理 EGL 上下文
    mEGLContextFactory = new DefaultContextFactory();
    // 获取 EGL 显示工厂,用于创建和管理 EGL 显示
    mEGLDisplayFactory = new DefaultDisplayFactory();
    // 创建一个新的渲染器线程,用于进行 OpenGL ES 渲染
    mGLThread = new GLThread(mThisWeakRef);
    // 设置渲染模式为连续渲染
    mRenderMode = RENDERMODE_CONTINUOUSLY;
    // 从属性集中获取一些自定义属性
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GLSurfaceView, defStyleAttr, 0);
    try {
        // 获取渲染模式属性
        int renderMode = a.getInt(R.styleable.GLSurfaceView_renderMode, RENDERMODE_CONTINUOUSLY);
        setRenderMode(renderMode);
        // 获取是否保留 EGL 上下文属性
        boolean preserveEGLContextOnPause = a.getBoolean(R.styleable.GLSurfaceView_preserveEGLContextOnPause, false);
        setPreserveEGLContextOnPause(preserveEGLContextOnPause);
    } finally {
        // 回收属性集,避免内存泄漏
        a.recycle();
    }
}

在这个方法中,会创建 EGLConfigChooserEGLContextFactoryEGLDisplayFactory 实例,创建渲染器线程 mGLThread,并设置默认的渲染模式。同时,会从属性集中获取一些自定义属性,并进行相应的设置。

6.2 启动阶段

GLSurfaceView 被添加到视图层级中并可见时,会启动渲染器线程,开始进行 OpenGL ES 渲染。在 GLSurfaceViewonAttachedToWindow 方法中,会调用 mGLThread.start() 方法启动渲染器线程:

java 复制代码
@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (mGLThread == null) {
        mGLThread = new GLThread(mThisWeakRef);
        mGLThread.start();
    }
}

6.3 暂停和恢复阶段

Activity 暂停时,需要暂停 GLSurfaceView 的渲染,以节省资源。在 ActivityonPause 方法中,需要调用 GLSurfaceViewonPause 方法:

java 复制代码
@Override
protected void onPause() {
    super.onPause();
    mGLSurfaceView.onPause();
}

public void onPause() {
    // 通知渲染器线程暂停渲染
    mGLThread.onPause();
    if (!mPreserveEGLContextOnPause) {
        // 如果不保留 EGL 上下文,通知线程释放 EGL 上下文
        mGLThread.releaseEglContextLocked();
    }
}

Activity 恢复时,需要恢复 GLSurfaceView 的渲染。在 ActivityonResume 方法中,需要调用 GLSurfaceViewonResume 方法:

java 复制代码
@Override
protected void onResume() {
    super.onResume();
    mGLSurfaceView.onResume();
}

public void onResume() {
    // 通知渲染器线程恢复渲染
    mGLThread.onResume();
    if (!mPreserveEGLContextOnPause) {
        // 如果不保留 EGL 上下文,通知线程重新创建 EGL 上下文
        mGLThread.requestEglContextLocked();
    }
}

6.4 销毁阶段

GLSurfaceView 被从视图层级中移除时,需要销毁渲染器线程,释放相关资源。在 GLSurfaceViewonDetachedFromWindow 方法中,会调用 mGLThread.requestExitAndWait() 方法销毁渲染器线程:

java 复制代码
@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mGLThread != null) {
        mGLThread.requestExitAndWait();
        mGLThread = null;
    }
}

6.5 生命周期管理源码分析

GLSurfaceView 的源码中,与生命周期管理相关的主要是 onAttachedToWindowonPauseonResumeonDetachedFromWindow 方法。这些方法通过与渲染器线程 mGLThread 进行交互,实现了 GLSurfaceView 的生命周期管理。

java 复制代码
@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (mGLThread == null) {
        mGLThread = new GLThread(mThisWeakRef);
        mGLThread.start();
    }
}

public void onPause() {
    mGLThread.onPause();
    if (!mPreserveEGLContextOnPause) {
        mGLThread.releaseEglContextLocked();
    }
}

public void onResume() {
    mGLThread.onResume();
    if (!mPreserveEGLContextOnPause) {
        mGLThread.requestEglContextLocked();
    }
}

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mGLThread != null) {
        mGLThread.requestExitAndWait();
        mGLThread = null;
    }
}

onAttachedToWindow 方法中,如果渲染器线程还未启动,则创建并启动渲染器线程。在 onPause 方法中,通知渲染器线程暂停渲染,并根据 mPreserveEGLContextOnPause 的值决定是否释放 EGL 上下文。在 onResume 方法中,通知渲染器线程恢复渲染,并根据 mPreserveEGLContextOnPause 的值决定是否重新创建 EGL 上下文。在 onDetachedFromWindow 方法中,通知渲染器线程退出并等待其结束,然后将渲染器线程置为 null

七、GLSurfaceView 的渲染机制

7.1 EGL 初始化

GLSurfaceView 的渲染过程中,首先需要进行 EGL(Embedded-System Graphics Library)的初始化。EGL 是 OpenGL ES 与底层窗口系统之间的接口,负责管理 OpenGL ES 的上下文、显示设备和表面等。在 GLThreadrun 方法中,会调用 start 方法进行 EGL 的初始化:

java 复制代码
@Override
public void run() {
    try {
        start();
        while (true) {
            // 渲染循环
            if (threadShouldExit()) {
                break;
            }
            if (doRenderFrame()) {
                // 进行渲染帧操作
            }
        }
    } finally {
        // 清理资源
        finish();
    }
}

private void start() {
    // 获取 EGL 显示
    mEglDisplay = mEGLDisplayFactory.createDisplay();
    // 初始化 EGL 显示
    mEglDisplay.initialize();
    // 选择合适的 EGL 配置
    mEglConfig = mEGLConfigChooser.chooseConfig(mEglDisplay);
    // 创建 EGL 上下文
    mEglContext = mEGLContextFactory.createContext(mEglDisplay, mEglConfig);
    // 创建 EGL 表面
    mEglSurface = mEGLWindowSurfaceFactory.createWindowSurface(mEglDisplay, mEglConfig, getHolder());
    // 绑定 EGL 上下文和表面
    mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext);
    // 调用渲染器的 onSurfaceCreated 方法
    mRenderer.onSurfaceCreated(mGL, mEglConfig);
}

start 方法中,首先通过 mEGLDisplayFactory.createDisplay() 方法获取 EGL 显示,然后调用 mEglDisplay.initialize() 方法初始化 EGL 显示。接着,通过 mEGLConfigChooser.chooseConfig(mEglDisplay) 方法选择合适的 EGL 配置,通过 mEGLContextFactory.createContext(mEglDisplay, mEglConfig) 方法创建 EGL 上下文,通过 mEGLWindowSurfaceFactory.createWindowSurface(mEglDisplay, mEglConfig, getHolder()) 方法创建 EGL 表面。最后,调用 mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext) 方法绑定 EGL 上下文和表面,并调用渲染器的 onSurfaceCreated 方法进行初始化。

7.2 渲染循环

在 EGL 初始化完成后,GLThread 会进入一个渲染循环,不断地进行渲染操作。在 GLThreadrun 方法中,会不断地调用 doRenderFrame 方法进行渲染:

java 复制代码
@Override
public void run() {
    try {
        start();
        while (true) {
            if (threadShouldExit()) {
                break;
            }
            if (doRenderFrame()) {
                // 进行渲染帧操作
            }
        }
    } finally {
        finish();
    }
}

private boolean doRenderFrame() {
    boolean needRender = false;
    synchronized (this) {
        while (true) {
            if (threadShouldExit()) {
                return false;
            }
            if (mRenderMode == RENDERMODE_CONTINUOUSLY || mRenderRequested) {
                needRender = true;
                mRenderRequested = false;
                break;
            } else {
                try {
                    // 等待渲染请求
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    if (needRender) {
        // 进行渲染操作
        mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext);
        mRenderer.onDrawFrame(mGL);
        mEglDisplay.swapBuffers(mEglSurface);
    }
    return needRender;
}

doRenderFrame 方法中,首先根据渲染模式和渲染请求标志判断是否需要进行渲染。如果渲染模式为连续渲染或者有渲染请求,则设置 needRendertrue,并将渲染请求标志置为 false。然后,调用 mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext) 方法绑定 EGL 上下文和表面,调用渲染器的 onDrawFrame 方法进行实际的绘制操作,最后调用 mEglDisplay.swapBuffers(mEglSurface) 方法交换前后缓冲区,将绘制结果显示在屏幕上。

7.3 渲染器回调

GLSurfaceView 的渲染过程中,会调用渲染器的三个重要回调方法:onSurfaceCreatedonSurfaceChangedonDrawFrame

  • onSurfaceCreated:当 OpenGL ES 上下文被创建时调用,用于进行初始化操作,如设置清屏颜色、加载纹理等。
java 复制代码
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // 设置清屏颜色为黑色
    gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
  • onSurfaceChanged:当 OpenGL ES 视图的大小发生变化时调用,用于设置视口和投影矩阵。
java 复制代码
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    // 设置视口大小为整个视图的大小
    gl.glViewport(0, 0, width, height);
}
  • onDrawFrame:每帧渲染时调用,用于进行实际的绘制操作。
java 复制代码
@Override
public void onDrawFrame(GL10 gl) {
    // 清除颜色缓冲区
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
}

7.4 渲染机制源码分析

GLSurfaceView 的源码中,与渲染机制相关的主要是 GLThread 类和渲染器的回调方法。GLThread 类负责管理 EGL 的初始化、渲染循环和资源清理等操作,而渲染器的回调方法则负责具体的绘制逻辑。

java 复制代码
@Override
public void run() {
    try {
        start();
        while (true) {
            if (threadShouldExit()) {
                break;
            }
            if (doRenderFrame()) {
                // 进行渲染帧操作
            }
        }
    } finally {
        finish();
    }
}

private void start() {
    mEglDisplay = mEGLDisplayFactory.createDisplay();
    mEglDisplay.initialize();
    mEglConfig = mEGLConfigChooser.chooseConfig(mEglDisplay);
    mEglContext = mEGLContextFactory.createContext(mEglDisplay, mEglConfig);
    mEglSurface = mEGLWindowSurfaceFactory.createWindowSurface(mEglDisplay, mEglConfig, getHolder());
    mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext);
    mRenderer.onSurfaceCreated(mGL, mEglConfig);
}

private boolean doRenderFrame() {
    boolean needRender = false;
    synchronized (this) {
        while (true) {
            if (threadShouldExit()) {
                return false;
            }
            if (mRenderMode == RENDERMODE_CONTINUOUSLY || mRenderRequested) {
                needRender = true;
                mRenderRequested = false;
                break;
            } else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    if (needRender) {
        mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext);
        mRenderer.onDrawFrame(mGL);
        mEglDisplay.swapBuffers(mEglSurface);
    }
    return needRender;
}

run 方法中,首先调用 start 方法进行 EGL 的初始化,然后进入渲染循环,不断地调用 doRenderFrame 方法进行渲染。在 start 方法中,进行 EGL 的初始化操作,并调用渲染器的 onSurfaceCreated 方法。在 doRenderFrame 方法中,根据渲染模式和渲染请求标志判断是否需要进行渲染,如果需要则调用渲染器的 onDrawFrame 方法进行绘制,并交换前后缓冲区。

八、GLSurfaceView 的线程管理

8.1 渲染器线程

GLSurfaceView 使用一个独立的线程(GLThread)来进行 OpenGL ES 渲染,以避免阻塞主线程。在 GLSurfaceView 的初始化阶段,会创建一个 GLThread 实例,并在 onAttachedToWindow 方法中启动该线程:

java 复制代码
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    // 其他初始化操作...
    mGLThread = new GLThread(mThisWeakRef);
}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (mGLThread == null) {
        mGLThread = new GLThread(mThisWeakRef);
        mGLThread.start();
    }
}

8.2 线程同步

GLThread 中,使用了多个同步机制来保证线程安全。例如,在 doRenderFrame 方法中,使用 synchronized 关键字来保证对渲染请求标志的访问是线程安全的:

java 复制代码
private boolean doRenderFrame() {
    boolean needRender = false;
    synchronized (this) {
        while (true) {
            if (threadShouldExit()) {
                return false;
            }
            if (mRenderMode == RENDERMODE_CONTINUOUSLY || mRenderRequested) {
                needRender = true;
                mRenderRequested = false;
                break;
            } else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    if (needRender) {
        // 进行渲染操作
        mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext);
        mRenderer.onDrawFrame(mGL);
        mEglDisplay.swapBuffers(mEglSurface);
    }
    return needRender;
}

8.3 线程生命周期管理

GLThread 的生命周期与 GLSurfaceView 的生命周期密切相关。在 GLSurfaceViewonPause 方法中,会通知 GLThread 暂停渲染;在 onResume 方法中,会通知 GLThread 恢复渲染;在 onDetachedFromWindow 方法中,会通知 GLThread 退出并等待其结束:

java 复制代码
public void onPause() {
    mGLThread.onPause();
    if (!mPreserveEGLContextOnPause) {
        mGLThread.releaseEglContextLocked();
    }
}

public void onResume() {
    mGLThread.onResume();
    if (!mPreserveEGLContextOnPause) {
        mGLThread.requestEglContextLocked();
    }
}

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mGLThread != null) {
        mGLThread.requestExitAndWait();
        mGLThread = null;
    }
}

8.4 线程管理源码分析

GLSurfaceView 的源码中,与线程管理相关的主要是 GLThread 类和 GLSurfaceView 的生命周期方法。GLThread 类负责管理渲染器线程的启动、暂停、恢复和退出等操作,而 GLSurfaceView 的生命周期方法则通过调用 GLThread 的相应方法来实现线程的生命周期管理。

java 复制代码
private static class GLThread extends Thread {
    // 其他成员变量...

    @Override
    public void run() {
        try {
            start();
            while (true) {
                if (threadShouldExit()) {
                    break;
                }
                if (doRenderFrame()) {
                    // 进行渲染帧操作
                }
            }
        } finally {
            finish();
        }
    }

    public void onPause() {
        synchronized (this) {
            mPaused = true;
            notifyAll();
        }
    }

    public void onResume() {
        synchronized (this) {
            mPaused = false;
            notifyAll();
        }
    }

    public void requestExitAndWait() {
        synchronized (this) {
            if (mExited) {
                return;
            }
            mExited = true;
            notifyAll();
        }
        try {
            join();
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
    }

    // 其他方法...
}

GLThreadrun 方法中,会进入一个渲染循环,不断地进行渲染操作。在 onPause 方法中,会设置 mPaused 标志为 true,并通知所有等待的线程。在 onResume 方法中,会设置

java 复制代码
private static class GLThread extends Thread {
    // 其他成员变量...

    @Override
    public void run() {
        try {
            start();
            while (true) {
                if (threadShouldExit()) {
                    break;
                }
                if (doRenderFrame()) {
                    // 进行渲染帧操作
                }
            }
        } finally {
            finish();
        }
    }

    public void onPause() {
        synchronized (this) {
            mPaused = true;
            notifyAll();
        }
    }

    public void onResume() {
        synchronized (this) {
            mPaused = false;
            notifyAll();
        }
    }

    public void requestExitAndWait() {
        synchronized (this) {
            if (mExited) {
                return;
            }
            mExited = true;
            notifyAll();
        }
        try {
            join();
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
    }

    private boolean threadShouldExit() {
        synchronized (this) {
            return mExited;
        }
    }

    private void finish() {
        if (mEglDisplay != null) {
            if (mEglDisplay.isCurrent()) {
                mEglDisplay.makeCurrent(null, null, null);
            }
            mEglDisplay.release();
            mEglDisplay = null;
        }
        mEglContext = null;
        mEglConfig = null;
        mEglSurface = null;
        mGL = null;
    }
}

在上述代码中,threadShouldExit 方法通过检查 mExited 标志位来判断线程是否应该退出,该标志位会在 requestExitAndWait 方法中被设置。finish 方法用于在渲染线程结束时进行资源清理工作,首先检查 EGL 显示是否当前正在使用,如果是则通过 mEglDisplay.makeCurrent(null, null, null) 解除当前绑定,然后调用 mEglDisplay.release() 释放 EGL 显示资源 ,并将相关的 EGL 上下文、配置、表面以及 OpenGL ES 接口实例等都置为 null,确保资源得到妥善回收。

同时,GLSurfaceView 通过持有 GLThread 的弱引用,避免在不需要线程时导致内存泄漏。在 GLSurfaceViewonDetachedFromWindow 方法中,调用 mGLThread.requestExitAndWait() 后将 mGLThread 置为 null,保证了视图销毁时,与之关联的渲染线程也能正确结束生命周期。

九、GLSurfaceView 的事件处理

9.1 触摸事件处理

GLSurfaceView 继承自 SurfaceView,而 SurfaceView 又继承自 View,因此它具备处理触摸事件的能力。当触摸事件发生时,GLSurfaceView 会将事件传递给相应的处理逻辑。

java 复制代码
@Override
public boolean onTouchEvent(MotionEvent event) {
    // 这里可以添加自定义的触摸事件处理逻辑
    // 例如,将触摸坐标转换为 OpenGL 坐标系下的坐标
    float x = event.getX();
    float y = event.getY();
    // 可以根据触摸事件类型进行不同处理
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 触摸按下事件处理
            break;
        case MotionEvent.ACTION_MOVE:
            // 触摸移动事件处理
            break;
        case MotionEvent.ACTION_UP:
            // 触摸抬起事件处理
            break;
    }
    // 如果触摸事件被处理,返回 true,否则返回 false
    return true;
}

在上述代码中,通过重写 onTouchEvent 方法,获取触摸事件的坐标和类型,并根据不同的事件类型进行相应的处理。开发者可以在这个方法中实现自定义的触摸交互逻辑,例如在 3D 场景中通过触摸操作实现模型的旋转、平移和缩放等。

9.2 按键事件处理

GLSurfaceView 同样可以处理按键事件,通过重写 onKeyDownonKeyUp 方法来实现。

java 复制代码
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // 处理按键按下事件
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        // 例如,处理返回键按下事件
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    // 处理按键抬起事件
    return super.onKeyUp(keyCode, event);
}

在上述代码中,onKeyDown 方法在按键按下时被调用,通过判断 keyCode 来识别具体的按键。例如,当检测到返回键(KeyEvent.KEYCODE_BACK)按下时,可以在该方法中添加相应的逻辑处理,如退出当前界面或执行特定操作。onKeyUp 方法则在按键抬起时被调用,开发者可以根据需要在此添加按键抬起时的处理逻辑 。

9.3 事件传递机制源码分析

在 Android 系统中,事件传递遵循一定的机制。当触摸事件或按键事件发生时,首先会传递给当前 Activity 的 dispatchTouchEventdispatchKeyEvent 方法,然后由 Activity 决定将事件传递给哪个视图。对于 GLSurfaceView 来说,当事件传递到它时,会调用其自身的 onTouchEventonKeyDownonKeyUp 等方法。

java 复制代码
// Activity 中的事件分发方法(简化示例)
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // 触摸按下时,尝试将事件传递给当前焦点视图
        View currentFocus = getCurrentFocus();
        if (currentFocus != null) {
            return currentFocus.dispatchTouchEvent(ev);
        }
    }
    return super.dispatchTouchEvent(ev);
}

在上述 Activity 的 dispatchTouchEvent 简化代码中,当触摸按下事件发生时,会获取当前获得焦点的视图(如果存在),并调用该视图的 dispatchTouchEvent 方法进行事件传递。GLSurfaceView 作为视图的一种,当它获得焦点时,就会接收到传递过来的事件,并在其 onTouchEvent 方法中进行处理。对于按键事件,也遵循类似的传递机制,从 Activity 逐步传递到具体的视图进行处理。

十、GLSurfaceView 的性能优化

10.1 减少绘制操作

onDrawFrame 方法中,应尽量减少不必要的绘制操作。例如,避免在每一帧都重新创建和初始化图形对象,而是复用已有的对象。

java 复制代码
// 错误示例:每一帧都重新创建图形对象
@Override
public void onDrawFrame(GL10 gl) {
    // 每次都重新创建顶点数组对象
    FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
           .order(ByteOrder.nativeOrder())
           .asFloatBuffer()
           .put(vertices)
           .position(0);
    // 其他绘制操作
}

// 正确示例:在 onSurfaceCreated 中创建图形对象,在 onDrawFrame 中复用
private FloatBuffer vertexBuffer;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
           .order(ByteOrder.nativeOrder())
           .asFloatBuffer()
           .put(vertices)
           .position(0);
}

@Override
public void onDrawFrame(GL10 gl) {
    // 复用已创建的顶点数组对象
    // 其他绘制操作
}

在错误示例中,每一帧都重新创建 vertexBuffer,这会消耗大量的内存分配和释放资源,影响性能。而在正确示例中,将 vertexBuffer 的创建放在 onSurfaceCreated 方法中,在 onDrawFrame 方法中直接复用,避免了重复的资源分配,提高了绘制效率。

10.2 合理使用纹理

纹理的加载和使用对性能有较大影响。应尽量使用合适尺寸的纹理,避免使用过大的纹理导致内存占用过高和渲染效率降低。同时,可以对纹理进行压缩处理,减小纹理文件的大小。

java 复制代码
// 加载纹理(示例代码,包含纹理压缩处理)
private int loadTexture(GL10 gl, Context context, int resourceId) {
    int[] textures = new int[1];
    gl.glGenTextures(1, textures, 0);
    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId);
    // 对 Bitmap 进行压缩处理(示例:按比例缩小)
    Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / 2, bitmap.getHeight() / 2, false);
    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, scaledBitmap, 0);
    scaledBitmap.recycle();
    bitmap.recycle();

    return textures[0];
}

在上述代码中,通过 BitmapFactory.decodeResource 加载资源文件生成 Bitmap 后,对其进行了压缩处理(按比例缩小),然后再将压缩后的 Bitmap 加载为纹理。这样可以减小纹理占用的内存空间,同时在一定程度上提高纹理的渲染效率。

10.3 优化视图层级

尽量简化 GLSurfaceView 所在的视图层级,避免过多的嵌套和复杂的布局。复杂的视图层级会增加布局计算和绘制的时间,影响整体性能。

xml 复制代码
<!-- 复杂视图层级示例,应尽量避免 -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <android.opengl.GLSurfaceView
        android:id="@+id/glSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

<!-- 优化后的简单视图层级示例 -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.opengl.GLSurfaceView
        android:id="@+id/glSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

在优化前的示例中,GLSurfaceView 所在的视图层级包含了 RelativeLayoutLinearLayout 以及多个子视图,布局计算和绘制相对复杂。而优化后的示例使用了简单的 FrameLayout,减少了视图层级的复杂度,有助于提高整体性能。

10.4 性能优化相关源码分析

虽然 Android 系统在底层对 GLSurfaceView 的性能进行了很多优化,但开发者在使用过程中也可以从源码角度理解一些关键操作对性能的影响。例如,在 GLThreaddoRenderFrame 方法中,mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext) 方法用于绑定 EGL 上下文和表面,这个操作涉及到与底层图形系统的交互。如果频繁地进行上下文和表面的绑定与解除操作,会增加系统开销。因此,合理安排渲染逻辑,避免不必要的上下文切换,对于性能提升至关重要。

java 复制代码
private boolean doRenderFrame() {
    boolean needRender = false;
    synchronized (this) {
        // 判断是否需要渲染
    }
    if (needRender) {
        mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext);
        mRenderer.onDrawFrame(mGL);
        mEglDisplay.swapBuffers(mEglSurface);
    }
    return needRender;
}

上述代码中,mEglDisplay.makeCurrentmEglDisplay.swapBuffers 操作都属于与底层图形系统交互的关键操作。在实际开发中,如果在 onDrawFrame 方法中进行过多复杂的操作,导致这些底层操作频繁执行,就可能影响性能。因此,开发者需要对渲染逻辑进行优化,减少不必要的操作,从而降低底层操作的执行频率,提升 GLSurfaceView 的渲染性能。

十一、GLSurfaceView 与其他视图的比较

11.1 与 SurfaceView 的比较

11.1.1 渲染功能
  • SurfaceView:主要用于一般的图形绘制和视频播放等场景,虽然也能在独立线程中进行绘制,但对于 OpenGL ES 渲染的支持不够直接,需要开发者自行处理更多的底层细节。例如,在进行 OpenGL ES 渲染时,需要手动管理 EGL 上下文、表面等,操作较为复杂。
  • GLSurfaceView :专门为 OpenGL ES 渲染设计,封装了 EGL 的相关操作,提供了更便捷的接口。开发者只需实现 GLSurfaceView.Renderer 接口的回调方法,即可进行 OpenGL ES 渲染,大大简化了开发流程 。
11.1.2 适用场景
  • SurfaceView :适用于对实时性要求较高的场景,如视频播放、游戏中的简单图形绘制等。例如,在视频播放器中,SurfaceView 可以高效地显示视频画面,并且不会阻塞主线程。
  • GLSurfaceView :更适合用于需要复杂 3D 图形渲染和 OpenGL ES 特效的场景,如 3D 游戏开发、科学计算可视化等。例如,在大型 3D 游戏中,GLSurfaceView 能够充分发挥 OpenGL ES 的强大功能,实现逼真的 3D 场景和特效。
11.1.3 开发难度
  • SurfaceView:开发难度相对较高,因为需要开发者自行处理很多底层的图形绘制和线程管理逻辑。例如,在实现一个简单的图形绘制功能时,需要开发者手动创建绘制线程,并处理线程与主线程之间的通信。
  • GLSurfaceView:开发难度相对较低,由于其对 OpenGL ES 渲染进行了高度封装,开发者可以专注于图形渲染的逻辑实现,而无需过多关注底层细节。

11.2 与 TextureView 的比较

11.2.1 渲染机制
  • TextureView :将内容渲染到一个纹理上,然后在主线程中进行显示。它基于 OpenGL ES 进行渲染,但渲染操作是在主线程中完成的。这使得 TextureView 可以像普通 View 一样进行动画、变换等操作,并且能够实现更流畅的画面过渡效果,但在处理复杂图形渲染时,可能会因为主线程的限制而影响性能。
  • GLSurfaceView :使用独立的线程进行 OpenGL ES 渲染,通过 EGL 管理上下文、表面等资源。这种渲染机制使得 GLSurfaceView 能够高效地处理复杂的 3D 图形渲染和大量的绘制操作,不会阻塞主线程,保证了界面的流畅性 。
11.2.2 灵活性
  • TextureView :灵活性较高,可以方便地进行各种动画和变换操作,并且能够与其他 View 进行混合布局,实现复杂的界面效果。例如,在一个应用中,可以将 TextureViewTextViewButton 等视图进行组合,实现带有视频预览和交互按钮的界面。
  • GLSurfaceView :在图形渲染方面具有较高的灵活性,可以实现各种复杂的 3D 图形效果和 OpenGL ES 特效。但由于其独立的渲染线程和特殊的渲染机制,在与其他视图进行混合布局和交互时,相对不如 TextureView 方便。
11.2.3 适用场景
  • TextureView:适用于需要进行简单动画和变换操作,以及对界面布局灵活性要求较高的场景,如视频编辑应用中的视频预览窗口、图像预览等。
  • GLSurfaceView:适用于对图形渲染性能要求较高,需要实现复杂 3D 图形效果的场景,如 3D 游戏开发、专业图形设计应用等。
相关推荐
_一条咸鱼_3 小时前
揭秘 Android TextInputLayout:从源码深度剖析其使用原理
android·java·面试
_一条咸鱼_3 小时前
揭秘!Android VideoView 使用原理大起底
android·java·面试
_一条咸鱼_3 小时前
深度揭秘!Android TextView 使用原理全解析
android·java·面试
_一条咸鱼_3 小时前
深度剖析:Android Canvas 使用原理全揭秘
android·java·面试
_一条咸鱼_3 小时前
深度剖析!Android TextureView 使用原理全揭秘
android·java·面试
_一条咸鱼_3 小时前
揭秘!Android CheckBox 使用原理全解析
android·java·面试
_一条咸鱼_3 小时前
深度揭秘:Android Toolbar 使用原理的源码级剖析
android·java·面试
_一条咸鱼_3 小时前
揭秘 Java ArrayList:从源码深度剖析其使用原理
android·java·面试