双缓冲是为了防止动画闪烁而实现的一种多线程应用,基于SurfaceView的双缓冲实现很简单,开一条线程并在其中绘图即可。本文介绍基于SurfaceView的双缓冲实现,以及介绍类似的更高效的实现方法。
本文程序运行截图如下,左边是开单个线程读取并绘图,右边是开两个线程,一个专门读取图片,一个专门绘图:
对比一下,右边动画的帧速明显比左边的快,左右两者都没使用Thread.sleep()。为什么要开两个线程一个读一个画,而不去开两个线程像左边那样都 “边读边画”呢?因为SurfaceView每次绘图都会锁定Canvas,也就是说同一片区域这次没画完下次就不能画,因此要提高双缓冲的效率,就得开一条线程专门画图,开另外一条线程做预处理的工作。
本文程序运行截图如下,左边是开单个线程读取并绘图,右边是开两个线程,一个专门读取图片,一个专门绘图:
对比一下,右边动画的帧速明显比左边的快,左右两者都没使用Thread.sleep()。为什么要开两个线程一个读一个画,而不去开两个线程像左边那样都 “边读边画”呢?因为SurfaceView每次绘图都会锁定Canvas,也就是说同一片区域这次没画完下次就不能画,因此要提高双缓冲的效率,就得开一条线程专门画图,开另外一条线程做预处理的工作。
代码片段(3)
[图片] 程序运行截图
[代码] 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">
|
05 |
|
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>
|
12 |
</LinearLayout>
|
13 |
<SurfaceView android:id="@+id/SurfaceView01"
|
14 |
android:layout_width="fill_parent" android:layout_height="fill_parent"></SurfaceView>
|
15 |
</LinearLayout>
|
[代码] TestSurfaceView.java
001 |
package com.testSurfaceView;
|
002 |
|
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;
|
017 |
|
018 |
public class TestSurfaceView extends Activity {
|
019 |
/** Called when the activity is first created. */
|
020 |
Button btnSingleThread, btnDoubleThread;
|
021 |
SurfaceView sfv;
|
022 |
SurfaceHolder sfh;
|
023 |
ArrayList<Integer> imgList = new ArrayList<Integer>();
|
024 |
int imgWidth, imgHeight;
|
025 |
Bitmap bitmap;//独立线程读取,独立线程绘图
|
026 |
|
027 |
@Override
|
028 |
public void onCreate(Bundle savedInstanceState) {
|
029 |
super.onCreate(savedInstanceState);
|
030 |
setContentView(R.layout.main);
|
031 |
|
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());// 自动运行surfaceCreated以及surfaceChanged
|
039 |
}
|
040 |
|
041 |
class ClickEvent implements View.OnClickListener {
|
042 |
|
043 |
@Override
|
044 |
public void onClick(View v) {
|
045 |
|
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();//开一条线程绘图
|
051 |
}
|
052 |
|
053 |
}
|
054 |
|
055 |
}
|
056 |
|
057 |
class MyCallBack implements SurfaceHolder.Callback {
|
058 |
|
059 |
@Override
|
060 |
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
061 |
int height) {
|
062 |
Log.i("Surface:", "Change");
|
063 |
|
064 |
}
|
065 |
|
066 |
@Override
|
067 |
public void surfaceCreated(SurfaceHolder holder) {
|
068 |
Log.i("Surface:", "Create");
|
069 |
|
070 |
// 用反射机制来获取资源中的图片ID和尺寸
|
071 |
Field[] fields = R.drawable.class.getDeclaredFields();
|
072 |
for (Field field : fields) {
|
073 |
if (!"icon".equals(field.getName()))// 除了icon之外的图片
|
074 |
{
|
075 |
int index = 0;
|
076 |
try {
|
077 |
index = field.getInt(R.drawable.class);
|
078 |
} catch (IllegalArgumentException e) {
|
079 |
// TODO Auto-generated catch block
|
080 |
e.printStackTrace();
|
081 |
} catch (IllegalAccessException e) {
|
082 |
// TODO Auto-generated catch block
|
083 |
e.printStackTrace();
|
084 |
}
|
085 |
// 保存图片ID
|
086 |
imgList.add(index);
|
087 |
}
|
088 |
}
|
089 |
// 取得图像大小
|
090 |
Bitmap bmImg = BitmapFactory.decodeResource(getResources(),
|
091 |
imgList.get(0));
|
092 |
imgWidth = bmImg.getWidth();
|
093 |
imgHeight = bmImg.getHeight();
|
094 |
}
|
095 |
|
096 |
@Override
|
097 |
public void surfaceDestroyed(SurfaceHolder holder) {
|
098 |
Log.i("Surface:", "Destroy");
|
099 |
|
100 |
}
|
101 |
|
102 |
}
|
103 |
|
104 |
/**
|
105 |
* 读取并显示图片的线程
|
106 |
*/
|
107 |
class Load_DrawImage extends Thread {
|
108 |
int x, y;
|
109 |
int imgIndex = 0;
|
110 |
|
111 |
public Load_DrawImage(int x, int y) {
|
112 |
this.x = x;
|
113 |
this.y = y;
|
114 |
}
|
115 |
|
116 |
public void run() {
|
117 |
while (true) {
|
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());
|
123 |
imgIndex++;
|
124 |
if (imgIndex == imgList.size())
|
125 |
imgIndex = 0;
|
126 |
|
127 |
sfh.unlockCanvasAndPost(c);// 更新屏幕显示内容
|
128 |
}
|
129 |
}
|
130 |
};
|
131 |
|
132 |
/**
|
133 |
* 只负责绘图的线程
|
134 |
*/
|
135 |
class DrawImage extends Thread {
|
136 |
int x, y;
|
137 |
|
138 |
public DrawImage(int x, int y) {
|
139 |
this.x = x;
|
140 |
this.y = y;
|
141 |
}
|
142 |
|
143 |
public void run() {
|
144 |
while (true) {
|
145 |
if (bitmap != null) {//如果图像有效
|
146 |
Canvas c = sfh.lockCanvas(new Rect(this.x, this.y, this.x
|
147 |
+ imgWidth, this.y + imgHeight));
|
148 |
|
149 |
c.drawBitmap(bitmap, this.x, this.y, new Paint());
|
150 |
|
151 |
sfh.unlockCanvasAndPost(c);// 更新屏幕显示内容
|
152 |
}
|
153 |
}
|
154 |
}
|
155 |
};
|
156 |
|
157 |
/**
|
158 |
* 只负责读取图片的线程
|
159 |
*/
|
160 |
class LoadImage extends Thread {
|
161 |
int imgIndex = 0;
|
162 |
|
163 |
public void run() {
|
164 |
while (true) {
|
165 |
bitmap = BitmapFactory.decodeResource(getResources(),
|
166 |
imgList.get(imgIndex));
|
167 |
imgIndex++;
|
168 |
if (imgIndex == imgList.size())//如果到尽头则重新读取
|
169 |
imgIndex = 0;
|
170 |
}
|
171 |
}
|
172 |
};
|
173 |
} |