android双缓冲绘图技术分析

双缓冲、多缓冲技术在计算机科学中其实是一个广义的概念,不过其本质上的意思都是差不多的。今天我们就来讲一讲双缓冲技术在android绘图中的应用。

何谓缓冲?

在理解双缓冲的原理之前,我们先要明白,什么叫缓冲?
我们可以举一个比较通俗的粟子,比如:

工头给你一个任务,让你把50块大板砖从A处搬到距离你1000米之外的B处去。你心想,50块板砖? 小case,我一次就能扛完。于是你撸起袖子,一步一步,真的一趟就搞定了。这个时候工头一声奸笑对你说,小伙子不错,那边还有2000块砖,你也搬过去吧。。。
看到这堆积如山的砖头,你眼前一黑,这孙子真是想累死我啊,钱给这么少还干这么多活!
可是没办法呀,谁叫自己当初书读得少,长大了只能靠搬砖为生呢。正当你准备徒手一趟一趟地开始干时,丰满漂亮的工头,的老婆来了,她走过来,带着迷一般的微笑。那种笑容,甜蜜优雅,仿佛春风拂过泸沽湖,秋雨浸润九寨沟,让你虎躯一震。她对你说,你开工地上的卡车把这些砖搬过去吧,2000块砖太多了,一趟一趟搬太累。
你瞬间来了精神,把砖搬到卡车上,油门一踩不带走一片云彩,一下就把2000块砖搬过去了。。。
............
“快起来,什么时候了还在睡,快去搬砖!”
该死的工头又来催!

缓冲的概念就讲到这里。

Android绘图中的双缓冲

我们知道,我们在绘图时有两样东西是少不了的,一个是Canvas(画布),一个是Paint(画笔)。Canvas提供画各种图形的方法,如画圆(drawCircle),画矩形(drawRect)等等,Paint用来设置画笔的样式,比如笔的粗细,颜色等。每个Canvas内部持有一个Bitmap对象的引用,画图的过程其实就是往这个Bitmap当中写入ARGB信息。
比如我们现在自定义一个View,在上面画一个矩形和一个圆:

@Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(rect,mPaint);
        canvas.drawCircle(cx,cy,100,mPaint);
    }

那么现在有一个问题,画矩形和画圆是两个独立的动作,会不会在drawRect执行完之后屏幕上马上就会显示出来一个矩形呢?
为了验证我们的猜想,我们在两个绘图动作中加一个sleep:

@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绘图当中的第一道缓冲,即显示缓冲区。

而所谓的双缓冲,在android绘图中其实就是再创建一个Canvas和对应的Bitmap,然后在onDraw方法里默认的Canvas通过drawBitmap画刚才new的那个bitmap从而实现双缓冲。用代码简单的表述是这样的:

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);
}

示意图:

双缓冲绘图的优缺点及适用场景

我们通过一个例子来说明。
实现这样一个功能,一个自定义View,每次点击的时候在点击处画一个圆。我们先不使用双缓冲来实现:

不用双缓冲的代码:

public class MyView extends View{

    private Paint mPaint;
    private List<Point> mPoints;

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.GREEN);
        setBackgroundColor(Color.WHITE);
        mPoints = new ArrayList<>();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                mPoints.add(new Point((int)event.getX(),(int)event.getY()));
                break;
            case MotionEvent.ACTION_UP:
                invalidate();
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (Point p : mPoints) {
            canvas.drawCircle(p.x,p.y,50,mPaint);
        }
    }

在实验之前,我们先打开开发者选项里的”GPU呈现模式分析“,设置为“在屏幕上显示为条形图”(不同的手机可能有略微的差异,我这里用的是google Nexus5)。

可以看到,当画的圆数目比较少时,GPU的负荷较低,但是出现一个逐步上升的趋势:

不使用双缓冲-少数据-GPU占用图

内存使用情况是这样的:


不使用双缓冲-少数据-内存占用图

当画的圆数目增加到比较大时,GPU负荷有点惨不妨睹了:


不使用双缓冲-大数据-GPU占用图

这时的内存使用情况:


不使用双缓冲-大数据-内存占用图

我们现在改用双缓冲来绘图,代码如下:

public class MyView extends View{

    private Paint mPaint;
    private Canvas mBufferCanvas;
    private Bitmap mBufferBitmap;

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.GREEN);
        setBackgroundColor(Color.WHITE);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                if (mBufferBitmap == null) {
                    mBufferBitmap = Bitmap.createBitmap(getWidth(),getHeight(), Bitmap.Config.ARGB_8888);
                    mBufferCanvas = new Canvas(mBufferBitmap);
                }
                mBufferCanvas.drawCircle((int)event.getX(),(int)event.getY(),50,mPaint);
                break;
            case MotionEvent.ACTION_UP:
                invalidate();
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBufferBitmap == null) {
            return;
        }
        canvas.drawBitmap(mBufferBitmap,0,0,null);
    }
}

使用双缓冲,在数量较小时的GPU使用情况是这样的:


使用双缓冲-少数据-GPU占用图

这时候的内存使用情况:


使用双缓冲-少数据-内存占用图

使用双缓冲,在数量非常大的时候,GPU使用情况是这样的:

使用双缓冲-大数据-GPU占用图

内存使用情况:


使用双缓冲-大数据-内存占用图

从上面的实验数据我们可以得出结论:

  • 在绘制数据量较小时,不使用双缓冲,GPU的负荷更低,即绘制性能更高;
  • 在绘制数据量较大时,使用双缓冲绘图,绘制性能明显高于不使用双缓冲的情况;
  • 使用双缓冲会增加内存消耗。

其实上面的结论也很好理解,就像上面举的搬砖的例子,如果砖少的话,用车来拉明显是划不来的,砖的数量很多的时候,用车来拉就可以节省很多时间,但是用车就要消耗额外的资源,这就需要根据不同的情况做出正确的选择。

android的双缓冲绘图技术就讲到这里,有不对的地方或大家有什么问题欢迎留言。


android实现画板功能源码

转载请说明出处:http://www.jianshu.com/p/efc0bebfd22e

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容