研究了android从网络上异步加载图像:
(1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法。
在主线程中new 一个Handler对象,加载图像方法如下所示
1
2
3
4
5
6
7
8
9
10
11
12
|
private
void
loadImage(
final
String url,
final
int
id) {
handler.post(
new
Runnable() {
public
void
run() {
Drawable drawable =
null
;
try
{
drawable = Drawable.createFromStream(
new
URL(url).openStream(),
"image.png"
);
}
catch
(IOException e) {
}
((ImageView) LazyLoadImageActivity.
this
.findViewById(id)).setImageDrawable(drawable);
}
});
}
|
上面这个方法缺点很显然,经测试,如果要加载多个图片,这并不能实现异步加载,而是等到所有的图片都加载完才一起显示,因为它们都运行在一个线程中。
然后,我们可以简单改进下,将Handler+Runnable模式改为Handler+Thread+Message模式不就能实现同时开启多个线程吗?
(2)在主线程中new 一个Handler对象,代码如下:
1
2
3
4
5
6
|
final
Handler handler2=
new
Handler(){
@Override
public
void
handleMessage(Message msg) {
((ImageView) LazyLoadImageActivity.
this
.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj);
}
};
|
对应加载图像代码如下:对应加载图像代码如下:对应加载图像代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 引入线程池来管理多线程
private
void
loadImage3(
final
String url,
final
int
id) {
executorService.submit(
new
Runnable() {
public
void
run() {
try
{
final
Drawable drawable = Drawable.createFromStream(
new
URL(url).openStream(),
"image.png"
);
handler.post(
new
Runnable() {
public
void
run() {
((ImageView) LazyLoadImageActivity.
this
.findViewById(id)).setImageDrawable(drawable);
}
});
}
catch
(Exception e) {
throw
new
RuntimeException(e);
}
}
});
}
|
(4)为了更方便使用我们可以将异步加载图像方法封装一个类,对外界只暴露一个方法即可,考虑到效率问题我们可以引入内存缓存机制,做法是
建立一个HashMap,其键(key)为加载图像url,其值(value)是图像对象Drawable。先看一下我们封装的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
public
class
AsyncImageLoader3 {
//为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
public
Map<String, SoftReference<Drawable>> imageCache =
new
HashMap<String, SoftReference<Drawable>>();
private
ExecutorService executorService = Executors.newFixedThreadPool(
5
);
//固定五个线程来执行任务
private
final
Handler handler=
new
Handler();
/**
*
* @param imageUrl 图像url地址
* @param callback 回调接口
* @return 返回内存中缓存的图像,第一次加载返回null
*/
public
Drawable loadDrawable(
final
String imageUrl,
final
ImageCallback callback) {
//如果缓存过就从缓存中取出数据
if
(imageCache.containsKey(imageUrl)) {
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
if
(softReference.get() !=
null
) {
return
softReference.get();
}
}
//缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
executorService.submit(
new
Runnable() {
public
void
run() {
try
{
final
Drawable drawable = Drawable.createFromStream(
new
URL(imageUrl).openStream(),
"image.png"
);
imageCache.put(imageUrl,
new
SoftReference<Drawable>(drawable));
handler.post(
new
Runnable() {
public
void
run() {
callback.imageLoaded(drawable);
}
});
}
catch
(Exception e) {
throw
new
RuntimeException(e);
}
}
});
return
null
;
}
//从网络上取数据方法
protected
Drawable loadImageFromUrl(String imageUrl) {
try
{
return
Drawable.createFromStream(
new
URL(imageUrl).openStream(),
"image.png"
);
}
catch
(Exception e) {
throw
new
RuntimeException(e);
}
}
//对外界开放的回调接口
public
interface
ImageCallback {
//注意 此方法是用来设置目标对象的图像资源
public
void
imageLoaded(Drawable imageDrawable);
}
}
|
这样封装好后使用起来就方便多了。在主线程中首先要引入AsyncImageLoader3 对象,然后直接调用其loadDrawable方法即可,需要注意的是ImageCallback接口的imageLoaded方法是唯一可以把加载的图像设置到目标ImageView或其相关的组件上。
在主线程调用代码:
先实例化对象 private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();
调用异步加载方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
private
void
loadImage4(
final
String url,
final
int
id) {
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
Drawable cacheImage = asyncImageLoader.loadDrawable(url,
new
AsyncImageLoader.ImageCallback() {
//请参见实现:如果第一次加载url时下面方法会执行
public
void
imageLoaded(Drawable imageDrawable) {
((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
}
});
if
(cacheImage!=
null
){
((ImageView) findViewById(id)).setImageDrawable(cacheImage);
}
}
|
5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
public
class
AsyncImageLoader {
//为了加快速度,加入了缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
private
Map<String, SoftReference<Drawable>> imageCache =
new
HashMap<String, SoftReference<Drawable>>();
/**
*
* @param imageUrl 图像url地址
* @param callback 回调接口
* @return 返回内存中缓存的图像,第一次加载返回null
*/
public
Drawable loadDrawable(
final
String imageUrl,
final
ImageCallback callback) {
//如果缓存过就从缓存中取出数据
if
(imageCache.containsKey(imageUrl)) {
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
if
(softReference.get() !=
null
) {
return
softReference.get();
}
}
final
Handler handler =
new
Handler() {
@Override
public
void
handleMessage(Message msg) {
callback.imageLoaded((Drawable) msg.obj);
}
};
new
Thread() {
public
void
run() {
Drawable drawable = loadImageFromUrl(imageUrl);
imageCache.put(imageUrl,
new
SoftReference<Drawable>(drawable));
handler.sendMessage(handler.obtainMessage(
0
, drawable));
}
}.start();
/*
下面注释的这段代码是Handler的一种代替方法
*/
// new AsyncTask() {
// @Override
// protected Drawable doInBackground(Object... objects) {
// Drawable drawable = loadImageFromUrl(imageUrl);
// imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
// return drawable;
// }
//
// @Override
// protected void onPostExecute(Object o) {
// callback.imageLoaded((Drawable) o);
// }
// }.execute();
return
null
;
}
protected
Drawable loadImageFromUrl(String imageUrl) {
try
{
return
Drawable.createFromStream(
new
URL(imageUrl).openStream(),
"src"
);
}
catch
(Exception e) {
throw
new
RuntimeException(e);
}
}
//对外界开放的回调接口
public
interface
ImageCallback {
public
void
imageLoaded(Drawable imageDrawable);
}
}
|
至此,异步加载就介绍完了,下面给出的代码为测试用的完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
package
com.bshark.supertelphone.activity;
import
android.app.Activity;
import
android.graphics.drawable.Drawable;
import
android.os.Bundle;
import
android.os.Handler;
import
android.os.Message;
import
android.widget.ImageView;
import
com.bshark.supertelphone.R;
import
com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader;
import
com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader3;
import
java.io.IOException;
import
java.net.URL;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
public
class
LazyLoadImageActivity
extends
Activity {
final
Handler handler=
new
Handler();
final
Handler handler2=
new
Handler(){
@Override
public
void
handleMessage(Message msg) {
((ImageView) LazyLoadImageActivity.
this
.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj);
}
};
private
ExecutorService executorService = Executors.newFixedThreadPool(
5
);
//固定五个线程来执行任务
private
AsyncImageLoader asyncImageLoader =
new
AsyncImageLoader();
private
AsyncImageLoader3 asyncImageLoader3 =
new
AsyncImageLoader3();
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.main);
// loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);
// loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);
// loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);
// loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);
// loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);
loadImage2(
"http://www.chinatelecom.com.cn/images/logo_new.gif"
, R.id.image1);
loadImage2(
"http://www.baidu.com/img/baidu_logo.gif"
, R.id.image2);
loadImage2(
"http://cache.soso.com/30d/img/web/logo.gif"
, R.id.image3);
loadImage2(
"http://www.baidu.com/img/baidu_logo.gif"
, R.id.image4);
loadImage2(
"http://cache.soso.com/30d/img/web/logo.gif"
, R.id.image5);
// loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);
// loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);
// loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);
// loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);
// loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);
// loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);
// loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);
// loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);
// loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);
// loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);
// loadImage5("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);
// //为了测试缓存而模拟的网络延时
// SystemClock.sleep(2000);
// loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);
// SystemClock.sleep(2000);
// loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);
// SystemClock.sleep(2000);
// loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);
// SystemClock.sleep(2000);
// loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);
// SystemClock.sleep(2000);
// loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);
}
@Override
protected
void
onDestroy() {
executorService.shutdown();
super
.onDestroy();
}
//线程加载图像基本原理
private
void
loadImage(
final
String url,
final
int
id) {
handler.post(
new
Runnable() {
public
void
run() {
Drawable drawable =
null
;
try
{
drawable = Drawable.createFromStream(
new
URL(url).openStream(),
"image.png"
);
}
catch
(IOException e) {
}
((ImageView) LazyLoadImageActivity.
this
.findViewById(id)).setImageDrawable(drawable);
}
});
}
//采用handler+Thread模式实现多线程异步加载
private
void
loadImage2(
final
String url,
final
int
id) {
Thread thread =
new
Thread(){
@Override
public
void
run() {
Drawable drawable =
null
;
try
{
drawable = Drawable.createFromStream(
new
URL(url).openStream(),
"image.png"
);
}
catch
(IOException e) {
}
Message message= handler2.obtainMessage() ;
message.arg1 = id;
message.obj = drawable;
handler2.sendMessage(message);
}
};
thread.start();
thread =
null
;
}
// 引入线程池来管理多线程
private
void
loadImage3(
final
String url,
final
int
id) {
executorService.submit(
new
Runnable() {
public
void
run() {
try
{
final
Drawable drawable = Drawable.createFromStream(
new
URL(url).openStream(),
"image.png"
);
handler.post(
new
Runnable() {
public
void
run() {
((ImageView) LazyLoadImageActivity.
this
.findViewById(id)).setImageDrawable(drawable);
}
});
}
catch
(Exception e) {
throw
new
RuntimeException(e);
}
}
});
}
//引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
private
void
loadImage4(
final
String url,
final
int
id) {
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
Drawable cacheImage = asyncImageLoader.loadDrawable(url,
new
AsyncImageLoader.ImageCallback() {
//请参见实现:如果第一次加载url时下面方法会执行
public
void
imageLoaded(Drawable imageDrawable) {
((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
}
});
if
(cacheImage!=
null
){
((ImageView) findViewById(id)).setImageDrawable(cacheImage);
}
}
//采用Handler+Thread+封装外部接口
private
void
loadImage5(
final
String url,
final
int
id) {
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
Drawable cacheImage = asyncImageLoader3.loadDrawable(url,
new
AsyncImageLoader3.ImageCallback() {
//请参见实现:如果第一次加载url时下面方法会执行
public
void
imageLoaded(Drawable imageDrawable) {
((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
}
});
if
(cacheImage!=
null
){
((ImageView) findViewById(id)).setImageDrawable(cacheImage);
}
}
}
|
xml文件大致如下:
1
2
3
4
5
6
7
8
9
10
11
|