实例演示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 }

你可能感兴趣的:(android)