研究了Android从网络上异步加载图像:
(1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法。
在主线程中new 一个Handler对象,加载图像方法如下所示
01 |
private void loadImage( final String url, final int id) { |
02 |
handler.post( new Runnable() { |
03 |
public void run() { |
04 |
Drawable drawable = null ; |
05 |
try { |
06 |
drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
07 |
} catch (IOException e) { |
08 |
} |
09 |
((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
10 |
} |
11 |
}); |
12 |
} |
上面这个方法缺点很显然,经测试,如果要加载多个图片,这并不能实现异步加载,而是等到所有的图片都加载完才一起显示,因为它们都运行在一个线程中。
然后,我们可以简单改进下,将Handler+Runnable模式改为Handler+Thread+Message模式不就能实现同时开启多个线程吗?
(2)在主线程中new 一个Handler对象,代码如下:
1 |
final Handler handler2= new Handler(){ |
2 |
@Override |
3 |
public void handleMessage(Message msg) { |
4 |
((ImageView) LazyLoadImageActivity. this .findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); |
5 |
} |
6 |
}; |
对应加载图像代码如下:对应加载图像代码如下:对应加载图像代码如下:
01 |
// 引入线程池来管理多线程 |
02 |
private void loadImage3( final String url, final int id) { |
03 |
executorService.submit( new Runnable() { |
04 |
public void run() { |
05 |
try { |
06 |
final Drawable drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
07 |
handler.post( new Runnable() { |
08 |
09 |
public void run() { |
10 |
((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
11 |
} |
12 |
}); |
13 |
} catch (Exception e) { |
14 |
throw new RuntimeException(e); |
15 |
} |
16 |
} |
17 |
}); |
18 |
} |
(4)为了更方便使用我们可以将异步加载图像方法封装一个类,对外界只暴露一个方法即可,考虑到效率问题我们可以引入内存缓存机制,做法是
建立一个HashMap,其键(key)为加载图像url,其值(value)是图像对象Drawable。先看一下我们封装的类
01 |
public class AsyncImageLoader3 { |
02 |
//为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动) |
03 |
public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); |
04 |
private ExecutorService executorService = Executors.newFixedThreadPool( 5 ); //固定五个线程来执行任务 |
05 |
private final Handler handler= new Handler(); |
06 |
|
07 |
/** |
08 |
* |
09 |
* @param imageUrl 图像url地址 |
10 |
* @param callback 回调接口 |
11 |
* @return 返回内存中缓存的图像,第一次加载返回null |
12 |
*/ |
13 |
public Drawable loadDrawable( final String imageUrl, final ImageCallback callback) { |
14 |
//如果缓存过就从缓存中取出数据 |
15 |
if (imageCache.containsKey(imageUrl)) { |
16 |
SoftReference<Drawable> softReference = imageCache.get(imageUrl); |
17 |
if (softReference.get() != null ) { |
18 |
return softReference.get(); |
19 |
} |
20 |
} |
21 |
//缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中 |
22 |
executorService.submit( new Runnable() { |
23 |
public void run() { |
24 |
try { |
25 |
final Drawable drawable = Drawable.createFromStream( new URL(imageUrl).openStream(), "image.png" ); |
26 |
|
27 |
imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); |
28 |
|
29 |
handler.post( new Runnable() { |
30 |
public void run() { |
31 |
callback.imageLoaded(drawable); |
32 |
} |
33 |
}); |
34 |
} catch (Exception e) { |
35 |
throw new RuntimeException(e); |
36 |
} |
37 |
} |
38 |
}); |
39 |
return null ; |
40 |
} |
41 |
//从网络上取数据方法 |
42 |
protected Drawable loadImageFromUrl(String imageUrl) { |
43 |
try { |
44 |
return Drawable.createFromStream( new URL(imageUrl).openStream(), "image.png" ); |
45 |
} catch (Exception e) { |
46 |
throw new RuntimeException(e); |
47 |
} |
48 |
} |
49 |
//对外界开放的回调接口 |
50 |
public interface ImageCallback { |
51 |
//注意 此方法是用来设置目标对象的图像资源 |
52 |
public void imageLoaded(Drawable imageDrawable); |
53 |
} |
54 |
} |
这样封装好后使用起来就方便多了。在主线程中首先要引入AsyncImageLoader3 对象,然后直接调用其loadDrawable方法即可,需要注意的是ImageCallback接口的imageLoaded方法是唯一可以把加载的图像设置到目标ImageView或其相关的组件上。
在主线程调用代码:
先实例化对象 private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();
调用异步加载方法:
01 |
//引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程 |
02 |
private void loadImage4( final String url, final int id) { |
03 |
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行 |
04 |
Drawable cacheImage = asyncImageLoader.loadDrawable(url, new AsyncImageLoader.ImageCallback() { |
05 |
//请参见实现:如果第一次加载url时下面方法会执行 |
06 |
public void imageLoaded(Drawable imageDrawable) { |
07 |
((ImageView) findViewById(id)).setImageDrawable(imageDrawable); |
08 |
} |
09 |
}); |
10 |
if (cacheImage!= null ){ |
11 |
((ImageView) findViewById(id)).setImageDrawable(cacheImage); |
12 |
} |
13 |
} |
5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:
01 |
public class AsyncImageLoader { |
02 |
//为了加快速度,加入了缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动) |
03 |
private Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); |
04 |
|
05 |
/** |
06 |
* |
07 |
* @param imageUrl 图像url地址 |
08 |
* @param callback 回调接口 |
09 |
* @return 返回内存中缓存的图像,第一次加载返回null |
10 |
*/ |
11 |
public Drawable loadDrawable( final String imageUrl, final ImageCallback callback) { |
12 |
//如果缓存过就从缓存中取出数据 |
13 |
if (imageCache.containsKey(imageUrl)) { |
14 |
SoftReference<Drawable> softReference = imageCache.get(imageUrl); |
15 |
if (softReference.get() != null ) { |
16 |
return softReference.get(); |
17 |
} |
18 |
} |
19 |
|
20 |
final Handler handler = new Handler() { |
21 |
@Override |
22 |
public void handleMessage(Message msg) { |
23 |
callback.imageLoaded((Drawable) msg.obj); |
24 |
} |
25 |
}; |
26 |
new Thread() { |
27 |
public void run() { |
28 |
Drawable drawable = loadImageFromUrl(imageUrl); |
29 |
imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); |
30 |
handler.sendMessage(handler.obtainMessage( 0 , drawable)); |
31 |
|
32 |
} |
33 |
|
34 |
}.start(); |
35 |
/* |
36 |
下面注释的这段代码是Handler的一种代替方法 |
37 |
*/ |
38 |
// new AsyncTask() { |
39 |
// @Override |
40 |
// protected Drawable doInBackground(Object... objects) { |
41 |
// Drawable drawable = loadImageFromUrl(imageUrl); |
42 |
// imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); |
43 |
// return drawable; |
44 |
// } |
45 |
// |
46 |
// @Override |
47 |
// protected void onPostExecute(Object o) { |
48 |
// callback.imageLoaded((Drawable) o); |
49 |
// } |
50 |
// }.execute(); |
51 |
return null ; |
52 |
} |
53 |
|
54 |
protected Drawable loadImageFromUrl(String imageUrl) { |
55 |
try { |
56 |
return Drawable.createFromStream( new URL(imageUrl).openStream(), "src" ); |
57 |
} catch (Exception e) { |
58 |
throw new RuntimeException(e); |
59 |
} |
60 |
} |
61 |
//对外界开放的回调接口 |
62 |
public interface ImageCallback { |
63 |
public void imageLoaded(Drawable imageDrawable); |
64 |
} |
65 |
} |
至此,异步加载就介绍完了,下面给出的代码为测试用的完整代码:
001 |
package com.bshark.supertelphone.activity; |
002 |
|
003 |
import android.app.Activity; |
004 |
import android.graphics.drawable.Drawable; |
005 |
import android.os.Bundle; |
006 |
import android.os.Handler; |
007 |
import android.os.Message; |
008 |
import android.widget.ImageView; |
009 |
import com.bshark.supertelphone.R; |
010 |
import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader; |
011 |
import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader3; |
012 |
|
013 |
import java.io.IOException; |
014 |
import java.net.URL; |
015 |
import java.util.concurrent.ExecutorService; |
016 |
import java.util.concurrent.Executors; |
017 |
|
018 |
public class LazyLoadImageActivity extends Activity { |
019 |
final Handler handler= new Handler(); |
020 |
final Handler handler2= new Handler(){ |
021 |
@Override |
022 |
public void handleMessage(Message msg) { |
023 |
((ImageView) LazyLoadImageActivity. this .findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); |
024 |
} |
025 |
}; |
026 |
private ExecutorService executorService = Executors.newFixedThreadPool( 5 ); //固定五个线程来执行任务 |
027 |
private AsyncImageLoader asyncImageLoader = new AsyncImageLoader(); |
028 |
private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3(); |
029 |
|
030 |
|
031 |
@Override |
032 |
public void onCreate(Bundle savedInstanceState) { |
033 |
super .onCreate(savedInstanceState); |
034 |
setContentView(R.layout.main); |
035 |
|
036 |
// loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); |
037 |
// loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); |
038 |
// loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); |
039 |
// loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); |
040 |
// loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); |
041 |
|
042 |
loadImage2( "http://www.chinatelecom.com.cn/images/logo_new.gif" , R.id.image1); |
043 |
loadImage2( "http://www.baidu.com/img/baidu_logo.gif" , R.id.image2); |
044 |
loadImage2( "http://cache.soso.com/30d/img/web/logo.gif" , R.id.image3); |
045 |
loadImage2( "http://www.baidu.com/img/baidu_logo.gif" , R.id.image4); |
046 |
loadImage2( "http://cache.soso.com/30d/img/web/logo.gif" , R.id.image5); |
047 |
// loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); |
048 |
// loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); |
049 |
// loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); |
050 |
// loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); |
051 |
// loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); |
052 |
|
053 |
// loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); |
054 |
// loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); |
055 |
// loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); |
056 |
// loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); |
057 |
// loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); |
058 |
|
059 |
// loadImage5("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); |
060 |
// //为了测试缓存而模拟的网络延时 |
061 |
// SystemClock.sleep(2000); |
062 |
// loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); |
063 |
// SystemClock.sleep(2000); |
064 |
// loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); |
065 |
// SystemClock.sleep(2000); |
066 |
// loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); |
067 |
// SystemClock.sleep(2000); |
068 |
// loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); |
069 |
// SystemClock.sleep(2000); |
070 |
// loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); |
071 |
} |
072 |
|
073 |
@Override |
074 |
protected void onDestroy() { |
075 |
executorService.shutdown(); |
076 |
super .onDestroy(); |
077 |
} |
078 |
//线程加载图像基本原理 |
079 |
private void loadImage( final String url, final int id) { |
080 |
handler.post( new Runnable() { |
081 |
public void run() { |
082 |
Drawable drawable = null ; |
083 |
try { |
084 |
drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
085 |
} catch (IOException e) { |
086 |
} |
087 |
((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
088 |
} |
089 |
}); |
090 |
} |
091 |
//采用handler+Thread模式实现多线程异步加载 |
092 |
private void loadImage2( final String url, final int id) { |
093 |
Thread thread = new Thread(){ |
094 |
@Override |
095 |
public void run() { |
096 |
Drawable drawable = null ; |
097 |
try { |
098 |
drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
099 |
} catch (IOException e) { |
100 |
} |
101 |
|
102 |
Message message= handler2.obtainMessage() ; |
103 |
message.arg1 = id; |
104 |
message.obj = drawable; |
105 |
handler2.sendMessage(message); |
106 |
} |
107 |
}; |
108 |
thread.start(); |
109 |
thread = null ; |
110 |
} |
111 |
// 引入线程池来管理多线程 |
112 |
private void loadImage3( final String url, final int id) { |
113 |
executorService.submit( new Runnable() { |
114 |
public void run() { |
115 |
try { |
116 |
final Drawable drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
117 |
handler.post( new Runnable() { |
118 |
|
119 |
public void run() { |
120 |
((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
121 |
} |
122 |
}); |
123 |
} catch (Exception e) { |
124 |
throw new RuntimeException(e); |
125 |
} |
126 |
} |
127 |
}); |
128 |
} |
129 |
//引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程 |
130 |
private void loadImage4( final String url, final int id) { |
131 |
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行 |
132 |
Drawable cacheImage = asyncImageLoader.loadDrawable(url, new AsyncImageLoader.ImageCallback() { |
133 |
//请参见实现:如果第一次加载url时下面方法会执行 |
134 |
public void imageLoaded(Drawable imageDrawable) { |
135 |
((ImageView) findViewById(id)).setImageDrawable(imageDrawable); |
136 |
} |
137 |
}); |
138 |
if (cacheImage!= null ){ |
139 |
((ImageView) findViewById(id)).setImageDrawable(cacheImage); |
140 |
} |
141 |
} |
142 |
|
143 |
//采用Handler+Thread+封装外部接口 |
144 |
private void loadImage5( final String url, final int id) { |
145 |
//如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行 |
146 |
Drawable cacheImage = asyncImageLoader3.loadDrawable(url, new AsyncImageLoader3.ImageCallback() { |
147 |
//请参见实现:如果第一次加载url时下面方法会执行 |
148 |
public void imageLoaded(Drawable imageDrawable) { |
149 |
((ImageView) findViewById(id)).setImageDrawable(imageDrawable); |
150 |
} |
151 |
}); |
152 |
if (cacheImage!= null ){ |
153 |
((ImageView) findViewById(id)).setImageDrawable(cacheImage); |
154 |
} |
155 |
} |
156 |
|
157 |
|
158 |
} |
xml文件大致如下:
01 |
<? xml version = "1.0" encoding = "utf-8" ?> |
02 |
|
03 |
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
04 |
android:layout_width = "fill_parent" |
05 |
android:orientation = "vertical" |
06 |
android:layout_height = "fill_parent" > |
07 |
< ImageView android:id = "@+id/image1" android:layout_height = "wrap_content" android:layout_width = "fill_parent" ></ ImageView > |
08 |
< ImageView android:id = "@+id/image2" android:layout_height = "wrap_content" android:layout_width = "fill_parent" ></ ImageView > |
09 |
< ImageView android:id = "@+id/image3" android:layout_height = "wrap_content" android:layout_width = "fill_parent" ></ ImageView > |
10 |
< ImageView android:id = "@+id/image5" android:layout_height = "wrap_content" android:layout_width = "fill_parent" ></ ImageView > |
11 |
< ImageView android:id = "@+id/image4" android:layout_height = "wrap_content" android:layout_width = "fill_parent" ></ ImageView > |
12 |
</ LinearLayout > |