Android異步加載圖像(含線程池,緩存方法)

研究瞭android從網絡上異步加載圖像:

(1)由於android UI更新支持單一線程原則,所以從網絡上取數據並更新到界面上,為瞭不阻塞主線程首先可能會想到以下方法。

     在主線程中new 一個Handler對象,加載圖像方法如下所示

[java] 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.png"); 
                   } catch (IOException e) { 
                   } 
                   ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable); 
               } 
           }); 
   } 

上面這個方法缺點很顯然,經測試,如果要加載多個圖片,這並不能實現異步加載,而是等到所有的圖片都加載完才一起顯示,因為它們都運行在一個線程中。

然後,我們可以簡單改進下,將Handler+Runnable模式改為Handler+Thread+Message模式不就能實現同時開啟多個線程嗎?

(2)在主線程中new 一個Handler對象,代碼如下:
[java]
final Handler handler2=new Handler(){ 
         @Override 
         public void handleMessage(Message msg) { 
            ((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); 
         } 
     }; 

對應加載圖像代碼如下:對應加載圖像代碼如下:對應加載圖像代碼如下:

[java]
// 引入線程池來管理多線程 
   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"); 
                   handler.post(new Runnable() { 
 
                       public void run() { 
                           ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable); 
                       } 
                   }); 
               } catch (Exception e) { 
                   throw new RuntimeException(e); 
               } 
           } 
       }); 
   } 

(4)為瞭更方便使用我們可以將異步加載圖像方法封裝一個類,對外界隻暴露一個方法即可,考慮到效率問題我們可以引入內存緩存機制,做法是

建立一個HashMap,其鍵(key)為加載圖像url,其值(value)是圖像對象Drawable。先看一下我們封裝的類

[java]
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 = Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png"); 
 
                    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 { 
            return Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png"); 
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
    } 
    //對外界開放的回調接口 
    public interface ImageCallback { 
        //註意 此方法是用來設置目標對象的圖像資源 
        public void imageLoaded(Drawable imageDrawable); 
    } 

這樣封裝好後使用起來就方便多瞭。在主線程中首先要引入AsyncImageLoader3 對象,然後直接調用其loadDrawable方法即可,需要註意的是ImageCallback接口的imageLoaded方法是唯一可以把加載的圖像設置到目標ImageView或其相關的組件上。

在主線程調用代碼:

  先實例化對象 private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();

  調用異步加載方法:

 

[java]
//引入線程池,並引入內存緩存功能,並對外部調用封裝瞭接口,簡化調用過程 
    private void loadImage4(final String url, final int id) { 
          //如果緩存過就會從緩存中取出圖像,ImageCallback接口中方法也不會被執行 
         Drawable cacheImage = asyncImageLoader.loadDrawable(url,new AsyncImageLoader.ImageCallback() { 
             //請參見實現:如果第一次加載url時下面方法會執行 
             public void imageLoaded(Drawable imageDrawable) { 
               ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); 
             } 
         }); 
        if(cacheImage!=null){ 
          ((ImageView) findViewById(id)).setImageDrawable(cacheImage); 
        } 
    } 

5)同理,下面也給出采用Thread+Handler+MessageQueue+內存緩存代碼,原則同(4),隻是把線程池換成瞭Thread+Handler+MessageQueue模式而已。代碼如下:5)同理,下面也給出采用Thread+Handler+MessageQueue+內存緩存代碼,原則同(4),隻是把線程池換成瞭Thread+Handler+MessageQueue模式而已。代碼如下:

[java]
public class AsyncImageLoader { 
   //為瞭加快速度,加入瞭緩存(主要應用於重復圖片較多時,或者同一個圖片要多次被訪問,比如在ListView時來回滾動) 
    private Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); 
 
     /**
     *
     * @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(); 
            } 
        } 
 
        final Handler handler = new Handler() { 
            @Override 
            public void handleMessage(Message msg) { 
                callback.imageLoaded((Drawable) msg.obj); 
            } 
        }; 
        new Thread() { 
            public void run() { 
                Drawable drawable = loadImageFromUrl(imageUrl); 
                imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); 
                handler.sendMessage(handler.obtainMessage(0, drawable)); 
 
            } 
 
        }.start(); 
        /*
        下面註釋的這段代碼是Handler的一種代替方法
         */ 
//        new AsyncTask() { 
//            @Override 
//            protected Drawable doInBackground(Object… objects) { 
//                  Drawable drawable = loadImageFromUrl(imageUrl); 
//                imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); 
//                return  drawable; 
//            } 
// 
//            @Override 
//            protected void onPostExecute(Object o) { 
//                  callback.imageLoaded((Drawable) o); 
//            } 
//        }.execute(); 
        return null; 
    } 
 
    protected Drawable loadImageFromUrl(String imageUrl) { 
        try { 
            return Drawable.createFromStream(new URL(imageUrl).openStream(), "src"); 
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
    } 
    //對外界開放的回調接口 
    public interface ImageCallback { 
        public void imageLoaded(Drawable imageDrawable); 
    } 

至此,異步加載就介紹完瞭,下面給出的代碼為測試用的完整代碼:
[java]
package com.bshark.supertelphone.activity; 
 
import android.app.Activity; 
import android.graphics.drawable.Drawable; 
import android.os.Bundle; 
import android.os.Handler; 
import android.os.Message; 
import android.widget.ImageView; 
import com.bshark.supertelphone.R; 
import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader; 
import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader3; 
 
import java.io.IOException; 
import java.net.URL; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
 
public class LazyLoadImageActivity extends Activity { 
       final Handler handler=new Handler(); 
      final Handler handler2=new Handler(){ 
          @Override 
          public void handleMessage(Message msg) { 
             ((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); 
          } 
      }; 
private ExecutorService executorService = Executors.newFixedThreadPool(5);    //固定五個線程來執行任務 
    private AsyncImageLoader asyncImageLoader = new AsyncImageLoader(); 
    private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3(); 
 
 
@Override 
public void onCreate(Bundle savedInstanceState) { 
  super.onCreate(savedInstanceState); 
  setContentView(R.layout.main); 
   
//  loadImage("/wp-content/images1/20181012/20120522105134213362.gif", R.id.image1); 
//  loadImage("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image2); 
//  loadImage("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image3); 
//        loadImage("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image4); 
//  loadImage("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image5); 
 
        loadImage2("/wp-content/images1/20181012/20120522105134213362.gif", R.id.image1); 
  loadImage2("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image2); 
  loadImage2("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image3); 
        loadImage2("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image4); 
  loadImage2("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image5); 
//        loadImage3("/wp-content/images1/20181012/20120522105134213362.gif", R.id.image1); 
//  loadImage3("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image2); 
//  loadImage3("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image3); 
//        loadImage3("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image4); 
//  loadImage3("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image5); 
 
//        loadImage4("/wp-content/images1/20181012/20120522105134213362.gif", R.id.image1); 
//  loadImage4("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image2); 
//  loadImage4("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image3); 
//        loadImage4("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image4); 
//  loadImage4("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image5); 
 
//        loadImage5("/wp-content/images1/20181012/20120522105134213362.gif", R.id.image1); 
//        //為瞭測試緩存而模擬的網絡延時 
//        SystemClock.sleep(2000); 
//  loadImage5("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image2); 
//        SystemClock.sleep(2000); 
//  loadImage5("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image3); 
//        SystemClock.sleep(2000); 
//        loadImage5("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image4); 
//        SystemClock.sleep(2000); 
//  loadImage5("/wp-content/images1/20181012/20120522105134759364.gif", R.id.image5); 
//        SystemClock.sleep(2000); 
//         loadImage5("/wp-content/images1/20181012/20120522105134546363.gif", R.id.image4); 

 
@Override 
protected void onDestroy() { 
  executorService.shutdown(); 
  super.onDestroy(); 

    //線程加載圖像基本原理 
    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.png"); 
                    } catch (IOException e) { 
                    } 
                    ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable); 
                } 
            }); 
    } 
     //采用handler+Thread模式實現多線程異步加載 
     private void loadImage2(final String url, final int id) { 
         Thread thread = new Thread(){ 
             @Override 
             public void run() { 
               Drawable drawable = null; 
                    try { 
                        drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png"); 
                    } catch (IOException e) { 
                    } 
 
                Message message= handler2.obtainMessage() ; 
                 message.arg1 = id; 
                 message.obj = drawable; 
                 handler2.sendMessage(message); 
             } 
         }; 
         thread.start(); 
         thread = null; 
    } 
    // 引入線程池來管理多線程 
    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"); 
                    handler.post(new Runnable() { 
 
                        public void run() { 
                            ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable); 
                        } 
                    }); 
                } catch (Exception e) { 
                    throw new RuntimeException(e); 
                } 
            } 
        }); 
    } 
    //引入線程池,並引入內存緩存功能,並對外部調用封裝瞭接口,簡化調用過程 
    private void loadImage4(final String url, final int id) { 
          //如果緩存過就會從緩存中取出圖像,ImageCallback接口中方法也不會被執行 
         Drawable cacheImage = asyncImageLoader.loadDrawable(url,new AsyncImageLoader.ImageCallback() { 
             //請參見實現:如果第一次加載url時下面方法會執行 
             public void imageLoaded(Drawable imageDrawable) { 
               ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); 
             } 
         }); 
        if(cacheImage!=null){ 
          ((ImageView) findViewById(id)).setImageDrawable(cacheImage); 
        } 
    } 
 
    //采用Handler+Thread+封裝外部接口 
    private void loadImage5(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); 
        } 
    } 
 
 

xml文件大致如下:

[html]
<?xml version="1.0" encoding="utf-8"?> 
 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
              android:layout_width="fill_parent" 
              android:orientation="vertical" 
              android:layout_height="fill_parent" > 
  <ImageView android:id="@+id/image1" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
   <ImageView android:id="@+id/image2" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    <ImageView android:id="@+id/image3" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    <ImageView android:id="@+id/image5" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    <ImageView android:id="@+id/image4" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
</LinearLayout> 

 

 

摘自 亨利摩根的專欄

發佈留言