研究了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 > |