android 使用雙緩衝辨析及surfaceview使用例程-轉載(以爲寫的很清楚)

本文轉自:http://blog.csdn.net/blogercn/article/details/7404485 java

感謝做者分享! android

      雙緩衝是圖像編程中很重要的概念,在電腦的圖像處理中就經常使用雙緩衝來加快圖像顯示速度,消除圖像刷新時的閃爍現象,提高用戶體驗。雙緩衝爲圖像加速,提高顯示速度,提升顯示質量的原理是:計算機訪問顯示屏和磁盤的速度遠遠小於CPU緩存和內存,每一次調用繪圖函數往顯示屏刷新數據,即便你的顯示內容已經加載到了內存,但每一次訪問顯示屏,仍然會花費比內存大得多的時間,若是你的資源裏有一百個圖片,那麼直接把他們全刷到屏幕上須要調用一百次drawBitmap方法,每次都須要訪問顯示屏,就會致使顯示過程效率低下。這時若是使用雙緩衝,建立一塊屏幕客戶區大小的BUFFER,把這一百個圖片全畫到BUFFER上,只就須要訪問一百次內存,最後訪問一次顯示屏,把這塊BUFFER刷到顯示屏上就好了,若是內存與外設的速度比是一比十,那麼幾乎能夠節約十分之九的時間,速度固然快了,這就是雙緩衝技術。 編程

那麼安卓編程是否須要雙緩衝?安卓surfaceview和雙緩衝有什麼關係? canvas

下面經過三個例子說明,三個例子內容同樣,都是一個背景圖片,5*8個不停放縮的實心圓: 緩存

一、不使用雙緩衝,直接使用onDraw往屏幕刷數據。代碼以下: app

package com.Double;

import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.View;

public class TestDoubleActivity extends Activity {
    MyView mv;
    float m_circle_r = 10;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        mv = new MyView(this);
        setContentView(mv);
        
        Timer timer = new Timer();  
        timer.scheduleAtFixedRate(new MyTask(), 1, 100);  
    }
    public class MyView extends View {
        MyView(Context context) {
            super(context);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            // TODO Auto-generated method stub
            super.onDraw(canvas);
            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            paint.setColor(Color.BLUE);
            paint.setStrokeWidth(10);
            paint.setStyle(Style.FILL);
            if (m_circle_r >= (getWidth()/10))
            {
                m_circle_r = 0;
            }
            else
            {
                m_circle_r++;
            }
            Bitmap pic = ((BitmapDrawable) getResources().getDrawable(R.drawable.qq)).getBitmap();
            canvas.drawBitmap(pic, 0, 0, paint); 
            for (int i = 0; i < 5; i++)
                for (int j = 0; j < 8; j++)
            canvas.drawCircle((getWidth()/5)*i+(getWidth()/10), (getHeight()/8)*j+(getHeight()/16), m_circle_r, 

paint);
        }

    }
    private class MyTask extends TimerTask{  
        @Override  
        public void run() {  
            mv.postInvalidate();
        }     
    }  
}

二、使用雙緩衝,一樣的代碼,建立一塊和屏幕大小同樣的Bitmap,把須要畫的內存提早畫在一個tmp的Bitmap上,最後把tmp刷到屏上,代碼以下: ide

package com.myDouble;

import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

import com.myDouble.MySurfaceView;

public class DoubleActivity extends Activity {
	MyView mv;
	float m_circle_r = 10;
	Bitmap tmp;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// setContentView(R.layout.main);
	
		mv = new MyView(this);
		setContentView(mv);

		Timer timer = new Timer();
		timer.scheduleAtFixedRate(new MyTask(), 1, 100);
		
	}

	public class MyView extends View {
		MyView(Context context) {
			super(context);
		}

		@Override
		protected void onDraw(Canvas canvas) {
			// TODO Auto-generated method stub
			super.onDraw(canvas);
			Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
			paint.setColor(Color.BLUE);
			paint.setStrokeWidth(10);
			paint.setStyle(Style.FILL);
			if (m_circle_r >= (getWidth() / 10)) {
				m_circle_r = 0;
			} else {
				m_circle_r++;
			}
			/* 建立Canvas對象 */
			Canvas mCanvas = new Canvas();
			/* 建立屏幕大小的緩衝區 tmp*/
			tmp = Bitmap
					.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
			/* 設置將內容繪製在tmp上 */
			mCanvas.setBitmap(tmp);
            //pic 在tmp上
			Bitmap pic = ((BitmapDrawable) getResources().getDrawable(
					R.drawable.qq)).getBitmap();
			mCanvas.drawBitmap(pic, 0, 0, paint);
			//把5*8個圓繪製在tmp上
			for (int i = 0; i < 5; i++)
				for (int j = 0; j < 8; j++)
					mCanvas.drawCircle(
							(getWidth() / 5) * i + (getWidth() / 10),
							(getHeight() / 8) * j + (getHeight() / 16),
							m_circle_r, paint);
			//把tmp繪製在物理設備上
			canvas.drawBitmap(tmp, 0, 0, paint);
		}
	}

	private class MyTask extends TimerTask {
		@Override
		public void run() {
			mv.postInvalidate();
		}
	}
}

      兩個例子對比後發現,若是這些圖形使用VC編寫,不使用雙緩衝動畫會閃得很厲害,使用雙緩衝動畫就會很流暢。而在安卓上,不使用雙緩衝圖像也很順暢,而使用了雙緩衝後,動畫反而比不使用更卡,根據結果的推測,應該是安卓平臺onDraw已經通過了雙緩衝優化,而再次使用雙緩衝時,反而不單單增大了系統內存開銷,並且比原來多使用了一次圖像拷貝操做,是而效率反而低了很多。再看下面使用surface的例子: 函數

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.BitmapDrawable;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder.Callback;

public class MySurfaceView extends SurfaceView implements Runnable, Callback {
	private SurfaceHolder mHolder; // 用於控制SurfaceView
	private Thread t; // 聲明一條線程
	private volatile boolean flag; // 線程運行的標識,用於控制線程
	private Canvas mCanvas; // 聲明一張畫布
	private Paint p; // 聲明一支畫筆
	float m_circle_r = 10;

	public MySurfaceView(Context context) {
		super(context);

		mHolder = getHolder(); // 得到SurfaceHolder對象
		mHolder.addCallback(this); // 爲SurfaceView添加狀態監聽
		p = new Paint(); // 建立一個畫筆對象
		p.setColor(Color.WHITE); // 設置畫筆的顏色爲白色
		setFocusable(true); // 設置焦點
	}

	/**
	 * 當SurfaceView建立的時候,調用此函數
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		t = new Thread(this); // 建立一個線程對象
		flag = true; // 把線程運行的標識設置成true
		t.start(); // 啓動線程
	}

	/**
	 * 當SurfaceView的視圖發生改變的時候,調用此函數
	 */
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	/**
	 * 當SurfaceView銷燬的時候,調用此函數
	 */
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		flag = false; // 把線程運行的標識設置成false
		mHolder.removeCallback(this);
	}

	/**
	 * 當屏幕被觸摸時調用
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {

		return true;
	}

	/**
	 * 當用戶按鍵時調用
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
		}
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		surfaceDestroyed(mHolder);
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public void run() {
		while (flag) {
			try {
				synchronized (mHolder) {
					Thread.sleep(100); // 讓線程休息100毫秒
					Draw(); // 調用自定義畫畫方法
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				if (mCanvas != null) {
					// mHolder.unlockCanvasAndPost(mCanvas);//結束鎖定畫圖,並提交改變。

				}
			}
		}
	}

	/**
	 * 自定義一個方法,在畫布上畫一個圓
	 */
	protected void Draw() {
		mCanvas = mHolder.lockCanvas(); // 得到畫布對象,開始對畫布畫畫
		if (mCanvas != null) {
			Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
			paint.setColor(Color.BLUE);
			paint.setStrokeWidth(10);
			paint.setStyle(Style.FILL);
			if (m_circle_r >= (getWidth() / 10)) {
				m_circle_r = 0;
			} else {
				m_circle_r++;
			}
			Bitmap pic = ((BitmapDrawable) getResources().getDrawable(
					R.drawable.qq)).getBitmap();
			mCanvas.drawBitmap(pic, 0, 0, paint);
			for (int i = 0; i < 5; i++)
				for (int j = 0; j < 8; j++)
					mCanvas.drawCircle(
							(getWidth() / 5) * i + (getWidth() / 10),
							(getHeight() / 8) * j + (getHeight() / 16),
							m_circle_r, paint);
			mHolder.unlockCanvasAndPost(mCanvas); // 完成畫畫,把畫布顯示在屏幕上
		}
	}
}
      這個例子咱們把繪圖工做放到了線程t裏,在線程t裏調用了咱們本身的非重載函數Draw。通常咱們都會使用這種方法更新用戶UI。由於若是把UI放在系統線程,若是遇到阻塞超過5S,軟件就會被系統幹掉。因此,咱們在安卓系統上,不須要再考慮雙緩衝,系統已經實現了雙緩衝,再次調用,反而會致使效率更進一步被壓縮。若是確實須要提高顯示效果,就使用surfaceview吧,若是仍然不行,就試着使用C,C++來實現這些複雜處理吧。程序效果圖以下:

相關文章
相關標籤/搜索