惊爆!深入剖析 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 渲染的视图组件,它继承自 SurfaceView
。SurfaceView
作为一种特殊的视图,拥有独立的绘图表面,能够在独立的线程中进行绘制操作,从而避免了主线程的阻塞,保证了界面的流畅性。GLSurfaceView
在 SurfaceView
的基础上,进一步封装了 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
接口的三个重要方法:onSurfaceCreated
、onSurfaceChanged
和 onDrawFrame
。在 MainActivity
中,我们创建了 GLSurfaceView
实例,设置了渲染器和渲染模式,并将其设置为当前 Activity 的内容视图。同时,我们在 onResume
和 onPause
方法中分别调用了 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 继承带来的特性
- 独立绘图表面 :继承自
SurfaceView
,GLSurfaceView
拥有独立的绘图表面,能够在独立的线程中进行绘制操作。这使得GLSurfaceView
可以实现高效的渲染,避免了主线程的阻塞,保证了界面的流畅性。 - 视图特性 :作为
View
的子类,GLSurfaceView
拥有View
的所有视图特性,如布局参数、事件处理、动画效果等。开发者可以像使用普通View
一样对GLSurfaceView
进行布局和操作。 - 生命周期管理 :
GLSurfaceView
继承了View
的生命周期管理机制,开发者可以在Activity
的onResume
和onPause
方法中分别调用GLSurfaceView
的onResume
和onPause
方法,来管理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
方法进行一些内部变量的初始化工作,包括创建 EGLConfigChooser
、EGLContextFactory
和 EGLDisplayFactory
实例,创建渲染器线程 mGLThread
,设置默认的渲染模式为连续渲染。接着从属性集中获取一些自定义属性,如渲染模式和是否保留 EGL 上下文,并进行相应的设置。最后,使用 a.recycle()
方法回收属性集,避免内存泄漏。
五、GLSurfaceView 的属性设置
5.1 基本属性
5.1.1 android:layout_width
和 android: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
在父容器中的对齐方式。可以设置为 left
、right
、center
等。
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();
}
}
在这个方法中,会创建 EGLConfigChooser
、EGLContextFactory
和 EGLDisplayFactory
实例,创建渲染器线程 mGLThread
,并设置默认的渲染模式。同时,会从属性集中获取一些自定义属性,并进行相应的设置。
6.2 启动阶段
当 GLSurfaceView
被添加到视图层级中并可见时,会启动渲染器线程,开始进行 OpenGL ES 渲染。在 GLSurfaceView
的 onAttachedToWindow
方法中,会调用 mGLThread.start()
方法启动渲染器线程:
java
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mGLThread == null) {
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
}
6.3 暂停和恢复阶段
当 Activity
暂停时,需要暂停 GLSurfaceView
的渲染,以节省资源。在 Activity
的 onPause
方法中,需要调用 GLSurfaceView
的 onPause
方法:
java
@Override
protected void onPause() {
super.onPause();
mGLSurfaceView.onPause();
}
public void onPause() {
// 通知渲染器线程暂停渲染
mGLThread.onPause();
if (!mPreserveEGLContextOnPause) {
// 如果不保留 EGL 上下文,通知线程释放 EGL 上下文
mGLThread.releaseEglContextLocked();
}
}
当 Activity
恢复时,需要恢复 GLSurfaceView
的渲染。在 Activity
的 onResume
方法中,需要调用 GLSurfaceView
的 onResume
方法:
java
@Override
protected void onResume() {
super.onResume();
mGLSurfaceView.onResume();
}
public void onResume() {
// 通知渲染器线程恢复渲染
mGLThread.onResume();
if (!mPreserveEGLContextOnPause) {
// 如果不保留 EGL 上下文,通知线程重新创建 EGL 上下文
mGLThread.requestEglContextLocked();
}
}
6.4 销毁阶段
当 GLSurfaceView
被从视图层级中移除时,需要销毁渲染器线程,释放相关资源。在 GLSurfaceView
的 onDetachedFromWindow
方法中,会调用 mGLThread.requestExitAndWait()
方法销毁渲染器线程:
java
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mGLThread != null) {
mGLThread.requestExitAndWait();
mGLThread = null;
}
}
6.5 生命周期管理源码分析
在 GLSurfaceView
的源码中,与生命周期管理相关的主要是 onAttachedToWindow
、onPause
、onResume
和 onDetachedFromWindow
方法。这些方法通过与渲染器线程 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 的上下文、显示设备和表面等。在 GLThread
的 run
方法中,会调用 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
会进入一个渲染循环,不断地进行渲染操作。在 GLThread
的 run
方法中,会不断地调用 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
方法中,首先根据渲染模式和渲染请求标志判断是否需要进行渲染。如果渲染模式为连续渲染或者有渲染请求,则设置 needRender
为 true
,并将渲染请求标志置为 false
。然后,调用 mEglDisplay.makeCurrent(mEglSurface, mEglSurface, mEglContext)
方法绑定 EGL 上下文和表面,调用渲染器的 onDrawFrame
方法进行实际的绘制操作,最后调用 mEglDisplay.swapBuffers(mEglSurface)
方法交换前后缓冲区,将绘制结果显示在屏幕上。
7.3 渲染器回调
GLSurfaceView
的渲染过程中,会调用渲染器的三个重要回调方法:onSurfaceCreated
、onSurfaceChanged
和 onDrawFrame
。
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
的生命周期密切相关。在 GLSurfaceView
的 onPause
方法中,会通知 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();
}
}
// 其他方法...
}
在 GLThread
的 run
方法中,会进入一个渲染循环,不断地进行渲染操作。在 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
的弱引用,避免在不需要线程时导致内存泄漏。在 GLSurfaceView
的 onDetachedFromWindow
方法中,调用 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
同样可以处理按键事件,通过重写 onKeyDown
和 onKeyUp
方法来实现。
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 的 dispatchTouchEvent
或 dispatchKeyEvent
方法,然后由 Activity 决定将事件传递给哪个视图。对于 GLSurfaceView
来说,当事件传递到它时,会调用其自身的 onTouchEvent
或 onKeyDown
、onKeyUp
等方法。
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
所在的视图层级包含了 RelativeLayout
和 LinearLayout
以及多个子视图,布局计算和绘制相对复杂。而优化后的示例使用了简单的 FrameLayout
,减少了视图层级的复杂度,有助于提高整体性能。
10.4 性能优化相关源码分析
虽然 Android 系统在底层对 GLSurfaceView
的性能进行了很多优化,但开发者在使用过程中也可以从源码角度理解一些关键操作对性能的影响。例如,在 GLThread
的 doRenderFrame
方法中,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.makeCurrent
和 mEglDisplay.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
进行混合布局,实现复杂的界面效果。例如,在一个应用中,可以将TextureView
与TextView
、Button
等视图进行组合,实现带有视频预览和交互按钮的界面。 - GLSurfaceView :在图形渲染方面具有较高的灵活性,可以实现各种复杂的 3D 图形效果和 OpenGL ES 特效。但由于其独立的渲染线程和特殊的渲染机制,在与其他视图进行混合布局和交互时,相对不如
TextureView
方便。
11.2.3 适用场景
- TextureView:适用于需要进行简单动画和变换操作,以及对界面布局灵活性要求较高的场景,如视频编辑应用中的视频预览窗口、图像预览等。
- GLSurfaceView:适用于对图形渲染性能要求较高,需要实现复杂 3D 图形效果的场景,如 3D 游戏开发、专业图形设计应用等。