双缓冲机制
CPU访问内存的速度要远远快于访问屏幕的速度
每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕
第一层缓冲
先在内存中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(rect,mPaint);
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
canvas.drawCircle(cx,cy,100,mPaint);
}
我们会看到,两个几乎同时一起显示出来的。这就说明必须要等onDraw方法执行完成之后,才会把数据交给GPU去处理展示。这就是android绘图当中的第一道缓冲,即显示缓冲区。
第二层缓冲
onDraw()方法的Canvas对象是和屏幕关联的,而onDraw()方法是运行在UI线程中的,如果要绘制的图像过于复杂,则有可能导致应用程序卡顿,甚至ANR。因此我们可以再创建一个Canvas和对应的Bitmap,将图像都绘制到这个临时的Canvas对象中,然后在onDraw方法里默认的Canvas通过drawBitmap画刚才new的那个bitmap可以减少对UI线程的阻塞。
private void init(){
Bitmap bufferBm = Bitmap.create(getWidth,getHeight,Bitmap.Config.ARGB_8888);
Canvas bufferCanvas = new Canvas(bufferBm);
}
private void drawSomething(){
bufferCanvas.drawRect();
bufferCanvas.drawCircle();
...
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bufferBm,0,0,null);
}
示意图
适用双缓冲的情况
- 在绘制数据量较小时,不使用双缓冲,GPU的负荷更低,即绘制性能更高
- 在绘制数据量较大时,使用双缓冲绘图,绘制性能明显高于不使用双缓冲的情况
- 使用双缓冲会增加内存消耗
SurfaceView的双缓冲
SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你再调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你讲重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。
SurfaceView
一般会开启一个子线程然后在子线程的run方法中通过SurfaceHolder
的lockCanvas方法获取到Canvas进行绘制操作,绘制完以后再通过SurfaceHolder的unlockCanvasAndPost方法释放canvas并提交更改
SurfaceView和View的对比
- View适用于主动更新,SurfaceView适用于被动更新(频繁刷新)
- View在主线程中对画面进行刷新,而SurfaceView会通过一个子线程来页面刷新
- View绘图没有使用双缓冲机制,而SurfaceView在底层实现机制中就已实现了双缓冲机制
SurfaceView、SurfaceHolder和Surface
这三者之间是典型的MVC模式,其中SurfaceView对应的是View层,SurfaceHolder就是controller接口,而Surface就是对应的Model层,它里面持有Canvas,保存着绘制的数据。
SurfaceView部分源码
继承View,和View有很大的不同,比如在底层拥有自己独立的绘图表面。SurfaceView中持有SurfaceHolder和Surface
以下是SurfaceView的源码(API 28以前的),从中我们可以看出
- 调用SurfaceHolder的lockCanvas方法实际上调用的是Surface的lockCanvas方法,返回的是Surface中的Canvas
- 调用过程加了一个可重入锁mSurfaceLock。所以绘制过程中只能绘制完一帧内容并提交更改以后才会释放Canvas,也就是才能继续下一帧的绘制操作
可重入锁ReentrantLock
- 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;
- 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。
示意图
public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback{
...
final Surface mSurface = new Surface();
final ReentrantLock mSurfaceLock = new ReentrantLock();
public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
...
@Override
public void addCallback(Callback callback) {
synchronized (mCallbacks) {
// This is a linear search, but in practice we'll
// have only a couple callbacks, so it doesn't matter.
if (mCallbacks.contains(callback) == false) {
mCallbacks.add(callback);
}
}
}
@Override
public void removeCallback(Callback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
@Override
public Canvas lockCanvas() {
return internalLockCanvas(null);
}
@Override
public Canvas lockCanvas(Rect inOutDirty) {
return internalLockCanvas(inOutDirty);
}
private final Canvas internalLockCanvas(Rect dirty) {
mSurfaceLock.lock();
Canvas c = null;
if (!mDrawingStopped && mWindow != null) {
try {
c = mSurface.lockCanvas(dirty);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}
long now = SystemClock.uptimeMillis();
long nextTime = mLastLockTime + 100;
if (nextTime > now) {
try {
Thread.sleep(nextTime-now);
} catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
}
mLastLockTime = now;
mSurfaceLock.unlock();
return null;
}
@Override
public void unlockCanvasAndPost(Canvas canvas) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
return mSurface;
}
@Override
public Rect getSurfaceFrame() {
return mSurfaceFrame;
}
};
...
}
SurfaceHolder
接口内容可分为两类:
- 第一类(
Callback接口
)主要用于监听SurfaceView的状态(比如创建绘制子线程,停止绘制等) - 第二类主要用于和SurfaceView以及Surface交互,比如lockCanvas方法和unlockCanvasAndPost方法用于获取Canvas以及提交绘制结果等
public interface SurfaceHolder {
...
public interface Callback {
public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
public void surfaceDestroyed(SurfaceHolder holder);
}
public interface Callback2 extends Callback {
void surfaceRedrawNeeded(SurfaceHolder holder);
default void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) {
surfaceRedrawNeeded(holder);
drawingFinished.run();
}
}
public void addCallback(Callback callback);
public void removeCallback(Callback callback);
public Canvas lockCanvas();
public Canvas lockCanvas(Rect dirty);
public void unlockCanvasAndPost(Canvas canvas);
public Surface getSurface();
...
}
Surface
Surface实现了Parcelable接口
,因为它需要在进程间以及本地方法间传输,Surface中创建了Canvas对象(用于执行具体的绘制操作)
public class Surface implements Parcelable {
final Object mLock = new Object(); // protects the native state
private final Canvas mCanvas = new CompatibleCanvas();
...
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
if (mLockedObject != 0) {
throw new IllegalArgumentException("Surface was already locked");
}
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) {
mHwuiContext.unlockAndPost(canvas);
} else {
unlockSwCanvasAndPost(canvas);
}
}
}
private void unlockSwCanvasAndPost(Canvas canvas) {
if (canvas != mCanvas) {
throw new IllegalArgumentException("canvas object must be the same instance that "
+ "was previously returned by lockCanvas");
}
if (mNativeObject != mLockedObject) {
Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
Long.toHexString(mLockedObject) +")");
}
if (mLockedObject == 0) {
throw new IllegalStateException("Surface was not locked");
}
try {
nativeUnlockCanvasAndPost(mLockedObject, canvas);
} finally {
nativeRelease(mLockedObject);
mLockedObject = 0;
}
}
...
}
SurfaceView的模板代码
// 必须实现SurfaceHolder.Callback接口和Runnable接口
// 开启子线程进行具体的绘制操作
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
private SurfaceHolder surfaceHolder;
private Canvas canvas;
//子线程绘制标记
private volatile boolean isDrawing;
public MySurfaceView(Context context) {
super(context);
init();
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
setFocusable(true);
// setFocusableInTouchMode(true);
// setKeepScreenOn(true);
}
//当SurfaceView被创建的时候被调用
@Override
public void surfaceCreated(SurfaceHolder holder) {
isDrawing = true;
new Thread(this).start();
}
//当SurfaceView的视图发生改变,比如横竖屏切换时,这个方法被调用
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
//当SurfaceView被销毁的时候,比如不可见了,会被调用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isDrawing = false;
surfaceHolder.removeCallback(this);
}
@Override
public void run() {
while (isDrawing) {
draw();
}
}
private void draw() {
try {
canvas = surfaceHolder.lockCanvas();
//执行具体的绘制操作
} catch (Exception e) {
e.printStackTrace();
}finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
- 必须实现SurfaceHolder的Callback接口,主要是3个方法,分别是surfaceCreated、surfaceChanged、surfaceDestroyed
- 当SurfaceView被创建时,surfaceCreated方法会被调用,surfaceCreated方法中一般做初始化动作,比如设置绘制线程的标记位,创建用于绘制的子线程等
- 当SurfaceView的状态改变时,比如尺寸大小、格式等,常见的操作就是旋转屏幕了,这个时候surfaceChanged方法会被调用
- 当SurfaceView被销毁时,surfaceDestroyed方法会被调用。surfaceDestroyed被调用后,就不能再对Surface对象进行任何操作,所以我们需要在surfaceDestroyed方法中将绘制的子线程停掉。
- 用于控制子线程绘制的标记参数,如上面代码中的isDrawing变量,需要用volatile关键字修饰,以保证多线程安全
- 通过将绘制操作移到子线程中,这也是双缓冲的体现