Android 的 SurfaceView 双缓冲应用
Android 的 SurfaceView 双缓冲应用
双缓冲是为了防止动画闪烁而实现的一种多线程应用,基于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 |
} |