安卓笔记-SurfaceView的学习(二)关于双(三)缓冲的理解

    首先关于安卓SurfaceView双缓冲的意思,我在上篇文章:安卓笔记-SurfaceView的学习(一)理解与使用 里面有简单的解释:如果画面在显示的时候发生了改变,那么会产生撕裂感,为了避免这种情况发生,安卓在画面进行显示时,不允许对其进行绘图操作,因此SurfaceView使用了双缓冲的策略,也就是,一个缓冲用来显示(front buffer),另一个缓冲用来绘图(back buffer),当下一帧显示时,交换两个缓冲。请注意,这里只是交换!并没有复制或者同步的过程,这也就意味着两个缓冲的内容可能是不同的!

    为了验证安卓的双缓冲,网上有一个非常非常经典的deom,以下我们以该dome为例进行详细的解析与探究。

    先简单介绍该deom,它使用了SurfaceView,并在绘图线程中循环绘制数字,每次循环数字加一,直到数字为30时停止绘图。

    首先新建类继承SurfaceView并实现相关方法,这里不在赘述,可以参考上篇文章。

public class LearnSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
    private Thread thread;
    private SurfaceHolder surfaceHolder;
    private Paint mypaint;
    private boolean ThFlag;
    private int count;
    ...
    ...
}

    然后再在线程中循环绘图:

    @Override
    public void run() {
        while (ThFlag){
            if (count==31)
                break;
            long start = System.currentTimeMillis();
            Canvas canvas=null;
            try {
                canvas = surfaceHolder.lockCanvas();
                if(canvas!=null)
                {
                    canvas.drawText(count+"",100,100+50*count,mypaint);
                }
            } catch (Exception e) {
            }finally{
                if(canvas!=null)
                {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
            count++;
            long end = System.currentTimeMillis();
            try {
                if (end - start < 1000) {
                    Thread.sleep(1000 - (end - start));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    在这个绘图进程里,为了让每一帧以固定的时间刷新,我加上了一个延时,将这个延时调到了一秒,让观察更加容易,倘若Surface是一个缓冲,那么每次都在同一buffer上绘图,得到的结果将会是:第一帧显示0,一秒后,显示0 1,之后显示0 1 2 以此类推。如果是双缓冲就应该是:第一帧0,然后1,再为0 2,再为1 3 ...直到count等于31。

    那么实际运行结果是什么呢?

    通过我的实际测试,目前结果有两种(手机个数有限)。

    第一个结果:在华为手机上,系统为安卓5.1,它的显示为第一帧0,然后1,然后2,然后1 3,再为2 4,一直到最后停止,截图如下:

    1458664831.png

    可以看出结果与我们双缓冲的预期基本一致,之所以说基本一致,是因为我们预期里第一个应该为0,而实际上,整个过程中0只出现了一次,即第一帧,关于这个问题,后面再讨论。

    看起来一切已经明了了,然而,我另一个手机(魅蓝note,安卓4.4,吐槽下魅族电信版后娘养的不更新)却有不同的结果:首先是0,然后为1,然后2,然后3,然后1 4,再2 5,再3 6,再1 4 7...直到结束。结束后的图:

    1458665661.jpg

    从这个过程中看出,仿佛它是三缓冲,那么为什么和另一个手机表现不同呢?是因为制造商还是安卓版本?可是无论手机配置还是系统版本,华为手机都比魅族那好,为什么反而缓冲变少了?

    通过查阅安卓官方关于Graphics的原理里有这样的解释:

    The BufferQueue for a display Surface is typically configured for triple-buffering; but buffers are allocated on demand. So if the producer generates buffers slowly enough -- maybe it's animating at 30fps on a 60fps display -- there might only be two allocated buffers in the queue. This helps minimize memory consumption.

    翻译过来就是:用来显示的Surface的buffer队列通常配置为三缓冲,但是缓冲的分配是看情况而定的,因此,如果生产者绘图足够的慢,或许差不多30fps,在60hz刷新率的屏幕显示时,就有可能只分配两个缓冲,这样可以节省内存开销。

    所以以上的原因就明了了,因为我的线程的刷新速递为一秒一次,所以在华为手机上的结果就是双缓冲的结果。

    要验证它,只要将速度调快,时间调小即可。当调到20的时候,华为手机运行结果证实了以上说法。

    1458667154.png

    但是多次运行发现,偶尔还是有一些不确定的情况发生:

    1458667305.png

    这现象其实也很好解释,刚开始可能系统处理的过来所以使用的是双缓冲,在数字15的时候,开启了第三个缓冲,因此产生了图上的奇怪现象。

    结论:

    通过以上过程,我们可以知道,安卓SurfaceView使用双缓冲,并且会根据情况自己抉择是否使用三缓冲,这意味着如果每次画布不做同步,而直接绘制改变的部分,那么很可能会将它们画在不同的缓冲中,也可能会导致画面更新只在某个缓冲中发生了改变,而其他缓冲没更新,当不同的缓冲内容有较大差异时候,三个缓冲循环起来就有可能会引起画面的闪烁(试想一下:"1 4","2 5","3 6";循环播放所产生的123456效果与直接"1","12","123"..."123456"所产生123456的区别),所以使用SurfaceView一定注意缓冲区的同步,至于如何同步,下一次文章再讲。

    完整代码:code.rar

    等等。。。还有一个问题没讲,以上所有实验中数字0都是只出现一次,关于这个我已经有了基本的猜想,下篇文章一起讲。



    U-NI.CN 版权所有 未经同意 不得转载

暂无评论
发表评论