赞
踩
surfaceview内部机制和外部层次结构
在安卓开发中,我们经常会遇到一些需要高性能、高帧率、高画质的应用场景,例如视频播放、游戏开发、相机预览等。这些场景中,我们需要直接操作图像数据,并且实时地显示到屏幕上。如果我们使用普通的view组件来实现这些功能,可能会遇到以下问题:
为了解决这些问题,安卓提供了一种特殊的view组件:surfaceview 。surfaceview拥有自己独立的surface,也就是一个可以在其上直接绘制内容的图形缓冲区。surfaceview的内容是透明的,可以嵌入到view层次结构中,并且可以和其他view进行重叠或者裁剪。surfaceview适用于需要频繁刷新或处理逻辑复杂的绘图场景,如视频播放、游戏等。
下图展示了surfaceview和普通view在屏幕上的显示效果:
surfaceview和普通view
从图中可以看出,普通view是按照顺序依次绘制到屏幕上的,而surfaceview则是直接绘制到屏幕上的一个透明区域,并且可以和其他view进行重叠或者裁剪。
从上面的介绍中,我们已经了解了surfaceview和普通view在显示效果上的区别。那么,在实现原理和使用方式上,它们又有什么不同呢?下面我们来对比一下它们的主要区别:
特点 | 普通view | surfaceview |
---|---|---|
更新方式 | 主动更新,可以在任何时候调用invalidate方法来触发重绘,在onDraw方法中使用canvas进行绘制 | 被动更新,不能直接控制重绘,需要通过一个子线程来进行页面的刷新,在子线程中直接操作surface进行绘制 |
刷新线程 | 主线程刷新,可以保证界面的一致性和同步性,但是可能导致主线程阻塞或者掉帧 | 子线程刷新,可以避免主线程阻塞,并且可以提高刷新频率和效率,但是需要注意线程间的通信和同步问题 |
缓冲机制 | 无双缓冲机制,每次绘制都是直接在屏幕上进行,可以节省内存空间,但是可能导致闪烁或者撕裂的现象 | 有双缓冲机制,每次绘制都是先在一个缓冲区中进行,然后再将缓冲区中的内容复制到屏幕上,可以避免闪烁或者撕裂的现象,并且可以提高绘制质量,但是需要消耗更多的内存空间 |
了解了surfaceview和普通view的区别之后,我们就可以开始创建和使用surfaceview了。创建自定义的surfaceview需要以下几个步骤:
下面给出一个简单的示例代码,实现了一个简单的画板功能:
- //自定义类继承自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的大小或位置
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- //设置标志位为true,表示子线程可以开始运行
- mIsDrawing = true;
- //创建并启动子线程
- mThread = new Thread(this);
- mThread.start();
- }
-
- //当Surface被改变时,重新获取Surface的宽高,并根据需要调整View的大小或位置
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- //TODO: 根据需要调整View的大小或位置
- }
-
- //当Surface被销毁时,停止子线程,并释放相关资源
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- //设置标志位为false,表示子线程可以停止运行
- mIsDrawing = false;
- try {
- //等待子线程结束,并释放子线程对象
- mThread.join();
- mThread = null;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- //实现run方法,实现子线程的绘图逻辑
- @Override
- public 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不可用,则返回null
- mCanvas = 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上绘制了一条随机的折线图,并且显示到了屏幕上。这只是一个简单的示例,我们可以根据自己的需求,实现更复杂的绘图逻辑和效果。
在使用surfaceview时,我们需要注意它和activity的生命周期之间的关系。因为surfaceview是嵌入到view层次结构中的,所以它会受到activity的生命周期的影响。但是,surfaceview也有自己的生命周期,它是由surfaceholder来管理的。因此,对于具有surfaceview的activity,存在两个单独但相互依赖的状态机:应用oncreate/onresume/onpause和已创建/更改/销毁的surface。
下图展示了这两个状态机之间的关系:
surfaceview和activity的状态机之间的关系
surfaceview和activity的生命周期
从图中可以看出,当activity被创建时,会触发surfaceview的创建;当activity被恢复时,会触发surfaceview的改变;当activity被暂停时,会触发surfaceview的销毁。因此,在这些事件中,我们需要做一些相应的处理,例如:
在上面的内容中,我们介绍了如何使用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图形效果。
了解了glsurfaceview和普通surfaceview的区别之后,我们就可以开始创建和使用glsurfaceview了。创建自定义的glsurfaceview需要以下几个步骤:
下面给出一个简单的示例代码,实现了一个简单的3D立方体效果:
- //自定义类继承自GLSurfaceView,并在构造方法中初始化相关对象
- public class MyGLSurfaceView extends GLSurfaceView {
-
- //声明渲染器对象
- private MyRenderer mRenderer;
-
- //构造方法,初始化相关对象
- public MyGLSurfaceView(Context context) {
- super(context);
- //设置opengl es版本为2.0
- setEGLContextClientVersion(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 left
- 0.5f, -0.5f, -0.5f, // front bottom right
- 0.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 left
- 0.5f, -0.5f, 0.5f, // back bottom right
- 0.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 face
- 0, 2, 3,
- 4, 5, 6, // back face
- 4, 6, 7,
- 0, 4, 7, // left face
- 0, 7, 3,
- 1, 5, 6, // right face
- 1, 6, 2,
- 3, 2, 6, // top face
- 3, 6, 7,
- 0, 1, 5, // bottom face
- 0, 5, 4
- };
-
- //声明颜色数组
- private final float[] colors = {
- 1.0f, 0.0f, 0.0f, 1.0f, // red
- 0.0f, 1.0f, 0.0f, 1.0f, // green
- 0.0f, 0.0f, 1.0f, 1.0f, // blue
- 1.0f, 1.0f, 0.0f, 1.0f, // yellow
- 1.0f, 0.0f, 1.0f, 1.0f, // magenta
- 0.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对象,并加载和编译着色器,创建和绑定图形数据,设置相机位置和投影方式等操作
- @Override
- public 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被改变时,调整视口大小,并设置投影方式,生成投影矩阵
- @Override
- public 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被绘制时,清除屏幕,并旋转模型矩阵,生成模型视图投影矩阵,并传输和绘制图形数据
- @Override
- public 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是一种专门用于渲染opengl es内容的surfaceview。opengl es是一种用于嵌入式设备上的3D图形渲染API。glsurfaceview类提供了用于管理egl上下文、在线程间通信以及与activity生命周期交互的辅助程序类。使用glsurfaceview时,无需自己创建和管理子线程,只需实现glsurfaceview.renderer接口,并设置给glsurfaceview对象即可。
glsurfaceview和surfaceview都是继承自surfaceview的类,都可以在子线程中直接操作surface进行绘制。但是glsurfaceview相比surfaceview有以下的优势:
创建自定义的glsurfaceview继承glsurfaceview,并在构造方法中设置opengl es版本、渲染器对象和渲染模式。创建自定义的渲染器实现glsurfaceview.renderer接口,并在回调方法中进行初始化、视口设置和绘图操作。
以下是一个简单的示例代码:
- // 自定义的glsurfaceview类
- public class MyGLSurfaceView extends GLSurfaceView {
-
- // 构造方法
- public MyGLSurfaceView(Context context) {
- super(context);
- // 设置opengl es版本为2.0
- setEGLContextClientVersion(2);
- // 设置渲染器对象
- setRenderer(new MyRenderer());
- // 设置渲染模式为连续模式
- setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
- }
-
- // 自定义的渲染器类
- private class MyRenderer implements GLSurfaceView.Renderer {
-
- // 渲染器创建时的回调方法
- @Override
- public void onSurfaceCreated(GL10 gl, EGLConfig config) {
- // 在这里进行一些初始化操作,比如设置清屏颜色为黑色
- GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- }
-
- // 渲染器改变时的回调方法
- @Override
- public void onSurfaceChanged(GL10 gl, int width, int height) {
- // 在这里进行一些视口设置操作,比如设置视口大小为surface的大小
- GLES20.glViewport(0, 0, width, height);
- }
-
- // 渲染器绘制时的回调方法
- @Override
- public void onDrawFrame(GL10 gl) {
- // 在这里进行一些绘图操作,比如清屏
- GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
- }
- }
- }
当使用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回调方法。
在这个过程中,需要注意以下几点:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。