android从网络上异步加载

来源:互联网 发布:淘宝助手上传宝贝很慢 编辑:程序博客网 时间:2024/05/22 10:25
研究了android从网络上异步加载图像,现总结如下:

(1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法。
在主线程中new 一个Handler对象,加载图像方法如下所示

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
privatevoidloadImage(finalString url, finalintid) {
handler.post(newRunnable() {
publicvoidrun() {
Drawable drawable = null;
try{
drawable = Drawable.createFromStream(newURL(url).openStream(),"image.png");
}catch(IOException e) {
}
((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);
}
});
}


上面这个方法缺点很显然,经测试,如果要加载多个图片,这并不能实现异步加载,而是等到所有的图片都加载完才一起显示,因为它们都运行在一个线程中。
然后,我们可以简单改进下,将Handler+Runnable模式改为Handler+Thread+Message模式不就能实现同时开启多个线程吗?

(2)在主线程中new 一个Handler对象,代码如下:
?
代码片段,双击复制
01
02
03
04
05
06
finalHandler handler2=newHandler(){
@Override
publicvoidhandleMessage(Message msg) {
((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj);
}
};


对应加载图像代码如下:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
//采用handler+Thread模式实现多线程异步加载
privatevoidloadImage2(finalString url, finalintid) {
Thread thread = newThread(){
@Override
publicvoidrun() {
Drawable drawable = null;
try{
drawable = Drawable.createFromStream(newURL(url).openStream(),"image.png");
}catch(IOException e) {
}
 
Message message= handler2.obtainMessage() ;
message.arg1 = id;
message.obj = drawable;
handler2.sendMessage(message);
}
};
thread.start();
thread = null;
}


这样就简单实现了异步加载了。细想一下,还可以优化的,比如引入线程池、引入缓存等,我们先介绍线程池。

(3)引入ExecutorService接口,于是代码可以优化如下:

在主线程中加入:private ExecutorService executorService = Executors.newFixedThreadPool(5); 
对应加载图像方法更改如下:

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// 引入线程池来管理多线程
privatevoidloadImage3(finalString url, finalintid) {
executorService.submit(newRunnable() {
publicvoidrun() {
try{
finalDrawable drawable = Drawable.createFromStream(newURL(url).openStream(),"image.png");
handler.post(newRunnable() {
 
publicvoidrun() {
((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);
}
});
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
});
}


4)为了更方便使用我们可以将异步加载图像方法封装一个类,对外界只暴露一个方法即可,考虑到效率问题我们可以引入内存缓存机制,做法是

建立一个HashMap,其键(key)为加载图像url,其值(value)是图像对象Drawable。先看一下我们封装的类
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
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
publicclassAsyncImageLoader3 {
//为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
publicMap<String, SoftReference<Drawable>> imageCache = newHashMap<String, SoftReference<Drawable>>();
privateExecutorService executorService = Executors.newFixedThreadPool(5);//固定五个线程来执行任务
privatefinalHandler handler=newHandler();
 
/**
*
* @param imageUrl 图像url地址
* @param callback 回调接口
* <a href="\"http://www.eoeandroid.com/home.php?mod=space&uid=7300\"" target="\"_blank\"">@return</a> 返回内存中缓存的图像,第一次加载返回null
*/
publicDrawable loadDrawable(finalString imageUrl, finalImageCallback callback) {
//如果缓存过就从缓存中取出数据
if(imageCache.containsKey(imageUrl)) {
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
if(softReference.get() != null) {
returnsoftReference.get();
}
}
//缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
executorService.submit(newRunnable() {
publicvoidrun() {
try{
finalDrawable drawable = Drawable.createFromStream(newURL(imageUrl).openStream(),"image.png");
 
imageCache.put(imageUrl,newSoftReference<Drawable>(drawable));
 
handler.post(newRunnable() {
publicvoidrun() {
callback.imageLoaded(drawable);
}
});
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
});
returnnull;
}
//从网络上取数据方法
protectedDrawable loadImageFromUrl(String imageUrl) {
try{
returnDrawable.createFromStream(newURL(imageUrl).openStream(),"image.png");
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
//对外界开放的回调接口
publicinterfaceImageCallback {
//注意 此方法是用来设置目标对象的图像资源
publicvoidimageLoaded(Drawable imageDrawable);
}
}


这样封装好后使用起来就方便多了。在主线程中首先要引入AsyncImageLoader3 对象,然后直接调用其loadDrawable方法即可,需要注意的是ImageCallback接口的imageLoaded方法是唯一可以把加载的图 像设置到目标ImageView或其相关的组件上。

在主线程调用代码:
先实例化对象 private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();

调用异步加载方法:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
//引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
privatevoidloadImage4(finalString url, finalintid) {
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
Drawable cacheImage = asyncImageLoader.loadDrawable(url,newAsyncImageLoader.ImageCallback() {
//请参见实现:如果第一次加载url时下面方法会执行
publicvoidimageLoaded(Drawable imageDrawable) {
((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
}
});
if(cacheImage!=null){
((ImageView) findViewById(id)).setImageDrawable(cacheImage);
}
}


5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
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
publicclassAsyncImageLoader {
//为了加快速度,加入了缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
privateMap<String, SoftReference<Drawable>> imageCache = newHashMap<String, SoftReference<Drawable>>();
 
/**
*
* @param imageUrl 图像url地址
* @param callback 回调接口
* @return 返回内存中缓存的图像,第一次加载返回null
*/
publicDrawable loadDrawable(finalString imageUrl, finalImageCallback callback) {
//如果缓存过就从缓存中取出数据
if(imageCache.containsKey(imageUrl)) {
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
if(softReference.get() != null) {
returnsoftReference.get();
}
}
 
finalHandler handler = newHandler() {
@Override
publicvoidhandleMessage(Message msg) {
callback.imageLoaded((Drawable) msg.obj);
}
};
newThread() {
publicvoidrun() {
Drawable drawable = loadImageFromUrl(imageUrl);
imageCache.put(imageUrl,newSoftReference<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();
returnnull;
}
 
protectedDrawable loadImageFromUrl(String imageUrl) {
try{
returnDrawable.createFromStream(newURL(imageUrl).openStream(),"src");
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
//对外界开放的回调接口
publicinterfaceImageCallback {
publicvoidimageLoaded(Drawable imageDrawable);
}
}


至此,异步加载就介绍完了,下面给出的代码为测试用的完整代码:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
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
packagecom.bshark.supertelphone.activity;
 
importandroid.app.Activity;
importandroid.graphics.drawable.Drawable;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.widget.ImageView;
importcom.bshark.supertelphone.R;
importcom.bshark.supertelphone.ui.adapter.util.AsyncImageLoader;
importcom.bshark.supertelphone.ui.adapter.util.AsyncImageLoader3;
 
importjava.io.IOException;
importjava.net.URL;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
 
publicclassLazyLoadImageActivity extendsActivity {
finalHandler handler=newHandler();
finalHandler handler2=newHandler(){
@Override
publicvoidhandleMessage(Message msg) {
((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj);
}
};
privateExecutorService executorService = Executors.newFixedThreadPool(5);//固定五个线程来执行任务
privateAsyncImageLoader asyncImageLoader = newAsyncImageLoader();
privateAsyncImageLoader3 asyncImageLoader3 = newAsyncImageLoader3();
 
@Override
publicvoidonCreate(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
protectedvoidonDestroy() {
executorService.shutdown();
super.onDestroy();
}
//线程加载图像基本原理
privatevoidloadImage(finalString url, finalintid) {
handler.post(newRunnable() {
publicvoidrun() {
Drawable drawable = null;
try{
drawable = Drawable.createFromStream(newURL(url).openStream(),"image.png");
}catch(IOException e) {
}
((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);
}
});
}
//采用handler+Thread模式实现多线程异步加载
privatevoidloadImage2(finalString url, finalintid) {
Thread thread = newThread(){
@Override
publicvoidrun() {
Drawable drawable = null;
try{
drawable = Drawable.createFromStream(newURL(url).openStream(),"image.png");
}catch(IOException e) {
}
 
Message message= handler2.obtainMessage() ;
message.arg1 = id;
message.obj = drawable;
handler2.sendMessage(message);
}
};
thread.start();
thread = null;
}
// 引入线程池来管理多线程
privatevoidloadImage3(finalString url, finalintid) {
executorService.submit(newRunnable() {
publicvoidrun() {
try{
finalDrawable drawable = Drawable.createFromStream(newURL(url).openStream(),"image.png");
handler.post(newRunnable() {
 
publicvoidrun() {
((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);
}
});
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
});
}
//引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
privatevoidloadImage4(finalString url, finalintid) {
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
Drawable cacheImage = asyncImageLoader.loadDrawable(url,newAsyncImageLoader.ImageCallback() {
//请参见实现:如果第一次加载url时下面方法会执行
publicvoidimageLoaded(Drawable imageDrawable) {
((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
}
});
if(cacheImage!=null){
((ImageView) findViewById(id)).setImageDrawable(cacheImage);
}
}
 
//采用Handler+Thread+封装外部接口
privatevoidloadImage5(finalString url, finalintid) {
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
Drawable cacheImage = asyncImageLoader3.loadDrawable(url,newAsyncImageLoader3.ImageCallback() {
//请参见实现:如果第一次加载url时下面方法会执行
publicvoidimageLoaded(Drawable imageDrawable) {
((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
}
});
if(cacheImage!=null){
((ImageView) findViewById(id)).setImageDrawable(cacheImage);
}
}
 
 
}


xml文件大致如下:

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
< ?xml version="1.0"encoding="utf-8"?>
 
< LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:orientation="vertical"
android:layout_height="fill_parent">
<ImageView android:id="@+id/image1"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
<ImageView android:id="@+id/image2"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
<ImageView android:id="@+id/image3"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
<ImageView android:id="@+id/image5"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
<ImageView android:id="@+id/image4"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
< /LinearLayout>