標籤:android blog http os io 使用 java ar strong
原文:http://www.cnblogs.com/ghj1976/archive/2011/05/06/2038738.html#3018499
在學習"Android非同步載入映像小結"這篇文章時, 發現有些地方沒寫清楚,我就根據我的理解,把這篇文章的代碼重寫整理了一遍,下面就是我的整理。
下面測試使用的layout檔案:
簡單來說就是 LinearLayout 布局,其下放了5個ImageView。
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="fill_parent"android:layout_height="fill_parent"><TextView android:text="圖片地區開始" android:id="@+id/textView2"android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView><ImageView android:id="@+id/imageView1"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView2"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView3"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView4"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView5"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><TextView android:text="圖片地區結束" android:id="@+id/textView1"android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView></LinearLayout>
我們將示範的邏輯是非同步從伺服器上下載5張不同圖片,依次放入這5個ImageView。上下2個TextView 是為了方便我們看是否阻塞了UI的顯示。
當然 AndroidManifest.xml 檔案中要配置好網路存取權限。
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Handler+Runnable模式
我們先看一個並不是非同步線程載入的例子,使用 Handler+Runnable模式。
這裡為何不是新開線程的原因請參看這篇文章:Android Runnable 運行在那個線程 這裡的代碼其實是在UI 主線程中下載圖片的,而不是新開線程。
我們運行下面代碼時,會發現他其實是阻塞了整個介面的顯示,需要所有圖片都載入完成後,才能顯示介面。
package ghj1976.AndroidTest;import java.io.IOException;import java.net.URL;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.os.Handler;import android.os.SystemClock;import android.util.Log;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage(http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage("http://cache.soso.com/30d/img/web/logo.gif, R.id.imageView3);loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage("http://images.cnblogs.com/logo_small.gif",R.id.imageView5);}private Handler handler = new Handler();private void loadImage(final String url, final int id) {handler.post(new Runnable() {public void run() {Drawable drawable = null;try {drawable = Drawable.createFromStream(new URL(url).openStream(), "image.gif");} catch (IOException e) {Log.d("test", e.getMessage());}if (drawable == null) {Log.d("test", "null drawable");} else {Log.d("test", "not null drawable");} // 為了測試緩衝而類比的網路延時
SystemClock.sleep(2000);
((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);}});}}
Handler+Thread+Message模式
這種模式使用了線程,所以可以看到非同步載入的效果。
核心代碼:
package ghj1976.AndroidTest;import java.io.IOException;import java.net.URL;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.SystemClock;import android.util.Log;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);loadImage2("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage2("http://images.cnblogs.com/logo_small.gif",R.id.imageView5);}final Handler handler2 = new Handler() {@Overridepublic void handleMessage(Message msg) {((ImageView) MainActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable) msg.obj);}};// 採用handler+Thread模式實現多線程非同步載入private void loadImage2(final String url, final int id) {Thread thread = new Thread() {@Overridepublic void run() {Drawable drawable = null;try {drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");} catch (IOException e) {Log.d("test", e.getMessage());}// 類比網路延時SystemClock.sleep(2000);Message message = handler2.obtainMessage();message.arg1 = id;message.obj = drawable;handler2.sendMessage(message);}};thread.start();thread = null;}}
這時候我們可以看到實現了非同步載入, 介面開啟時,五個ImageView都是沒有圖的,然後在各自線程下載完後才把圖自動更新上去。
Handler+ExecutorService(線程池)+MessageQueue模式
能開線程的個數畢竟是有限的,我們總不能開很多線程,對於手機更是如此。
這個例子是使用線程池。Android擁有與Java相同的ExecutorService實現,我們就來用它。
線程池的基本思想還是一種對象池的思想,開闢一塊記憶體空間,裡面存放了眾多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣可以避免反覆建立線程對象所帶來的效能開銷,節省了系統的資源。
線程池的資訊可以參看這篇文章:Java&Android的線程池-ExecutorService 下面的示範例子是建立一個可重用固定線程數的線程池。
核心代碼
package ghj1976.AndroidTest;import java.io.IOException;import java.net.URL;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.SystemClock;import android.util.Log;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage3("http://cache.soso.com/30d/img/web/logo.gif",R.id.imageView3);loadImage3("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage3("http://images.cnblogs.com/logo_small.gif",R.id.imageView5);}private Handler handler = new Handler();private ExecutorService executorService = Executors.newFixedThreadPool(5);// 引入線程池來管理多線程private void loadImage3(final String url, final int id) {executorService.submit(new Runnable() {public void run() {try {final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");// 類比網路延時SystemClock.sleep(2000);handler.post(new Runnable() {public void run() {((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);}});} catch (Exception e) {throw new RuntimeException(e);}}});}}
這裡我們象第一步一樣使用了 handler.post(new Runnable() { 更新前段顯示當然是在UI主線程,我們還有 executorService.submit(new Runnable() { 來確保下載是線上程池的線程中。
Handler+ExecutorService(線程池)+MessageQueue+緩衝模式
下面比起前一個做了幾個改造:
- 把整個代碼封裝在一個類中
- 為了避免出現同時多次下載同一幅圖的問題,使用了本機快取
封裝的類:
package ghj1976.AndroidTest;import java.lang.ref.SoftReference;import java.net.URL;import java.util.HashMap;import java.util.Map;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import android.graphics.drawable.Drawable;import android.os.Handler;import android.os.SystemClock;public class AsyncImageLoader3 {// 為了加快速度,在記憶體中開啟緩衝(主要應用於重複圖片較多時,或者同一個圖片要多次被訪問,比如在ListView時來復原動)public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五個線程來執行任務private final Handler handler = new Handler();/** * * @param imageUrl * 映像url地址 * @param callback * 回調介面 * @return 返回記憶體中緩衝的映像,第一次載入返回null */public Drawable loadDrawable(final String imageUrl,final ImageCallback callback) {// 如果緩衝過就從緩衝中取出資料if (imageCache.containsKey(imageUrl)) {SoftReference<Drawable> softReference = imageCache.get(imageUrl);if (softReference.get() != null) {return softReference.get();}}// 緩衝中沒有映像,則從網路上取出資料,並將取出的資料緩衝到記憶體中executorService.submit(new Runnable() {public void run() {try {final Drawable drawable = loadImageFromUrl(imageUrl); imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));handler.post(new Runnable() {public void run() {callback.imageLoaded(drawable);}});} catch (Exception e) {throw new RuntimeException(e);}}});return null;}// 從網路上取資料方法protected Drawable loadImageFromUrl(String imageUrl) {try {// 測試時,類比網路延時,實際時這行代碼不能有SystemClock.sleep(2000);return Drawable.createFromStream(new URL(imageUrl).openStream(),"image.png");} catch (Exception e) {throw new RuntimeException(e);}}// 對外界開放的回調介面public interface ImageCallback {// 注意 此方法是用來設定目標對象的映像資源public void imageLoaded(Drawable imageDrawable);}}
說明:
final參數是指當函數參數為final類型時,你可以讀取使用該參數,但是無法改變該參數的值。參看:Java關鍵字final、static使用總結
這裡使用SoftReference 是為瞭解決記憶體不足的錯誤(OutOfMemoryError)的,更詳細的可以參看:記憶體最佳化的兩個類:SoftReference 和 WeakReference
前段調用:
package ghj1976.AndroidTest;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage4("http://cache.soso.com/30d/img/web/logo.gif",R.id.imageView3);loadImage4("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage4("http://images.cnblogs.com/logo_small.gif",R.id.imageView5);}private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();// 引入線程池,並引入記憶體緩衝功能,並對外部調用封裝了介面,簡化調用過程private void loadImage4(final String url, final int id) {// 如果緩衝過就會從緩衝中取出映像,ImageCallback介面中方法也不會被執行Drawable cacheImage = asyncImageLoader3.loadDrawable(url,new AsyncImageLoader3.ImageCallback() {// 請參見實現:如果第一次載入url時下面方法會執行public void imageLoaded(Drawable imageDrawable) {((ImageView) findViewById(id)).setImageDrawable(imageDrawable);}});if (cacheImage != null) {((ImageView) findViewById(id)).setImageDrawable(cacheImage);}}}
參考資料:
Android非同步載入映像小結
http://blog.csdn.net/sgl870927/archive/2011/03/29/6285535.aspx
演化理解 Android 非同步載入圖片