android ListView非同步載入圖片(雙快取)

weixin_34344677發表於2013-01-18


首先宣告,參考部落格地址:http://www.iteye.com/topic/685986

 

對於ListView,相信很多人都很熟悉,因為確實太常見了,所以,做的使用者體驗更好,就成了我們的追求。。。

 

常見的ListView中很少全是文字的,一般都是圖文共存的,而圖片的來源是伺服器端(很少有寫在客戶端的吧。。。考慮客戶端的大小和更新的問題),所以,網路問題就成了圖片是否能順利載入成功的決定性因素了。大家都知道每次啟動一個Android應用,都會啟動一個UI主執行緒,主要是響應使用者的互動,如果我們把不確定的獲取網路圖片的操作放在UI主執行緒,結果也就不確定了。。。當然,如果你網路足夠好的話,應該問題不大,但是,網路誰能保證呢?所以就出現了“非同步載入”的方法!

 

我先敘述一下非同步載入的原理,說的通俗一點就是UI主執行緒繼續做與使用者互動的響應監聽和操作,而載入圖片的任務交到其他執行緒中去做,當圖片載入完成之後,再跟據某種機制(比如回撥)繪製到要顯示的控制元件中。

 

首先,貼出AsyncBitmapLoader.java,這個類是關鍵,主要做的就是當載入圖片的時候,去緩衝區查詢,如果有的話,則立刻返回Bitmap物件,省掉再去網路伺服器下載的時間和流量。

 

  1. <span style="font-size:18px;">package onerain.ald.async;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.io.InputStream;  
  8. import java.lang.ref.SoftReference;  
  9. import java.util.HashMap;  
  10.   
  11. import onerain.ald.common.HttpUtils;  
  12. import android.graphics.Bitmap;  
  13. import android.graphics.BitmapFactory;  
  14. import android.os.Handler;  
  15. import android.os.Message;  
  16. import android.widget.ImageView;  
  17.   
  18. /** 
  19.  * @author oneRain 
  20.  **/  
  21. public class AsyncBitmapLoader  
  22. {  
  23.     /** 
  24.      * 記憶體圖片軟引用緩衝 
  25.      */  
  26.     private HashMap<String, SoftReference<Bitmap>> imageCache = null;  
  27.       
  28.     public AsyncBitmapLoader()  
  29.     {  
  30.         imageCache = new HashMap<String, SoftReference<Bitmap>>();  
  31.     }  
  32.       
  33.     public Bitmap loadBitmap(final ImageView imageView, final String imageURL, final ImageCallBack imageCallBack)  
  34.     {  
  35.         //在記憶體快取中,則返回Bitmap物件  
  36.         if(imageCache.containsKey(imageURL))  
  37.         {  
  38.             SoftReference<Bitmap> reference = imageCache.get(imageURL);  
  39.             Bitmap bitmap = reference.get();  
  40.             if(bitmap != null)  
  41.             {  
  42.                 return bitmap;  
  43.             }  
  44.         }  
  45.         else  
  46.         {  
  47.             /** 
  48.              * 加上一個對本地快取的查詢 
  49.              */  
  50.             String bitmapName = imageURL.substring(imageURL.lastIndexOf("/") + 1);  
  51.             File cacheDir = new File("/mnt/sdcard/test/");  
  52.             File[] cacheFiles = cacheDir.listFiles();  
  53.             int i = 0;  
  54.             for(; i<cacheFiles.length; i++)  
  55.             {  
  56.                 if(bitmapName.equals(cacheFiles[i].getName()))  
  57.                 {  
  58.                     break;  
  59.                 }  
  60.             }  
  61.               
  62.             if(i < cacheFiles.length)  
  63.             {  
  64.                 return BitmapFactory.decodeFile("/mnt/sdcard/test/" + bitmapName);  
  65.             }  
  66.         }  
  67.           
  68.         final Handler handler = new Handler()  
  69.         {  
  70.             /* (non-Javadoc) 
  71.              * @see android.os.Handler#handleMessage(android.os.Message) 
  72.              */  
  73.             @Override  
  74.             public void handleMessage(Message msg)  
  75.             {  
  76.                 // TODO Auto-generated method stub  
  77.                 imageCallBack.imageLoad(imageView, (Bitmap)msg.obj);  
  78.             }  
  79.         };  
  80.           
  81.         //如果不在記憶體快取中,也不在本地(被jvm回收掉),則開啟執行緒下載圖片  
  82.         new Thread()  
  83.         {  
  84.             /* (non-Javadoc) 
  85.              * @see java.lang.Thread#run() 
  86.              */  
  87.             @Override  
  88.             public void run()  
  89.             {  
  90.                 // TODO Auto-generated method stub  
  91.                 InputStream bitmapIs = HttpUtils.getStreamFromURL(imageURL);  
  92.                   
  93.                 Bitmap bitmap = BitmapFactory.decodeStream(bitmapIs);  
  94.                 imageCache.put(imageURL, new SoftReference<Bitmap>(bitmap));  
  95.                 Message msg = handler.obtainMessage(0, bitmap);  
  96.                 handler.sendMessage(msg);  
  97.                   
  98.                 File dir = new File("/mnt/sdcard/test/");  
  99.                 if(!dir.exists())  
  100.                 {  
  101.                     dir.mkdirs();  
  102.                 }  
  103.                   
  104.                 File bitmapFile = new File("/mnt/sdcard/test/" +   
  105.                         imageURL.substring(imageURL.lastIndexOf("/") + 1));  
  106.                 if(!bitmapFile.exists())  
  107.                 {  
  108.                     try  
  109.                     {  
  110.                         bitmapFile.createNewFile();  
  111.                     }  
  112.                     catch (IOException e)  
  113.                     {  
  114.                         // TODO Auto-generated catch block  
  115.                         e.printStackTrace();  
  116.                     }  
  117.                 }  
  118.                 FileOutputStream fos;  
  119.                 try  
  120.                 {  
  121.                     fos = new FileOutputStream(bitmapFile);  
  122.                     bitmap.compress(Bitmap.CompressFormat.JPEG,   
  123.                             100, fos);  
  124.                     fos.close();  
  125.                 }  
  126.                 catch (FileNotFoundException e)  
  127.                 {  
  128.                     // TODO Auto-generated catch block  
  129.                     e.printStackTrace();  
  130.                 }  
  131.                 catch (IOException e)  
  132.                 {  
  133.                     // TODO Auto-generated catch block  
  134.                     e.printStackTrace();  
  135.                 }  
  136.             }  
  137.         }.start();  
  138.           
  139.         return null;  
  140.     }  
  141.       
  142.     /** 
  143.      * 回撥介面 
  144.      * @author onerain 
  145.      * 
  146.      */  
  147.     public interface ImageCallBack  
  148.     {  
  149.         public void imageLoad(ImageView imageView, Bitmap bitmap);  
  150.     }  
  151. }  
  152. </span>  

 

 

PS:我這裡用到了兩個緩衝,一是記憶體快取,一個是本地快取(即SD卡快取),其中用到了SoftReference,這個類的主要作用是生成一個“軟引用”,你可以認為是一種隨時會由於JVM垃圾回收機制回收掉的Map物件(而平時我們所用到的引用不釋放的話不會被JVM回收),之所以用到軟引用,就是考慮到android對圖片的快取是有大小限制的,當超過這個大小時,就一定要釋放,如果你用引用,保持不釋放的話,那麼FC(Force close)就離你不遠了。。。我這裡還用到了一個本地快取的機制,是和參考部落格不太一樣的地方,只是提供一種思路,方法還沒有完善(主要因為和伺服器還沒約定好關於圖片的命名規則),主要作用是在使用者瀏覽過大量圖片之後(超過記憶體快取容量之後),保留在本地,一是為了提高讀取速度,二是可以減少流量消耗!

 

這個類設計好之後,在自定義的Adapter當中比之前會有些不同,先上程式碼再解釋

 

  1. <span style="font-size:18px;">package onerain.ald.adapter;  
  2.   
  3. import java.util.List;  
  4.   
  5. import onerain.ald.R;  
  6. import onerain.ald.async.AsyncBitmapLoader;  
  7. import onerain.ald.async.AsyncBitmapLoader.ImageCallBack;  
  8. import onerain.ald.entity.BaseBookEntity;  
  9. import onerain.ald.holder.ViewHolder;  
  10. import android.content.Context;  
  11. import android.graphics.Bitmap;  
  12. import android.view.LayoutInflater;  
  13. import android.view.View;  
  14. import android.view.ViewGroup;  
  15. import android.widget.BaseAdapter;  
  16. import android.widget.ImageView;  
  17. import android.widget.TextView;  
  18.   
  19. /** 
  20.  * @author oneRain 
  21.  **/  
  22. public class ListAdapter extends BaseAdapter  
  23. {  
  24.     private Context context = null;  
  25.     private List<BaseBookEntity> bookList = null;  
  26.       
  27.     private AsyncBitmapLoader asyncLoader = null;  
  28.       
  29.     public ListAdapter(Context context, List<BaseBookEntity> bookList)  
  30.     {  
  31.         this.context = context;  
  32.         this.bookList = bookList;  
  33.         this.asyncLoader = new AsyncBitmapLoader();  
  34.     }  
  35.       
  36.     @Override  
  37.     public int getCount()  
  38.     {  
  39.         // TODO Auto-generated method stub  
  40.         return bookList.size();  
  41.     }  
  42.   
  43.     @Override  
  44.     public Object getItem(int position)  
  45.     {  
  46.         // TODO Auto-generated method stub  
  47.         return bookList.get(position);  
  48.     }  
  49.   
  50.     @Override  
  51.     public long getItemId(int position)  
  52.     {  
  53.         // TODO Auto-generated method stub  
  54.         return position;  
  55.     }  
  56.   
  57.     @Override  
  58.     public View getView(int position, View convertView, ViewGroup parent)  
  59.     {  
  60.         // TODO Auto-generated method stub  
  61.         ViewHolder holder = null;  
  62.           
  63.         if(convertView == null)  
  64.         {  
  65.             LayoutInflater inflater = LayoutInflater.from(context);  
  66.             convertView = inflater.inflate(R.layout.item, null);  
  67.               
  68.             holder = new ViewHolder((ImageView)convertView.findViewById(R.id.imageView),  
  69.                     (TextView)convertView.findViewById(R.id.textView));  
  70.             convertView.setTag(holder);  
  71.         }  
  72.         else  
  73.         {  
  74.             holder = (ViewHolder) convertView.getTag();  
  75.         }  
  76.           
  77.         ImageView imageView = holder.getImageView();  
  78.   
  79.                 <span style="color:#FF0000;">imageView.setImageBitmap(null);</span>  
  80.   
  81.                 //根據圖片URL去查詢記憶體快取有沒有對應的Bitmap物件,並傳遞迴調方法,如果沒有,則等下載完畢回撥  
  82.         Bitmap bitmap = asyncLoader.loadBitmap(imageView,   
  83.                 bookList.get(position).getBook_pic(),  
  84.                 new ImageCallBack()  
  85.                 {  
  86.                     @Override  
  87.                     public void imageLoad(ImageView imageView, Bitmap bitmap)  
  88.                     {  
  89.                         // TODO Auto-generated method stub  
  90.                         imageView.setImageBitmap(bitmap);  
  91.                     }  
  92.                 });  
  93.           
  94.         if(bitmap == null)  
  95.         {  
  96.             imageView.setImageResource(R.drawable.ic_launcher);  
  97.         }  
  98.         else  
  99.         {  
  100.             imageView.setImageBitmap(bitmap);  
  101.         }  
  102.           
  103.         holder.getTextView().setText(bookList.get(position).getTitle());  
  104.           
  105.         return convertView;  
  106.     }  
  107. }  
  108. </span>  

 

在Adapter中,主要不同表現在

public View getView(int position, View convertView, ViewGroup parent)方法中(我這裡用到了一些優化方面的處理,會在其他時間再與大家分享,今天先不解釋),和非同步載入最相關的是這一段

  1. <span style="font-size:18px;">ImageView imageView = holder.getImageView();  
  2.         //根據圖片URL去查詢記憶體快取有沒有對應的Bitmap物件,並傳遞迴調方法,如果沒有,則等下載完畢回撥  
  3.     Bitmap bitmap = asyncLoader.loadBitmap(imageView,   
  4.             bookList.get(position).getBook_pic(),  
  5.             new ImageCallBack()  
  6.             {  
  7.                 @Override  
  8.                 public void imageLoad(ImageView imageView, Bitmap bitmap)  
  9.                 {  
  10.                     // TODO Auto-generated method stub  
  11.                     imageView.setImageBitmap(bitmap);  
  12.                 }  
  13.             });  
  14.           
  15.     if(bitmap == null)  
  16.     {  
  17.         imageView.setImageResource(R.drawable.ic_launcher);  
  18.     }  
  19.     else  
  20.     {  
  21.         imageView.setImageBitmap(bitmap);  
  22.     }</span>  

asyncLoader是我們定義的非同步載入類的物件,通過這個類的

public Bitmap loadBitmap(final ImageView imageView, final String imageURL, final ImageCallBack imageCallBack)

載入圖片,傳遞引數也與參考部落格有些不同,我覺得這樣更好理解一下,就是要顯示圖片的URL連結,和圖片要顯示對應的控制元件,當然最重要的還有這個介面實現的回撥物件,是線上程中下載完圖片之後,用以載入圖片的回撥物件。而這個回撥物件在傳遞的時候已經實現了介面方法,即將下載好的圖片繪製在對應的控制元件之中

  1. <span style="font-size:18px;">imageView.setImageBitmap(bitmap);</span>  

 

好了,今天就分享到這裡,有言辭不清楚的地方歡迎留言!

相關文章