安卓surfaceview的使用方式

1. 什么是surfaceview

surfaceview内部机制和外部层次结构

在安卓开发中,我们经常会遇到一些需要高性能、高帧率、高画质的应用场景,例如视频播放、游戏开发、相机预览等。这些场景中,我们需要直接操作图像数据,并且实时地显示到屏幕上。如果我们使用普通的view组件来实现这些功能,可能会遇到以下问题:

  • view组件是在主线程中进行绘制的,如果绘制过程耗时过长或者频繁刷新,可能会导致主线程阻塞,影响用户交互和界面响应。
  • view组件在绘制时没有使用双缓冲机制,也就是说每次绘制都是直接在屏幕上进行的,这可能会导致绘制过程中出现闪烁或者撕裂的现象。
  • view组件是基于view层次结构的,也就是说每个view都是一个矩形区域,如果我们想要实现一些不规则形状或者透明度变化的效果,可能会比较困难。

为了解决这些问题,安卓提供了一种特殊的view组件:surfaceview 。surfaceview拥有自己独立的surface,也就是一个可以在其上直接绘制内容的图形缓冲区。surfaceview的内容是透明的,可以嵌入到view层次结构中,并且可以和其他view进行重叠或者裁剪。surfaceview适用于需要频繁刷新或处理逻辑复杂的绘图场景,如视频播放、游戏等。

下图展示了surfaceview和普通view在屏幕上的显示效果:

surfaceview和普通view

从图中可以看出,普通view是按照顺序依次绘制到屏幕上的,而surfaceview则是直接绘制到屏幕上的一个透明区域,并且可以和其他view进行重叠或者裁剪。

2. surfaceview和view的区别

从上面的介绍中,我们已经了解了surfaceview和普通view在显示效果上的区别。那么,在实现原理和使用方式上,它们又有什么不同呢?下面我们来对比一下它们的主要区别:

特点普通viewsurfaceview
更新方式主动更新,可以在任何时候调用invalidate方法来触发重绘,在onDraw方法中使用canvas进行绘制被动更新,不能直接控制重绘,需要通过一个子线程来进行页面的刷新,在子线程中直接操作surface进行绘制
刷新线程主线程刷新,可以保证界面的一致性和同步性,但是可能导致主线程阻塞或者掉帧子线程刷新,可以避免主线程阻塞,并且可以提高刷新频率和效率,但是需要注意线程间的通信和同步问题
缓冲机制无双缓冲机制,每次绘制都是直接在屏幕上进行,可以节省内存空间,但是可能导致闪烁或者撕裂的现象有双缓冲机制,每次绘制都是先在一个缓冲区中进行,然后再将缓冲区中的内容复制到屏幕上,可以避免闪烁或者撕裂的现象,并且可以提高绘制质量,但是需要消耗更多的内存空间
  • 更新方式:普通view适用于主动更新的情况,也就是说我们可以在任何时候调用view的invalidate方法来触发view的重绘,然后在onDraw方法中使用canvas进行绘制。而surfaceview主要用于被动更新的情况,也就是说我们不能直接控制surfaceview的重绘,而是需要通过一个子线程来进行页面的刷新,然后在子线程中直接操作surface进行绘制。
  • 刷新线程:普通view是在主线程里面进行刷新的,也就是说所有的绘制操作都是在主线程中完成的。这样的好处是可以保证界面的一致性和同步性,但是也有可能导致主线程阻塞或者掉帧。而surfaceview是通过一个子线程来进行页面的刷新的,也就是说所有的绘制操作都是在子线程中完成的。这样的好处是可以避免主线程阻塞,并且可以提高刷新频率和效率,但是也需要注意线程间的通信和同步问题。
  • 缓冲机制:普通view在绘图时没有使用双缓冲机制,也就是说每次绘制都是直接在屏幕上进行的。这样的好处是可以节省内存空间,但是也可能导致绘制过程中出现闪烁或者撕裂的现象。而surfaceview在底层实现机制中已经实现了双缓冲机制,也就是说每次绘制都是先在一个缓冲区中进行,然后再将缓冲区中的内容复制到屏幕上。这样的好处是可以避免闪烁或者撕裂的现象,并且可以提高绘制质量,但是也需要消耗更多的内存空间。

3. surfaceview的创建和使用

了解了surfaceview和普通view的区别之后,我们就可以开始创建和使用surfaceview了。创建自定义的surfaceview需要以下几个步骤:

  • 继承surfaceview:首先,我们需要创建一个自定义的类,继承自surfaceview,并实现两个接口:surfaceholder.callback和runnable。前者用于监听surface的状态变化,后者用于实现子线程的逻辑。
  • 初始化surfaceholder:其次,我们需要在构造方法中初始化surfaceholder对象,并注册surfaceholder的回调方法。surfaceholder是一个用于管理surface的类,它提供了一些方法来获取和操作surface。
  • 处理回调方法:然后,我们需要在回调方法中处理surface的创建、改变和销毁事件。当surface被创建时,我们需要启动子线程,并根据需要调整view的大小或位置;当surface被改变时,我们需要重新获取surface的宽高,并根据需要调整view的大小或位置;当surface被销毁时,我们需要停止子线程,并释放相关资源。
  • 实现run方法:接着,我们需要在run方法中实现子线程的绘图逻辑。我们可以使用一个循环来不断地刷新页面,并且根据不同的条件来控制循环的退出。
  • 获取canvas对象:最后,我们需要在draw方法中获取canvas对象,并通过lockcanvas和unlockcanvasandpost方法进行绘图操作。lockcanvas方法会返回一个canvas对象,我们可以使用它来对surface进行绘制;unlockcanvasandpost方法会将绘制好的内容显示到屏幕上,并且释放canvas对象。

下面给出一个简单的示例代码,实现了一个简单的画板功能:

//自定义类继承自SurfaceView,并实现SurfaceHolder.Callback和Runnable接口
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {//声明SurfaceHolder对象private SurfaceHolder mHolder;//声明子线程对象private Thread mThread;//声明画笔对象private Paint mPaint;//声明画布对象private Canvas mCanvas;//声明一个标志位,用于控制子线程的退出private boolean mIsDrawing;//构造方法,初始化相关对象public MySurfaceView(Context context) {super(context);//获取SurfaceHolder对象mHolder = getHolder();//注册SurfaceHolder的回调方法mHolder.addCallback(this);//初始化画笔对象,设置颜色和宽度mPaint = new Paint();mPaint.setColor(Color.RED);mPaint.setStrokeWidth(10);}//当Surface被创建时,启动子线程,并根据需要调整View的大小或位置@Overridepublic void surfaceCreated(SurfaceHolder holder) {//设置标志位为true,表示子线程可以开始运行mIsDrawing = true;//创建并启动子线程mThread = new Thread(this);mThread.start();}//当Surface被改变时,重新获取Surface的宽高,并根据需要调整View的大小或位置@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//TODO: 根据需要调整View的大小或位置}//当Surface被销毁时,停止子线程,并释放相关资源@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {//设置标志位为false,表示子线程可以停止运行mIsDrawing = false;try {//等待子线程结束,并释放子线程对象mThread.join();mThread = null;} catch (InterruptedException e) {e.printStackTrace();}}//实现run方法,实现子线程的绘图逻辑@Overridepublic void run() {//使用一个循环来不断地刷新页面while (mIsDrawing) {//获取当前时间,用于计算绘制时间long start = System.currentTimeMillis();//调用draw方法进行绘制操作draw();//获取结束时间,用于计算绘制时间long end = System.currentTimeMillis();//如果绘制时间小于16ms,则延时一段时间,保证每秒60帧的刷新率if (end - start < 16) {try {Thread.sleep(16 - (end - start));} catch (InterruptedException e) {e.printStackTrace();}}}}//获取canvas对象,并通过lockCanvas和unlockCanvasAndPost方法进行绘制操作private void draw() {try {//通过lockCanvas方法获取canvas对象,如果surface不可用,则返回nullmCanvas = mHolder.lockCanvas();if (mCanvas != null) {//TODO: 在canvas上进行绘制操作,例如画线、画圆、画文字等//在本例中,我们简单地使用随机数生成一些坐标点,并用画笔连接它们,形成一条折线图//生成一个随机数对象Random random = new Random();//生成一个点的集合,用于存储坐标点List<Point> points = new ArrayList<>();//循环生成10个随机坐标点,并添加到集合中for (int i = 0; i < 10; i++) {int x = random.nextInt(mCanvas.getWidth());int y = random.nextInt(mCanvas.getHeight());points.add(new Point(x, y));}//遍历点的集合,用画笔连接相邻的两个点,形成一条折线图for (int i = 0; i < points.size() - 1; i++) {Point p1 = points.get(i);Point p2 = points.get(i + 1);mCanvas.drawLine(p1.x, p1.y, p2.x, p2.y, mPaint);}//通过unlockCanvasAndPost方法将绘制好的内容显示到屏幕上,并释放canvas对象mHolder.unlockCanvasAndPost(mCanvas);}} catch (Exception e) {e.printStackTrace();}}
}

下图展示了上述代码运行的效果:

简单的画板

从图中可以看出,我们在surface上绘制了一条随机的折线图,并且显示到了屏幕上。这只是一个简单的示例,我们可以根据自己的需求,实现更复杂的绘图逻辑和效果。

4. surfaceview和activity的生命周期

在使用surfaceview时,我们需要注意它和activity的生命周期之间的关系。因为surfaceview是嵌入到view层次结构中的,所以它会受到activity的生命周期的影响。但是,surfaceview也有自己的生命周期,它是由surfaceholder来管理的。因此,对于具有surfaceview的activity,存在两个单独但相互依赖的状态机:应用oncreate/onresume/onpause和已创建/更改/销毁的surface。

下图展示了这两个状态机之间的关系:

surfaceview和activity的状态机之间的关系

surfaceview和activity的生命周期

从图中可以看出,当activity被创建时,会触发surfaceview的创建;当activity被恢复时,会触发surfaceview的改变;当activity被暂停时,会触发surfaceview的销毁。因此,在这些事件中,我们需要做一些相应的处理,例如:

  • 启动/停止子线程:当surface被创建或者销毁时,我们需要启动或者停止子线程,并根据需要调整view的大小或位置。如果我们不及时地启动或者停止子线程,可能会导致内存泄漏或者空指针异常。
  • 保存/恢复状态:当activity被暂停时,我们需要从子线程中提取状态,并保存到bundle中;当activity被恢复时,我们需要从bundle中恢复状态,并传递给子线程。如果我们不及时地保存或者恢复状态,可能会导致数据丢失或者不一致。

5. surfaceview和glsurfaceview

在上面的内容中,我们介绍了如何使用surfaceview来实现一些高性能、高帧率、高画质的应用。但是,如果我们想要实现一些更加复杂和精美的3D图形效果,例如光照、阴影、纹理、动画等,那么我们就需要使用opengl es来进行渲染。opengl es是一种跨平台的图形库,它可以利用gpu加速来提高渲染效率。

为了方便我们使用opengl es进行渲染,安卓提供了一种专门用于渲染opengl es内容的surfaceview:glsurfaceview 。glsurfaceview是一种继承自surfaceview的组件,它在底层封装了egl上下文、线程间通信以及与activity生命周期交互等功能。使用glsurfaceview时,我们无需自己创建和管理子线程,只需实现glsurfaceview.renderer接口,并设置给glsurfaceview对象即可。

下图展示了glsurfaceview和普通surfaceview在屏幕上的显示效果:

glsurfaceview和普通surfaceview

从图中可以看出,glsurfaceview和普通surfaceview都是直接绘制到屏幕上的一个透明区域,但是glsurfaceview可以使用opengl es来绘制一些更加复杂和精美的3D图形效果。

6. glsurfaceview的创建和使用

了解了glsurfaceview和普通surfaceview的区别之后,我们就可以开始创建和使用glsurfaceview了。创建自定义的glsurfaceview需要以下几个步骤:

  • 继承glsurfaceview:首先,我们需要创建一个自定义的类,继承自glsurfaceview,并在构造方法中初始化相关对象。
  • 设置渲染器:其次,我们需要实现glsurfaceview.renderer接口,并设置给glsurfaceview对象。渲染器是一个用于绘制opengl es内容的类,它提供了三个方法:onSurfaceCreated、onSurfaceChanged和onDrawFrame。
  • 设置渲染模式:然后,我们需要设置glsurfaceview的渲染模式,有两种可选:RENDERMODE_CONTINUOUSLY和RENDERMODE_WHEN_DIRTY。前者表示持续地刷新页面,后者表示只有在调用requestRender方法时才刷新页面。
  • 获取opengl es对象:最后,我们需要在渲染器的方法中获取opengl es对象,并使用它来进行绘制操作。opengl es对象是一个用于操作图形数据的类,它提供了一系列的方法来创建、加载、绘制、变换、释放等图形资源。

下面给出一个简单的示例代码,实现了一个简单的3D立方体效果:

//自定义类继承自GLSurfaceView,并在构造方法中初始化相关对象
public class MyGLSurfaceView extends GLSurfaceView {//声明渲染器对象private MyRenderer mRenderer;//构造方法,初始化相关对象public MyGLSurfaceView(Context context) {super(context);//设置opengl es版本为2.0setEGLContextClientVersion(2);//创建并设置渲染器对象mRenderer = new MyRenderer();setRenderer(mRenderer);//设置渲染模式为持续刷新setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);}//自定义类实现GLSurfaceView.Renderer接口,并实现三个方法private class MyRenderer implements GLSurfaceView.Renderer {//声明opengl es对象private GLES20 gl;//声明顶点着色器代码private final String vertexShaderCode ="attribute vec4 vPosition;" +"uniform mat4 uMVPMatrix;" +"void main() {" +"  gl_Position = uMVPMatrix * vPosition;" +"}";//声明片元着色器代码private final String fragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +"  gl_FragColor = vColor;" +"}";//声明顶点坐标数组private final float[] vertexCoords = {-0.5f, -0.5f, -0.5f,   // front bottom left0.5f, -0.5f, -0.5f,   // front bottom right0.5f,  0.5f, -0.5f,   // front top right-0.5f,  0.5f, -0.5f,  // front top left-0.5f, -0.5f,  0.5f,   // back bottom left0.5f, -0.5f,  0.5f,   // back bottom right0.5f,  0.5f,  0.5f,   // back top right-0.5f,  0.5f,  0.5f   // back top left};//声明顶点索引数组private final short[] drawOrder = {0, 1, 2,   // front face0, 2, 3,4, 5, 6,   // back face4, 6, 7,0, 4, 7,   // left face0, 7, 3,1, 5, 6,   // right face1, 6, 2,3, 2, 6,   // top face3, 6, 7,0, 1, 5,   // bottom face0, 5, 4};//声明颜色数组private final float[] colors = {1.0f, 0.0f, 0.0f, 1.0f, // red0.0f, 1.0f, 0.0f, 1.0f, // green0.0f, 0.0f, 1.0f, 1.0f, // blue1.0f, 1.0f, 0.0f, 1.0f, // yellow1.0f, 0.0f, 1.0f, 1.0f, // magenta0.0f, 1.0f, 1.0f, 1.0f // cyan};//声明顶点缓冲对象private FloatBuffer vertexBuffer;//声明索引缓冲对象private ShortBuffer drawListBuffer;//声明颜色缓冲对象private FloatBuffer colorBuffer;//声明顶点着色器对象private int vertexShader;//声明片元着色器对象private int fragmentShader;//声明程序对象private int program;//声明顶点位置属性的句柄private int positionHandle;//声明颜色属性的句柄private int colorHandle;//声明投影矩阵属性的句柄private int mvpMatrixHandle;//声明模型矩阵对象private float[] modelMatrix = new float[16];//声明视图矩阵对象private float[] viewMatrix = new float[16];//声明投影矩阵对象private float[] projectionMatrix = new float[16];//声明模型视图投影矩阵对象private float[] mvpMatrix = new float[16];//当Surface被创建时,初始化opengl es对象,并加载和编译着色器,创建和绑定图形数据,设置相机位置和投影方式等操作@Overridepublic void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {//获取opengl es对象,用于后续的绘制操作gl = (GLES20) gl10;//设置背景颜色为黑色,用于清除屏幕时使用gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);//加载和编译顶点着色器,返回一个句柄,用于后续的链接操作vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);//加载和编译片元着色器,返回一个句柄,用于后续的链接操作fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);//创建一个空的程序对象,返回一个句柄,用于后续的链接操作program = GLES20.glCreateProgram();//将顶点着色器和片元着色器附加到程序对象上GLES20.glAttachShader(program, vertexShader);GLES20.glAttachShader(program, fragmentShader);//链接程序对象,生成最终的可执行程序GLES20.glLinkProgram(program);//使用程序对象,激活相关的属性和统一变量GLES20.glUseProgram(program);//获取顶点位置属性的句柄,用于后续的绑定操作positionHandle = GLES20.glGetAttribLocation(program, "vPosition");//获取颜色属性的句柄,用于后续的绑定操作colorHandle = GLES20.glGetUniformLocation(program, "vColor");//获取投影矩阵属性的句柄,用于后续的绑定操作mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");//将顶点坐标数组转换为字节缓冲对象,用于后续的传输操作ByteBuffer bb = ByteBuffer.allocateDirect(vertexCoords.length * 4);bb.order(ByteOrder.nativeOrder());vertexBuffer = bb.asFloatBuffer();vertexBuffer.put(vertexCoords);vertexBuffer.position(0);//将顶点索引数组转换为字节缓冲对象,用于后续的传输操作ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);dlb.order(ByteOrder.nativeOrder());drawListBuffer = dlb.asShortBuffer();drawListBuffer.put(drawOrder);drawListBuffer.position(0);//将颜色数组转换为字节缓冲对象,用于后续的传输操作ByteBuffer cb = ByteBuffer.allocateDirect(colors.length * 4);cb.order(ByteOrder.nativeOrder());colorBuffer = cb.asFloatBuffer();colorBuffer.put(colors);colorBuffer.position(0);//设置相机位置和朝向,生成视图矩阵Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);}//当Surface被改变时,调整视口大小,并设置投影方式,生成投影矩阵@Overridepublic void onSurfaceChanged(GL10 gl10, int width, int height) {//设置视口大小为Surface的大小GLES20.glViewport(0, 0, width, height);//设置投影方式为透视投影,并根据视口宽高比计算投影矩阵float ratio = (float) width / height;Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);}//当Surface被绘制时,清除屏幕,并旋转模型矩阵,生成模型视图投影矩阵,并传输和绘制图形数据@Overridepublic void onDrawFrame(GL10 gl10) {//清除屏幕颜色缓冲区GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);//设置模型矩阵为单位矩阵,并根据系统时间旋转模型矩阵Matrix.setIdentityM(modelMatrix, 0);Matrix.rotateM(modelMatrix, 0, (float) SystemClock.uptimeMillis() / 1000 * 30f, 1.0f, 1.0f, 1.0f);//将模型矩阵、视图矩阵和投影矩阵相乘,生成模型视图投影矩阵Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0);Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0);//将模型视图投影矩阵传输到顶点着色器中,并激活该属性GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);//将顶点坐标数据传输到顶点着色器中,并激活该属性GLES20.glVertexAttribPointer(positionHandle, 3,GLES20.GL_FLOAT, false,0, vertexBuffer);GLES20.glEnableVertexAttribArray(positionHandle);//使用循环为每个面设置不同的颜色,并绘制三角形for (int i = 0; i < 6; i++) {//将颜色数据传输到片元着色器中,并激活该属性colorBuffer.position(i * 4);GLES20.glUniform4fv(colorHandle, 1, colorBuffer);//绘制三角形,使用顶点索引数组来确定顶点的顺序drawListBuffer.position(i * 6);GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6,GLES20.GL_UNSIGNED_SHORT, drawListBuffer);}}//定义一个加载和编译着色器的方法,接收一个着色器类型和一个着色器代码,返回一个着色器句柄public int loadShader(int type, String shaderCode) {//创建一个空的着色器对象,返回一个句柄,用于后续的编译操作int shader = GLES20.glCreateShader(type);//将着色器代码传输到着色器对象中GLES20.glShaderSource(shader, shaderCode);//编译着色器对象GLES20.glCompileShader(shader);//返回着色器句柄return shader;}}
}

glsurfaceview的使用方式

什么是glsurfaceview

glsurfaceview是一种专门用于渲染opengl es内容的surfaceview。opengl es是一种用于嵌入式设备上的3D图形渲染API。glsurfaceview类提供了用于管理egl上下文、在线程间通信以及与activity生命周期交互的辅助程序类。使用glsurfaceview时,无需自己创建和管理子线程,只需实现glsurfaceview.renderer接口,并设置给glsurfaceview对象即可。

glsurfaceview和surfaceview的区别

glsurfaceview和surfaceview都是继承自surfaceview的类,都可以在子线程中直接操作surface进行绘制。但是glsurfaceview相比surfaceview有以下的优势:

  • glsurfaceview可以自动创建和管理egl上下文,无需自己处理egl的初始化、销毁、切换等操作。
  • glsurfaceview可以自动创建和管理子线程,无需自己处理线程的启动、停止、同步等操作。
  • glsurfaceview可以自动处理与activity生命周期的交互,无需自己处理activity的暂停、恢复、保存状态等操作。
  • glsurfaceview可以提供多种渲染模式,可以根据需要调整渲染频率,避免过度绘制或掉帧。

glsurfaceview的创建和使用

创建自定义的glsurfaceview继承glsurfaceview,并在构造方法中设置opengl es版本、渲染器对象和渲染模式。创建自定义的渲染器实现glsurfaceview.renderer接口,并在回调方法中进行初始化、视口设置和绘图操作。

以下是一个简单的示例代码:

// 自定义的glsurfaceview类
public class MyGLSurfaceView extends GLSurfaceView {// 构造方法public MyGLSurfaceView(Context context) {super(context);// 设置opengl es版本为2.0setEGLContextClientVersion(2);// 设置渲染器对象setRenderer(new MyRenderer());// 设置渲染模式为连续模式setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);}// 自定义的渲染器类private class MyRenderer implements GLSurfaceView.Renderer {// 渲染器创建时的回调方法@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {// 在这里进行一些初始化操作,比如设置清屏颜色为黑色GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);}// 渲染器改变时的回调方法@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {// 在这里进行一些视口设置操作,比如设置视口大小为surface的大小GLES20.glViewport(0, 0, width, height);}// 渲染器绘制时的回调方法@Overridepublic void onDrawFrame(GL10 gl) {// 在这里进行一些绘图操作,比如清屏GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);}}
}

glsurfaceview和activity的生命周期

当使用glsurfaceview时,无需自己处理与activity生命周期的交互,glsurfaceview会自动根据activity的状态来暂停或恢复渲染器。但是如果需要保存或恢复一些重要的数据或状态,可以在activity的onSaveInstanceState和onRestoreInstanceState方法中进行操作。

以下是一个示意图,展示了glsurfaceview和activity的生命周期之间的关系:

glsurfaceview和activity的生命周期

从图中可以看出,当activity创建时,会触发glsurfaceview的onSurfaceCreated回调方法,这时会创建渲染器对象,并调用渲染器的onSurfaceCreated回调方法。当activity恢复时,会触发glsurfaceview的onDrawFrame回调方法,这时会恢复渲染器的绘制操作,并调用渲染器的onDrawFrame回调方法。当activity暂停时,会触发glsurfaceview的onDrawFrame回调方法,这时会暂停渲染器的绘制操作,并调用渲染器的onDrawFrame回调方法。当activity销毁时,会触发glsurfaceview的onSurfaceCreated回调方法,这时会销毁渲染器对象,并调用渲染器的onSurfaceCreated回调方法。

在这个过程中,需要注意以下几点:

  • 在activity的onSaveInstanceState和onRestoreInstanceState方法中,可以保存或恢复一些重要的数据或状态,比如使用一个bundle对象来存储或获取一些opengl es相关的对象或参数。
  • 在glsurfaceview的onSurfaceChanged回调方法中,可以根据surface的宽高调整视口大小或投影方式,比如使用glviewport或glfrustum等方法来设置视口或投影矩阵。
  • 在glsurfaceview的setRenderMode方法中,可以设置不同的渲染模式,比如使用RENDERMODE_CONTINUOUSLY或RENDERMODE_WHEN_DIRTY来设置连续模式或按需模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/836377.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

传感网应用开发教程--AT指令访问新大陆云平台(ESP8266模块+物联网云+TCP)

实现目标 1、熟悉AT指令 2、熟悉新大陆云平台新建项目 3、具体目标&#xff1a;&#xff08;1&#xff09;注册新大陆云平台&#xff1b;&#xff08;2&#xff09;新建一个联网方案为WIFI的项目&#xff1b;&#xff08;3&#xff09;ESP8266模块&#xff0c;通过AT指令访问…

电商购物系统首页的商品分类

如上图对商品的一个分类实际上和省市区的分类十分类似 , 都是通过自关联的方法来实现 , 但是这里不同的是 , 涉及到外键来获取数据 首先让我们来看一下最后通过后端返回数据的形式是什么样子的 """{1:{channels:[{id:1 , name:手机 , url:},{}{}],sub_cats:[{…

Vue报错:TypeError: Cannot read property ‘upgrade‘ of undefined

Vue报错&#xff1a;TypeError: Cannot read property ‘upgrade’ of undefined 前言 最近打开一个很就之前的开发项目&#xff0c;因为扫描包&#xff0c;所以删除了部分代码&#xff0c;后来就一直报错&#xff0c;现在总结一下。 报错原因&#xff1a;vue.config.js中 d…

力扣HOT100 - 74. 搜索二维矩阵

解题思路&#xff1a; 两次二分&#xff0c;第一次定位行&#xff0c;第二次定位列。 class Solution {public boolean searchMatrix(int[][] matrix, int target) {int m matrix.length, n matrix[0].length;int l 0, r m - 1;//定位行int row -1;while (l < r) {in…

【机器学习300问】86、简述超参数优化的步骤?如何寻找最优的超参数组合?

本文想讲述清楚怎么样才能选出最优的超参数组合。关于什么是超参数&#xff1f;什么是超参数组合&#xff1f;本文不赘述&#xff0c;在之前我写的文章中有详细介绍哦&#xff01; 【机器学习300问】22、什么是超参数优化&#xff1f;常见超参数优化方法有哪些&#xff1f;htt…

Web3探索加密世界:如何避免限制并增加空投成功的几率

今天分享空投如何避免限制以提高效率&#xff0c;增加成功几率&#xff0c;首先我们来了解什么是空投加密&#xff0c;有哪些空投类型。 一、什么是空投加密&#xff1f; 加密货币空投是一种营销策略&#xff0c;包括向用户的钱包地址发送免费的硬币或代币。 加密货币项目使用…

BM7 链表中环的入口结点(快慢指针模板题)

描述 给一个长度为n链表&#xff0c;若其中包含环&#xff0c;请找出该链表的环的入口结点&#xff0c;否则&#xff0c;返回null。 数据范围&#xff1a; &#x1d45b;≤10000n≤10000&#xff0c;1<结点值<100001<结点值<10000 要求&#xff1a;空间复杂度 &…

第02章 计算机网络概述

2.1 本章目标 了解计算机网络的定义了解计算机网络的功能了解计算机网络的分类了解计算机网络的组成 2.2 计算机网络的定义 2.3 计算机网络的功能 2.4 计算机网络的分类 物理拓扑结构分类&#xff1a;总线型、环型、星型 2.5 计算机网络的组成 网络适配器(NIC)接口规格分类&a…

阮怀俊谈如何盘活和挖掘乡村文旅资源

近年来&#xff0c;浙江凭借高水平建设新时代美丽乡村&#xff0c;各项工作持续走在全国前列&#xff0c;最近&#xff0c;在国家发展改革委关于恢复和扩大消费措施的通知中也提到&#xff1a; “推广浙江‘千万工程’经验&#xff0c;建设宜居宜业和美乡村。实施文化产业赋能乡…

报告!Golang冲上来啦!

今天又来讲Go语言&#xff0c;根据全球知名的编程语言排行榜TIOBE在4月份公布的最新的编程语言排名&#xff0c;令人瞩目的是&#xff0c;Go语言已经跃升至历史最高位&#xff0c;位列排行榜第七名&#xff0c;并且Go语言是前十榜单中最年轻的编程语言。这一成绩不仅彰显了Go语…

哈希表Hash table

哈希表是根据关键码的值而直接进行访问的数据结构。 数组就是⼀张哈希表。 哈希表中关键码就是数组的索引下标&#xff0c;然后通过下标直接访问数组中的元素&#xff0c;如下图所示&#xff1a; 那么哈希表能解决什么问题呢&#xff0c;一般哈希表都是用来快速判断⼀个元素是…

【JavaScript】DOM 事件的传播机制

事件与事件流 事件&#xff0c;这里指和网页进行互动。比如点击链接&#xff0c;移动鼠标等网页被触发&#xff0c;做出响应&#xff0c;形成交互。 js 采用事件监听器来监听事件是否发生。 事件流 事件流描述了从页面中接收事件的顺序。当一个事件发生在某个元素上时&…

【二叉树】Leetcode N 叉树的层序遍历

题目讲解 429. N 叉树的层序遍历 算法讲解 在做层序遍历的时候由于它的每一个结点是有val vector child组成&#xff0c;所以在做层序遍历的时候需要考虑它每一层结点的个数&#xff0c;那我们就可以使用一个queue保存每一层的结点&#xff1b;那么我们在做第一层的时候&am…

B端弹窗设计指南,3000字讲清楚,内附大量案例。

B端系统弹窗是指在企业级&#xff08;Business to Business&#xff09;系统中&#xff0c;弹出的窗口或对话框&#xff0c;用于向用户展示信息、提供操作选项或者收集用户输入。 一、B端系统弹窗的作用 作用如下&#xff1a; 提示和通知&#xff1a;弹窗可以用于向用户展示重…

一个全栈SpringBoot项目-Book Social Network

一个全栈SpringBoot项目-Book Social Network BSN是一个会员之间交换图书的社交网络平台。图书社交网络是一个全栈应用程序&#xff0c;使用户能够管理他们的图书收藏并与图书爱好者社区互动。它提供的功能包括用户注册、安全电子邮件验证、图书管理&#xff08;包括创建、更新…

(java)websocket服务的两种实现方式

1.基于java注解实现websocket服务器端 1.1需要的类 1.1.1服务终端类 用java注解来监听连接ServerEndpoint、连接成功OnOpen、连接失败OnClose、收到消息等状态OnMessage 1.1.2配置类 把spring中的ServerEndpointExporter对象注入进来 2.1代码示例 2.1.1 maven配置 <…

【前端】桌面版docker并部署前端项目

环境 win10专业版 2004 , 需科学 官网下载安装包并安装4.29.0版本 终端输入 wsl --installdocker桌面版和模拟器只能选一个&#xff0c;不然一直转圈圈 镜像配置加速&#xff0c;在settings—>docker engine下 {"builder": {"gc": {"defaultKee…

【RAG 论文】AAR:训练一个LLM喜欢的检索器来做RAG

论文&#xff1a;Augmentation-Adapted Retriever Improves Generalization of Language Models as Generic Plug-In ⭐⭐⭐ ACL 2023, Tsinghua & Microsoft&#xff0c;arXiv:2305.17331 论文速读 以往 RAG 的工作通常联合微调 retriever 和 LLM 导致紧密耦合&#xff0…

算法学习008-登山爬石梯 c++动态规划/递归算法实现 中小学算法思维学习 信奥算法解析

目录 C登山爬石梯 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、推荐资料 C登山爬石梯 一、题目要求 1、编程实现 小明周末和朋友约好了一起去爬山&#xff0c;来到山下&#xff0c;发现登山道是…

【机器学习300问】87、学习率这种超参数在优化时选择随机搜索方法,为什么要在对数尺度范围进行随机搜索?

在超参数优化过程中&#xff0c;对数尺度范围进行随机采样对于某些类型的超参数来说是非常有效的&#xff0c;特别是当超参数的有效值跨越几个数量级时。学习率就是这样一种超参数&#xff0c;它可以从非常小&#xff08;例如&#xff09;到相对大的值&#xff08;例如&#xf…