1. 程式人生 > >android繪製知識--雙緩衝繪圖技術

android繪製知識--雙緩衝繪圖技術

今天我們說下雙緩衝繪圖技術。其實也沒有那麼神祕,可以理解為就是一個裝載工具,說點接地氣的比如搬磚。我們每次可以搬十塊磚,從A搬到B,如果是就搬十塊我們當然是直接搬過去比較快,但是如果是一百或者一千塊呢?你直接搬效率肯定很低,於是我們用身邊的小推車一直裝上然後推過去。我們的雙緩衝技術就是這個“小推車”。

我們知道,我們在繪圖時有兩樣東西是少不了的,一個是Canvas(畫布),一個是Paint(畫筆)。Canvas提供畫各種圖形的方法,如畫圓(drawCircle),畫矩形(drawRect)等等,Paint用來設定畫筆的樣式,比如筆的粗細,顏色等。每個Canvas內部持有一個Bitmap物件的引用,畫圖的過程其實就是往這個Bitmap當中寫入ARGB資訊。

那我們現在考慮一件事情是不是我們呼叫drawCircle等方法就能立刻畫出來了呢
這裡我們寫個程式碼測試下

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

上面的程式碼執行顯示,矩形和圓同時出來而不是先出矩形然後再出現圓形,這也就說明了並不是我們呼叫drawCircle等方法就能立刻繪製出來的。這就說明必須要等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); }

示意圖如下:
這裡寫圖片描述

這樣你可以清晰的看到我們如果只是單純的換一個圖的話簡直是畫蛇添足,但是我們如果是做畫板或者是動態的製造圖形的話我們這樣就很有必要了就不用每次在ondraw重複的呼叫繪製方法,這樣說有點抽象我給出個點擊出現圓形的栗子看看就比較清楚了

沒用雙緩衝的時候的程式碼

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

用了雙緩衝

public class NewMyView extends View {
    private Paint mPaint;
    private Canvas mBufferCanvas;
    private Bitmap mBufferBitmap;
    public NewMyView(Context context) {
        super(context);
    }

    public NewMyView(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);
    }
}

咱們對比下,ondraw

 @Override
    protected void onDraw(Canvas canvas) {
        for (Point p : mPoints) {
            canvas.drawCircle(p.x,p.y,50,mPaint);
        }
    }
//使用雙緩衝的
    @Override
    protected void onDraw(Canvas canvas) {
        if (mBufferBitmap == null) {
            return;
        }
        canvas.drawBitmap(mBufferBitmap,0,0,null);
    }

沒用雙緩衝的每次我們點選螢幕繪製圓形的時候會在onDraw()迴圈呼叫繪製就好像一塊一塊的搬磚毫無效率,而我們的雙緩衝呢只需繪製一次,其他的繪製都繪製在了mBufferBitmap 就好像裝車一樣

從上面我們可以得出結論:

  • 在繪製資料量較小時,不使用雙緩衝,GPU的負荷更低,即繪製效能更高;
  • 在繪製資料量較大時,使用雙緩衝繪圖,繪製效能明顯高於不使用雙緩衝的情況;
  • 使用雙緩衝會增加記憶體消耗。

其實上面的結論也很好理解,就像上面舉的搬磚的例子,如果磚少的話,用車來拉明顯是划不來的,磚的數量很多的時候,用車來拉就可以節省很多時間,但是用車就要消耗額外的資源,這就需要根據不同的情況做出正確的選擇。