Q1:为什么使用SurfaceView

SurfaceView的目的:提供了一个Surface,非UI(主)线程(即Render线程)通过此Surface可以把内容绘制到屏幕上。

我们知道View是通过刷新来重绘视图,系统通过发出VSSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题。

另外,对一些游戏画面,或者摄像头,视频播放等,UI都比较复杂,要求能够进行高效的绘制,因此,他们的UI不适合在主线程中绘制。这时候就必须要给那些需要复杂而高效的UI视图生成一个独立的绘制表面Surface,并且使用独立的线程来绘制这些视图UI。

使用概述:


       • UI(主线程): 调用 SurfaceView.getHolder()获得SurfaceHolder对象

       • UI(主线程): 调用SurfaceHolder.addCallback(callback)添加回调函数

       • Render线程: 调用SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布

       • Render线程: 调用Canvas的画图函数画图 

       • Render线程: 调用SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并将图形显示

UI(主)线程与Render线程的分工:

        • UI(主)线程:所有SurfaceView和SurfaceHolder.Callback的方法都由UI(主)线程调用

        • Render线程:Render线程只有在SurfaceHolder.Callback.surfaceCreated()与

             SurfaceHolder.Callback.surfaceDestroyed() 之间可以通过Surface来画图。

        • 由于存在两个线程,对于共享对象,需要进行同步访问。 

Q2:SurfaceView使用

Android SurfaceView简介与使用 - 安卓开发 | 软件开发 | 编程 | RustFisher

创建一个类继承SurfaceView并实现SurfaceHolder.Callback接口

public class MySView extends SurfaceView implements SurfaceHolder.Callback {
    .........
}

获取SurfaceHolder

在构造函数中获取SurfaceHolder。并添加回调。

public MySView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    holder = getHolder();
    holder.addCallback(this);
    // ......
}

复写方法:

@Override
public void surfaceCreated(SurfaceHolder holder) {
    Log.d(TAG, "surfaceCreated");
    drawThread = new DrawThread(holder);// 创建一个绘图线程
    drawThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    Log.d(TAG, "surfaceChanged");
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    drawThread.closeThread();// 销毁线程
    Log.d(TAG, "surfaceDestroyed");
}

Activity不可见时,SurfaceView会调用surfaceDestroyed方法。此时就要销毁绘图线程。

绘图子线程

SurfaceView中绘图操作是在子线程中进行的。正常来说子线程不能操作UI。但是我们可以使用 SurfaceHolder提供的方法锁定画布,绘图完成后释放并更新画布。 下面的线程提供了停止、恢复和结束等功能。

class DrawThread extends Thread {

    private SurfaceHolder mmHolder;

    public DrawThread(SurfaceHolder holder) {
        this.mmHolder = holder;
        mmRunning = true;
    }

    @Override
    public void run() {
        while (mmRunning && !isInterrupted()) {
            if (!mmIsPause) {
                Canvas canvas = null;
                try {
                    synchronized (mmHolder) {
                        canvas = holder.lockCanvas();        // 锁定画布,获得返回的画布对象Canvas
                        canvas.drawColor(bgSurfaceViewColor);// 设置画布背景颜色
                        // 绘图操作......
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (canvas != null) {
                        mmHolder.unlockCanvasAndPost(canvas);// 释放画布,并提交改变。
                    }
                    pauseThread();
                }
            } else {
                onThreadWait();
            }
        }
    }

    /**
     * 线程等待,不提供给外部调用
     */
    private void onThreadWait() {
        // ...
    }

    public synchronized void resumeThread() {
        // ...
    }

    public synchronized void closeThread() {
        // ...
    }
}

Q3:View和SurfaceView的区别

View:必须在UI的主线程中更新画面,用于被动更新画面。
SurfaceView:UI线程和子线程中都可以。在一个新启动的线程中重新绘制画面,主动更新画面。

Q4:SurfaceView为什么可以直接子线程绘制

通常View更新的时候都会调用ViewRootImpl中的performXXX()方法,在该方法中会首先使用checkThread()检查是否当前更新位于主线线程;

SurfaceView提供了专门用于绘制的Surface,可以通过SurfaceView来控制Surface的格式和尺寸,SurfaceView更新就不需要考虑线程的问题,它既可以在子线程更新,也可以在主线程更新。