双缓冲是为了防止动画闪烁而实现的一种多线程应用,基于SurfaceView的双缓冲实现很简单,开一条线程并在其中绘图即可。本文介绍基于SurfaceView的双缓冲实现,以及介绍类似的更高效的实现方法。
本文程序运行截图如下,左边是开单个线程读取并绘图,右边是开两个线程,一个专门读取图片,一个专门绘图:
对比一下,右边动画的帧速明显比左边的快,左右两者都没使用Thread.sleep()。为什么要开两个线程一个读一个画,而不去开两个线程像左边那样都 “边读边画”呢?因为SurfaceView每次绘图都会锁定Canvas,也就是说同一片区域这次没画完下次就不能画,因此要提高双缓冲的效率,就得开一条线程专门画图,开另外一条线程做预处理的工作。
[代码] main.xml
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
03 | android:layout_width = "fill_parent" android:layout_height = "fill_parent" |
04 | android:orientation = "vertical" > |
06 | < LinearLayout android:id = "@+id/LinearLayout01" |
07 | android:layout_width = "wrap_content" android:layout_height = "wrap_content" > |
08 | < Button android:id = "@+id/Button01" android:layout_width = "wrap_content" |
09 | android:layout_height = "wrap_content" android:text = "单个独立线程" ></ Button > |
10 | < Button android:id = "@+id/Button02" android:layout_width = "wrap_content" |
11 | android:layout_height = "wrap_content" android:text = "两个独立线程" ></ Button > |
13 | < SurfaceView android:id = "@+id/SurfaceView01" |
14 | android:layout_width = "fill_parent" android:layout_height = "fill_parent" ></ SurfaceView > |
[代码] TestSurfaceView.java
001 | package com.testSurfaceView; |
003 | import java.lang.reflect.Field; |
004 | import java.util.ArrayList; |
005 | import android.app.Activity; |
006 | import android.graphics.Bitmap; |
007 | import android.graphics.BitmapFactory; |
008 | import android.graphics.Canvas; |
009 | import android.graphics.Paint; |
010 | import android.graphics.Rect; |
011 | import android.os.Bundle; |
012 | import android.util.Log; |
013 | import android.view.SurfaceHolder; |
014 | import android.view.SurfaceView; |
015 | import android.view.View; |
016 | import android.widget.Button; |
018 | public class TestSurfaceView extends Activity { |
019 | /** Called when the activity is first created. */ |
020 | Button btnSingleThread, btnDoubleThread; |
023 | ArrayList<Integer> imgList = new ArrayList<Integer>(); |
024 | int imgWidth, imgHeight; |
028 | public void onCreate(Bundle savedInstanceState) { |
029 | super .onCreate(savedInstanceState); |
030 | setContentView(R.layout.main); |
032 | btnSingleThread = (Button) this .findViewById(R.id.Button01); |
033 | btnDoubleThread = (Button) this .findViewById(R.id.Button02); |
034 | btnSingleThread.setOnClickListener( new ClickEvent()); |
035 | btnDoubleThread.setOnClickListener( new ClickEvent()); |
036 | sfv = (SurfaceView) this .findViewById(R.id.SurfaceView01); |
037 | sfh = sfv.getHolder(); |
038 | sfh.addCallback( new MyCallBack()); |
041 | class ClickEvent implements View.OnClickListener { |
044 | public void onClick(View v) { |
046 | if (v == btnSingleThread) { |
047 | new Load_DrawImage( 0 , 0 ).start(); |
048 | } else if (v == btnDoubleThread) { |
049 | new LoadImage().start(); |
050 | new DrawImage(imgWidth + 10 , 0 ).start(); |
057 | class MyCallBack implements SurfaceHolder.Callback { |
060 | public void surfaceChanged(SurfaceHolder holder, int format, int width, |
062 | Log.i( "Surface:" , "Change" ); |
067 | public void surfaceCreated(SurfaceHolder holder) { |
068 | Log.i( "Surface:" , "Create" ); |
071 | Field[] fields = R.drawable. class .getDeclaredFields(); |
072 | for (Field field : fields) { |
073 | if (! "icon" .equals(field.getName())) |
077 | index = field.getInt(R.drawable. class ); |
078 | } catch (IllegalArgumentException e) { |
081 | } catch (IllegalAccessException e) { |
090 | Bitmap bmImg = BitmapFactory.decodeResource(getResources(), |
092 | imgWidth = bmImg.getWidth(); |
093 | imgHeight = bmImg.getHeight(); |
097 | public void surfaceDestroyed(SurfaceHolder holder) { |
098 | Log.i( "Surface:" , "Destroy" ); |
107 | class Load_DrawImage extends Thread { |
111 | public Load_DrawImage( int x, int y) { |
118 | Canvas c = sfh.lockCanvas( new Rect( this .x, this .y, this .x |
119 | + imgWidth, this .y + imgHeight)); |
120 | Bitmap bmImg = BitmapFactory.decodeResource(getResources(), |
121 | imgList.get(imgIndex)); |
122 | c.drawBitmap(bmImg, this .x, this .y, new Paint()); |
124 | if (imgIndex == imgList.size()) |
127 | sfh.unlockCanvasAndPost(c); |
135 | class DrawImage extends Thread { |
138 | public DrawImage( int x, int y) { |
145 | if (bitmap != null ) { |
146 | Canvas c = sfh.lockCanvas( new Rect( this .x, this .y, this .x |
147 | + imgWidth, this .y + imgHeight)); |
149 | c.drawBitmap(bitmap, this .x, this .y, new Paint()); |
151 | sfh.unlockCanvasAndPost(c); |
160 | class LoadImage extends Thread { |
165 | bitmap = BitmapFactory.decodeResource(getResources(), |
166 | imgList.get(imgIndex)); |
168 | if (imgIndex == imgList.size()) |