研究了Android从网络上异步加载图像:
(1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法。
在主线程中new 一个Handler对象,加载图像方法如下所示
01 | private void loadImage( final String url, final int id) { |
02 | handler.post( new Runnable() { |
04 | Drawable drawable = null ; |
06 | drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
07 | } catch (IOException e) { |
09 | ((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
上面这个方法缺点很显然,经测试,如果要加载多个图片,这并不能实现异步加载,而是等到所有的图片都加载完才一起显示,因为它们都运行在一个线程中。
然后,我们可以简单改进下,将Handler+Runnable模式改为Handler+Thread+Message模式不就能实现同时开启多个线程吗?
(2)在主线程中new 一个Handler对象,代码如下:
1 | final Handler handler2= new Handler(){ |
3 | public void handleMessage(Message msg) { |
4 | ((ImageView) LazyLoadImageActivity. this .findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); |
对应加载图像代码如下:对应加载图像代码如下:对应加载图像代码如下:
02 | private void loadImage3( final String url, final int id) { |
03 | executorService.submit( new Runnable() { |
06 | final Drawable drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
07 | handler.post( new Runnable() { |
10 | ((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
13 | } catch (Exception e) { |
14 | throw new RuntimeException(e); |
(4)为了更方便使用我们可以将异步加载图像方法封装一个类,对外界只暴露一个方法即可,考虑到效率问题我们可以引入内存缓存机制,做法是
建立一个HashMap,其键(key)为加载图像url,其值(value)是图像对象Drawable。先看一下我们封装的类
01 | public class AsyncImageLoader3 { |
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(); |
09 | * @param imageUrl 图像url地址 |
10 | * @param callback 回调接口 |
11 | * @return 返回内存中缓存的图像,第一次加载返回null |
13 | public Drawable loadDrawable( final String imageUrl, final ImageCallback callback) { |
15 | if (imageCache.containsKey(imageUrl)) { |
16 | SoftReference<Drawable> softReference = imageCache.get(imageUrl); |
17 | if (softReference.get() != null ) { |
18 | return softReference.get(); |
22 | executorService.submit( new Runnable() { |
25 | final Drawable drawable = Drawable.createFromStream( new URL(imageUrl).openStream(), "image.png" ); |
27 | imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); |
29 | handler.post( new Runnable() { |
31 | callback.imageLoaded(drawable); |
34 | } catch (Exception e) { |
35 | throw new RuntimeException(e); |
42 | protected Drawable loadImageFromUrl(String imageUrl) { |
44 | return Drawable.createFromStream( new URL(imageUrl).openStream(), "image.png" ); |
45 | } catch (Exception e) { |
46 | throw new RuntimeException(e); |
50 | public interface ImageCallback { |
52 | public void imageLoaded(Drawable imageDrawable); |
这样封装好后使用起来就方便多了。在主线程中首先要引入AsyncImageLoader3 对象,然后直接调用其loadDrawable方法即可,需要注意的是ImageCallback接口的imageLoaded方法是唯一可以把加载的图像设置到目标ImageView或其相关的组件上。
在主线程调用代码:
先实例化对象 private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();
调用异步加载方法:
02 | private void loadImage4( final String url, final int id) { |
04 | Drawable cacheImage = asyncImageLoader.loadDrawable(url, new AsyncImageLoader.ImageCallback() { |
06 | public void imageLoaded(Drawable imageDrawable) { |
07 | ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); |
11 | ((ImageView) findViewById(id)).setImageDrawable(cacheImage); |
5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:
01 | public class AsyncImageLoader { |
03 | private Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); |
07 | * @param imageUrl 图像url地址 |
08 | * @param callback 回调接口 |
09 | * @return 返回内存中缓存的图像,第一次加载返回null |
11 | public Drawable loadDrawable( final String imageUrl, final ImageCallback callback) { |
13 | if (imageCache.containsKey(imageUrl)) { |
14 | SoftReference<Drawable> softReference = imageCache.get(imageUrl); |
15 | if (softReference.get() != null ) { |
16 | return softReference.get(); |
20 | final Handler handler = new Handler() { |
22 | public void handleMessage(Message msg) { |
23 | callback.imageLoaded((Drawable) msg.obj); |
28 | Drawable drawable = loadImageFromUrl(imageUrl); |
29 | imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); |
30 | handler.sendMessage(handler.obtainMessage( 0 , drawable)); |
54 | protected Drawable loadImageFromUrl(String imageUrl) { |
56 | return Drawable.createFromStream( new URL(imageUrl).openStream(), "src" ); |
57 | } catch (Exception e) { |
58 | throw new RuntimeException(e); |
62 | public interface ImageCallback { |
63 | public void imageLoaded(Drawable imageDrawable); |
至此,异步加载就介绍完了,下面给出的代码为测试用的完整代码:
001 | package com.bshark.supertelphone.activity; |
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; |
013 | import java.io.IOException; |
015 | import java.util.concurrent.ExecutorService; |
016 | import java.util.concurrent.Executors; |
018 | public class LazyLoadImageActivity extends Activity { |
019 | final Handler handler= new Handler(); |
020 | final Handler handler2= new Handler(){ |
022 | public void handleMessage(Message msg) { |
023 | ((ImageView) LazyLoadImageActivity. this .findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); |
026 | private ExecutorService executorService = Executors.newFixedThreadPool( 5 ); |
027 | private AsyncImageLoader asyncImageLoader = new AsyncImageLoader(); |
028 | private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3(); |
032 | public void onCreate(Bundle savedInstanceState) { |
033 | super .onCreate(savedInstanceState); |
034 | setContentView(R.layout.main); |
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); |
074 | protected void onDestroy() { |
075 | executorService.shutdown(); |
079 | private void loadImage( final String url, final int id) { |
080 | handler.post( new Runnable() { |
082 | Drawable drawable = null ; |
084 | drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
085 | } catch (IOException e) { |
087 | ((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
092 | private void loadImage2( final String url, final int id) { |
093 | Thread thread = new Thread(){ |
096 | Drawable drawable = null ; |
098 | drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
099 | } catch (IOException e) { |
102 | Message message= handler2.obtainMessage() ; |
104 | message.obj = drawable; |
105 | handler2.sendMessage(message); |
112 | private void loadImage3( final String url, final int id) { |
113 | executorService.submit( new Runnable() { |
116 | final Drawable drawable = Drawable.createFromStream( new URL(url).openStream(), "image.png" ); |
117 | handler.post( new Runnable() { |
120 | ((ImageView) LazyLoadImageActivity. this .findViewById(id)).setImageDrawable(drawable); |
123 | } catch (Exception e) { |
124 | throw new RuntimeException(e); |
130 | private void loadImage4( final String url, final int id) { |
132 | Drawable cacheImage = asyncImageLoader.loadDrawable(url, new AsyncImageLoader.ImageCallback() { |
134 | public void imageLoaded(Drawable imageDrawable) { |
135 | ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); |
138 | if (cacheImage!= null ){ |
139 | ((ImageView) findViewById(id)).setImageDrawable(cacheImage); |
144 | private void loadImage5( final String url, final int id) { |
146 | Drawable cacheImage = asyncImageLoader3.loadDrawable(url, new AsyncImageLoader3.ImageCallback() { |
148 | public void imageLoaded(Drawable imageDrawable) { |
149 | ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); |
152 | if (cacheImage!= null ){ |
153 | ((ImageView) findViewById(id)).setImageDrawable(cacheImage); |
xml文件大致如下:
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
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 > |