双缓冲机制

问题的由来

CPU访问内存的速度要远远快于访问屏幕的速度。如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕,从而导致效率很低。这就跟CPU和内存之间还需要有三级缓存一样,需要提高效率。

第一层缓冲

在绘制图像时不用上述一个一个绘制的方案,而采用先在内存中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕,从而提高绘制的效率。Android中View的onDraw()方法已经实现了这一层缓冲。onDraw()方法中不是绘制一点显示一点,而是都绘制完后一次性显示到屏幕。

第二层缓冲

onDraw()方法的Canvas对象是和屏幕关联的,而onDraw()方法是运行在UI线程中的,如果要绘制的图像过于复杂,则有可能导致应用程序卡顿,甚至ANR。因此我们可以先创建一个临时的Canvas对象,将图像都绘制到这个临时的Canvas对象中,绘制完成之后再将这个临时Canvas对象中的内容(也就是一个Bitmap),通过drawBitmap()方法绘制到onDraw()方法中的canvas对象中。这样的话就相当于是一个Bitmap的拷贝过程,比直接绘制效率要高,可以减少对UI线程的阻塞。


SurfaceView

在SurfaceView中,我们一般都会开启一个子线程,然后在子线程的run方法中通过SurfaceHolder的lockCanvas方法获取到Canvas进行绘制操作,绘制完以后再通过SurfaceHolder的unlockCanvasAndPost方法释放canvas并提交更改。

SurfaceView的特点

  • View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁的刷新
  • View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新
  • View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制

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被创建的时候被调用@Overridepublic void surfaceCreated(SurfaceHolder holder) {isDrawing = true;new Thread(this).start();}//当SurfaceView的视图发生改变,比如横竖屏切换时,这个方法被调用@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}//当SurfaceView被销毁的时候,比如不可见了,会被调用@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {isDrawing = false;surfaceHolder.removeCallback(this);}@Overridepublic void run() {while (isDrawing) {draw();}}private void draw() {try {canvas = surfaceHolder.lockCanvas();//执行具体的绘制操作} catch (Exception e) {e.printStackTrace();}finally {if (canvas != null) {surfaceHolder.unlockCanvasAndPost(canvas);}}}
}

以上是SurfaceView的一种常见的代码模版,因为SurfaceView主要用在视频播放以及游戏等应用中,这里只做一些简单的介绍,不做深入的探讨。

(1)SurfaceView必须实现SurfaceHolder的Callback接口,主要是3个方法,分别是surfaceCreated、surfaceChanged、surfaceDestroyed。从名字就可以看出来这个是监听SurfaceView状态的,跟Activity的生命周期有点像。

  • 当SurfaceView被创建时,surfaceCreated方法会被调用,surfaceCreated方法中一般做初始化动作,比如设置绘制线程的标记位,创建用于绘制的子线程等
  • 当SurfaceView的状态改变时,比如尺寸大小、格式等,常见的操作就是旋转屏幕了,这个时候surfaceChanged方法会被调用。
  • 当SurfaceView被销毁时,surfaceDestroyed方法会被调用。surfaceDestroyed被调用后,就不能再对Surface对象进行任何操作,所以我们需要在surfaceDestroyed方法中将绘制的子线程停掉。

(2)由于SurfaceView常被用于游戏、视频等场景,绘制操作会相对复杂很多,通常都需要开启子线程,在子线程中执行绘制操作,以免阻塞UI线程。在子线程中,我们通过SurfaceHolder的lockCanvas方法获取Canvas对象来进行具体的绘制操作,此时Canvas对象被当前线程锁定,绘制完成后通过SurfaceHolder的unlockCanvasAndPost方法提交绘制结果并释放Canvas对象。

(3)用于控制子线程绘制的标记参数,如上面代码中的isDrawing变量,需要用volatile关键字修饰,以保证多线程安全。

(4)由上面代码可见, 通过将绘制操作移到子线程中,这也是双缓冲的体现。

SurfaceView、SurfaceHolder和Surface的简单介绍

要分析SurfaceView,就得和其他2个类一起分析,那就是SurfaceHolder和Surface,这3者之间其实是典型的MVC模式,其中SurfaceView对应的就是View层,SurfaceHolder就是controler接口,而Surface就是对应的Model层,它里面持有Canvas,保存着绘制的数据。

(1)SurfaceView中持有SurfaceHolder和Surface,SurfaceHolder中的接口可以分为2类,一类是Callback接口,也就是我们上面模版代码中实现的3个接口方法,这类接口主要是用于监听SurfaceView的状态,以便我们进行相应的处理,比如创建绘制子线程,停止绘制等。另一类方法主要用于和Surface以及SurfaceView交互,比如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 {public void surfaceRedrawNeeded(SurfaceHolder holder);}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();...
}

(2)SurfaceView继承自View,但是其实和View是有很大的不同的,除了文章前面介绍的几点SurfaceView的特性外,在底层SurfaceView也很大的不同,包括拥有自己独立的绘图表面等。从下面SurfaceView的源码中我们可以看到,我们调用SurfaceHolder的lockCanvas方法实际上调用的是Surface的lockCanvas方法,返回的是Surface中的Canvas。并且调用过程加了一个可重入锁mSurfaceLock。所以绘制过程中只能绘制完一帧内容并提交更改以后才会释放Canvas,也就是才能继续下一帧的绘制操作

public class SurfaceView extends View {...final Surface mSurface = new Surface(); final ReentrantLock mSurfaceLock = new ReentrantLock();private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {private static final String LOG_TAG = "SurfaceHolder";...@Overridepublic 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);}}}@Overridepublic void removeCallback(Callback callback) {synchronized (mCallbacks) {mCallbacks.remove(callback);}}@Overridepublic Canvas lockCanvas() {return internalLockCanvas(null);}@Overridepublic 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;}@Overridepublic void unlockCanvasAndPost(Canvas canvas) {mSurface.unlockCanvasAndPost(canvas);mSurfaceLock.unlock();}@Overridepublic Surface getSurface() {return mSurface;}@Overridepublic Rect getSurfaceFrame() {return mSurfaceFrame;}};...
}

(3)Surface实现了Parcelable接口,因为它需要在进程间以及本地方法间传输。Surface中创建了Canvas对象,用于执行具体的绘制操作

/*** Handle onto a raw buffer that is being managed by the screen compositor.* ...*/
public class Surface implements Parcelable {final Object mLock = new Object(); // protects the native stateprivate 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;}}...
}

总结

  • 我们学习了View绘制中的双缓冲机制,并了解了SurfaceView的特性和使用方法。
  • SurfaceView主要用于游戏、视频等复杂视觉效果的场景,利用双缓冲机制,在子线程中执行复杂的绘制操作,可以防止阻塞UI线程。
  • 我们在使用SurfaceView时一般都要实现Runnable接口和SurfaceHolder的Callback接口,并开启子线程进行具体的绘制操作
  • 因为SurfaceView的特殊使用场景,所以本文没有做深入的分析,后面如果有机会做视频方面的场景再来好好深入分析学习。
  • 重点在于双缓冲机制的理解,这个面试时也会经常问道。

作者:xxq2dream
链接:https://www.jianshu.com/p/afe23814b207

关于Surface的底层双缓冲机制学习相关推荐

  1. android缓冲机制,Android自定义View之双缓冲机制和SurfaceView

    Android自定义View系列 双缓冲机制 问题的由来 CPU访问内存的速度要远远快于访问屏幕的速度.如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕, ...

  2. Android SurfaceView的双缓冲机制,引起的闪屏问题

    SurfaceView相关目录 SurfaceView要点 SurfaceView拥有独立的Surface(绘图表面) SurfaceView是用Zorder排序的,他默认在宿主Window的后面,S ...

  3. 今儿新学会一个写日志技能:双缓冲机制

    摘要:通过交换指针的方式实现两个缓冲区的功能互换,十分巧妙,令人称赞. 本文分享自华为云社区<奇妙的双缓冲机制写日志(Java实现)>,作者: 洛叶飘 . 写日志面临的问题 写日志在Web ...

  4. QT5开发及实例学习之十七Qt5双缓冲机制

    文章目录 一.原理与设计 二.绘图区的实现 三.主窗口的实现 一.原理与设计   所谓双缓冲机制,是指在绘制控件时,首先将要绘制的内容绘制在一个图片中,再将图片一次性地绘制到控件上.在早期的 Qt 版 ...

  5. Linux-FrameBuffer双缓冲机制显示图像

    1. 液晶屏的基本概念 像素: 屏幕上显示颜色的最小单位,英文叫 pixel.注意,位图(如jpg.bmp等格式的常见图片)也是由一个个的像素点构成的,跟屏幕的像素点的概念一样.原理上讲,将一张位图显 ...

  6. MFC---定时器和双缓冲机制绘制旋转的金刚石图案

    双缓冲原理 MFC中绘制动画的基本思路是在固定时间间隔内绘制图像,然后擦除旧图像再绘制新图像,这样连续         起来就在人类的视觉上形成动画.为了实现这种"绘制-擦除-再绘制&quo ...

  7. 多线程异步日志系统,高效、强悍的实现方式-双缓冲

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 目录 文章目录 单片机中常用的环形缓冲区 多线程异步日志:双缓冲机制 双缓冲机制为什么高效 尽可能的降低 Lock 的时 ...

  8. EGE基础入门篇(九):双缓冲与手动渲染

    EGE专栏:EGE专栏 上一篇:EGE基础入门篇(八):清屏与重绘 下一篇: 文章目录 一.双缓冲机制 1. 单缓冲绘图 1.1 单缓冲绘图的缺点 1.2 系统读取帧缓冲 2. 双缓冲绘图 2.1 双 ...

  9. JAVA多线程双缓冲笔记_关于多线程学习的笔记

    task: implement Observer and Observable, read shared_ptr base 库 1. base/StringPiece.h Viewpoint 1. 判 ...

最新文章

  1. git push everything up to date问题解决
  2. android服务的说法错误的是,下面关于Android开发描述有误的一项是()。
  3. MYSQL描述选课系统的问题与_mysql+php实现选课系统中遇到的问题及解决方法
  4. hibernate延迟加载,LazyInitializationException session失效问题。多数据源配置
  5. The 2021 ICPC Asia Regionals Online Contest (I)
  6. json日期格式转换为正常格式
  7. Python库详解。python有那些库你都知道了嘛?
  8. uboot移植之迷雾解码
  9. 中国计量大学计算机考研难吗,去中国计量大学读研好吗 考研题难吗_
  10. php如何实现快速压缩视频,如何把大视频压缩小 怎么将视频压缩到最小方便储存...
  11. [经验教程]拼多多退店保证金多久到账 拼多多退店正确操作35天内保证金到账
  12. server多笔记录拼接字符串 sql_第四章、SQL Server数据库查询大全(单表查询、多表连接查询、嵌套查询、关联子查询、拼sql字符串的查询、交叉查询)...
  13. html涟漪效果,涟漪效果.html
  14. 【Unity性能优化】静态资源优化——Audio优化
  15. 邮箱输入注册测试用例
  16. 大数据真的很牛B吗?不不不,10分钟让你读懂它
  17. 原创OI题目:部落冲突
  18. 英语和数学不好是不是学不了计算机语言,英语和数学不好能学好C语言吗
  19. 边缘设备、系统及计算杂谈(9)——dapr学习之二
  20. 《代码整洁之道》简单总结

热门文章

  1. hive上亿级别的表关联 调优
  2. 苹果app充值限制解除_抖音短视频app充值抖币苹果为什么比安卓贵?
  3. 好看的日剧推荐-看日剧学日语
  4. 深入理解虚拟机实战:修改class文件实现System标准输出重定向
  5. 局域网测速:iperf实现局域网点对点测试传输速度
  6. 学习Python写的基础知识笔记
  7. TextView的ClickableSpan、OnClickListener、OnLongClickListener冲突的问题
  8. Java面试题(一)-----Java的优势
  9. Send A Tweet
  10. Android 多商品订单评价(类似淘宝)