本文给大家演示异步加载图片的分析过程。让大家了解异步加载图片的好处,以及如何更新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 }