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

若水工作室


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM