实例演示Android异步加载图片

本文给大家演示异步加载图片的分析过程。让大家了解异步加载图片的好处,以及如何更新UI。
首先给出main.xml布局文件:
简单来说就是 LinearLayout 布局,其下放了2个TextView和5个ImageView。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     android:orientation="vertical"
 5     android:layout_width="fill_parent"
 6     android:layout_height="fill_parent">
 7     <TextView
 8         android:text="图片区域开始"
 9         android:id="@+id/textView2"
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content" />
12     <ImageView
13         android:id="@+id/imageView1"
14         android:layout_height="wrap_content"
15         android:src="@drawable/icon"
16         android:layout_width="wrap_content" />
17     <ImageView
18         android:id="@+id/imageView2"
19         android:layout_height="wrap_content"
20         android:src="@drawable/icon"
21         android:layout_width="wrap_content" />
22     <ImageView
23         android:id="@+id/imageView3"
24         android:layout_height="wrap_content"
25         android:src="@drawable/icon"
26         android:layout_width="wrap_content" />
27     <ImageView
28         android:id="@+id/imageView4"
29         android:layout_height="wrap_content"
30         android:src="@drawable/icon"
31         android:layout_width="wrap_content" />
32     <ImageView
33         android:id="@+id/imageView5"
34         android:layout_height="wrap_content"
35         android:src="@drawable/icon"
36         android:layout_width="wrap_content" />
37     <TextView
38         android:text="图片区域结束"
39         android:id="@+id/textView1"
40         android:layout_width="wrap_content"
41         android:layout_height="wrap_content" />
42 </LinearLayout>

我们将演示的过程是异步从服务器上下载5张不同图片,依次放入这5个ImageView。上下2个TextView 是为了方便我们看是否阻塞了UI的显示。
当然 AndroidManifest.xml 文件中要配置好网络访问权限。

1 <uses-permission android:name="android.permission.INTERNET" />

1)Handler+Runnable模式
我们先看一个并不是异步线程加载的例子,而是使用 Handler+Runnable模式。
注意这里不是新开的线程,这里的代码其实是在UI主线程中下载图片的。
我们运行下面代码时,会发现它其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面。

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.io.IOException;
 4 import java.net.URL;
 5 
 6 import android.app.Activity;
 7 import android.graphics.drawable.Drawable;
 8 import android.os.Bundle;
 9 import android.os.Handler;
10 import android.os.SystemClock;
11 import android.util.Log;
12 import android.widget.ImageView;
13 
14 /**
15  *@author coolszy
16  *@date 2012-2-13
17  *@blog http://blog.92coding.com
18  *
19  */
20 public class MainActivity extends Activity
21 {
22     @Override
23     public void onCreate(Bundle savedInstanceState)
24     {
25         super.onCreate(savedInstanceState);
26         setContentView(R.layout.main);
27         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
28         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
29         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
30         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
31         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
32     }
33 
34     private Handler handler = new Handler();
35 
36     private void loadImage(final String url, final int id)
37     {
38         handler.post(new Runnable()
39         {
40             public void run()
41             {
42                 Drawable drawable = null;
43                 try
44                 {
45                     drawable = Drawable.createFromStream(new URL(url).openStream(), "image.gif");
46                 } catch (IOException e)
47                 {
48                     Log.i("MainActivity", e.getMessage());
49                 }
50                 if (drawable == null)
51                 {
52                     Log.i("MainActivity", "null drawable");
53                 } else
54                 {
55                     Log.i("MainActivity", "not null drawable");
56                 }
57                 // 为了测试缓存而模拟的网络延时
58                 SystemClock.sleep(2000);
59                 ((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);
60             }
61         });
62     }
63 }

2)Handler+Thread+Message模式
这种模式使用了线程,所以可以看到异步加载的效果。
核心代码:

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.io.IOException;
 4 import java.net.URL;
 5 
 6 import android.app.Activity;
 7 import android.graphics.drawable.Drawable;
 8 import android.os.Bundle;
 9 import android.os.Handler;
10 import android.os.Message;
11 import android.os.SystemClock;
12 import android.util.Log;
13 import android.widget.ImageView;
14 
15 /**
16  *@author coolszy
17  *@date 2012-2-13
18  *@blog http://blog.92coding.com
19  *
20  */
21 public class MainActivity extends Activity
22 {
23     @Override
24     public void onCreate(Bundle savedInstanceState)
25     {
26         super.onCreate(savedInstanceState);
27         setContentView(R.layout.main);
28         Log.i("MainActivity", "MainThread ID:"+Thread.currentThread().getId());
29         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
30         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
31         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
32         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
33         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
34     }
35 
36     final Handler handler = new Handler()
37     {
38         @Override
39         public void handleMessage(Message msg)
40         {
41             Log.i("MainActivity", "UpdateUIThread ID:"+Thread.currentThread().getId());
42             ((ImageView) MainActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable) msg.obj);
43         }
44     };
45 
46     // 采用handler+Thread模式实现多线程异步加载
47     private void loadImage(final String url, final int id)
48     {
49         Thread thread = new Thread()
50         {
51             @Override
52             public void run()
53             {
54                 Drawable drawable = null;
55                 try
56                 {
57                     Log.i("MainActivity", "Thread ID:"+Thread.currentThread().getId());
58                     drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
59                 } catch (IOException e)
60                 {
61                     Log.i("MainActivity", e.getMessage());
62                 }
63 
64                 // 模拟网络延时
65                 SystemClock.sleep(2000);
66 
67                 Message message = handler.obtainMessage();
68                 message.arg1 = id;
69                 message.obj = drawable;
70                 handler.sendMessage(message);
71             }
72         };
73         thread.start();
74         thread = null;
75     }
76 }

这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去。
3)Handler+ExecutorService(线程池)+MessageQueue模式
能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。
这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就使用它。
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
下面的演示例子是创建一个可重用固定线程数的线程池。
核心代码

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.net.URL;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 
 7 import android.app.Activity;
 8 import android.graphics.drawable.Drawable;
 9 import android.os.Bundle;
10 import android.os.Handler;
11 import android.os.SystemClock;
12 import android.util.Log;
13 import android.widget.ImageView;
14 
15 /**
16  *@author coolszy
17  *@date 2012-2-13
18  *@blog http://blog.92coding.com
19  *
20  */
21 public class MainActivity extends Activity
22 {
23     @Override
24     public void onCreate(Bundle savedInstanceState)
25     {
26         super.onCreate(savedInstanceState);
27         setContentView(R.layout.main);
28         Log.i("MainActivity", "MainThread ID:"+Thread.currentThread().getId());
29         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
30         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
31         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
32         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
33         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
34     }
35 
36     private Handler handler = new Handler();
37 
38     private ExecutorService executorService = Executors.newFixedThreadPool(5);
39 
40     // 引入线程池来管理多线程
41     private void loadImage(final String url, final int id)
42     {
43         executorService.submit(new Runnable()
44         {
45             public void run()
46             {
47                 try
48                 {
49                     Log.i("MainActivity", "Thread ID:"+Thread.currentThread().getId());
50                     final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
51                     // 模拟网络延时
52                     SystemClock.sleep(2000);
53                     handler.post(new Runnable()
54                     {
55                         public void run()
56                         {
57                             Log.i("MainActivity", "UpdateUIThread ID:"+Thread.currentThread().getId());
58                             ((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);
59                         }
60                     });
61                 } catch (Exception e)
62                 {
63                     throw new RuntimeException(e);
64                 }
65             }
66         });
67     }
68 }

这里我们象第一步一样使用了
handler.post(new Runnable() { }) 更新前段显示当然是在UI主线程,我们还有 executorService.submit(new Runnable() { }) 来确保下载是在线程池的线程中。
4)Handler+ExecutorService(线程池)+MessageQueue+缓存模式
下面比起前一个做了几个改造:
把整个代码封装在一个类中,同时为了避免出现同时多次下载同一幅图的问题,使用了本地缓存封装的类:

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.lang.ref.SoftReference;
 4 import java.net.URL;
 5 import java.util.HashMap;
 6 import java.util.Map;
 7 import java.util.concurrent.ExecutorService;
 8 import java.util.concurrent.Executors;
 9 
10 import android.graphics.drawable.Drawable;
11 import android.os.Handler;
12 import android.os.SystemClock;
13 import android.util.Log;
14 
15 /**
16  *@author coolszy
17  *@date 2012-2-13
18  *@blog http://blog.92coding.com
19  */
20 
21 public class AsyncImageLoader
22 {
23     // 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
24     public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
25 
26     private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五个线程来执行任务
27     private final Handler handler = new Handler();
28 
29     /**
30      *
31      * @param imageUrl
32      *            图像url地址
33      * @param callback
34      *            回调接口
35      * @return 返回内存中缓存的图像,第一次加载返回null
36      */
37     public Drawable loadDrawable(final String imageUrl, final ImageCallback callback)
38     {
39         // 如果缓存过就从缓存中取出数据
40         if (imageCache.containsKey(imageUrl))
41         {
42             SoftReference<Drawable> softReference = imageCache.get(imageUrl);
43             if (softReference.get() != null)
44             {
45                 Log.i("MainActivity", "图片存在缓存中.");
46                 return softReference.get();
47             }
48         }
49         // 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
50         executorService.submit(new Runnable()
51         {
52             public void run()
53             {
54                 try
55                 {
56                     Log.i("MainActivity", "下载图片...");
57                     final Drawable drawable = loadImageFromUrl(imageUrl);
58                     imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
59                     handler.post(new Runnable()
60                     {
61                         public void run()
62                         {
63                             callback.imageLoaded(drawable);
64                         }
65                     });
66                 } catch (Exception e)
67                 {
68                     throw new RuntimeException(e);
69                 }
70             }
71         });
72         return null;
73     }
74 
75     // 从网络上取数据方法
76     protected Drawable loadImageFromUrl(String imageUrl)
77     {
78         try
79         {
80             // 测试时,模拟网络延时,实际时这行代码不能有
81             SystemClock.sleep(2000);
82             return Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png");
83 
84         } catch (Exception e)
85         {
86             throw new RuntimeException(e);
87         }
88     }
89 
90     // 对外界开放的回调接口
91     public interface ImageCallback
92     {
93         // 注意 此方法是用来设置目标对象的图像资源
94         public void imageLoaded(Drawable imageDrawable);
95     }
96 }

说明:
final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的。
前端调用:

 1 package com.szy.textviewimagedemo;
 2 
 3 import android.app.Activity;
 4 import android.graphics.drawable.Drawable;
 5 import android.os.Bundle;
 6 import android.widget.ImageView;
 7 
 8 /**
 9  *@author coolszy
10  *@date 2012-2-13
11  *@blog http://blog.92coding.com
12  *
13  */
14 public class MainActivity extends Activity
15 {
16     @Override
17     public void onCreate(Bundle savedInstanceState)
18     {
19         super.onCreate(savedInstanceState);
20         setContentView(R.layout.main);
21         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
22         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
23         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
24         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
25         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
26     }
27 
28     private AsyncImageLoader asyncImageLoader = new AsyncImageLoader();
29 
30     // 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
31     private void loadImage(final String url, final int id)
32     {
33         // 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
34         Drawable cacheImage = asyncImageLoader.loadDrawable(url, new AsyncImageLoader.ImageCallback()
35         {
36             // 请参见实现:如果第一次加载url时下面方法会执行
37             public void imageLoaded(Drawable imageDrawable)
38             {
39                 ((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
40             }
41         });
42         if (cacheImage != null)
43         {
44             ((ImageView) findViewById(id)).setImageDrawable(cacheImage);
45         }
46     }
47 }

若水工作室

posted @ 2014-03-10 23:28  欢醉  阅读(3608)  评论(0编辑  收藏  举报