首先宣告,參考部落格地址:http://www.iteye.com/topic/685986
對於ListView,相信很多人都很熟悉,因為確實太常見了,所以,做的使用者體驗更好,就成了我們的追求。。。
常見的ListView中很少全是文字的,一般都是圖文共存的,而圖片的來源是伺服器端(很少有寫在客戶端的吧。。。考慮客戶端的大小和更新的問題),所以,網路問題就成了圖片是否能順利載入成功的決定性因素了。大家都知道每次啟動一個Android應用,都會啟動一個UI主執行緒,主要是響應使用者的互動,如果我們把不確定的獲取網路圖片的操作放在UI主執行緒,結果也就不確定了。。。當然,如果你網路足夠好的話,應該問題不大,但是,網路誰能保證呢?所以就出現了“非同步載入”的方法!
我先敘述一下非同步載入的原理,說的通俗一點就是UI主執行緒繼續做與使用者互動的響應監聽和操作,而載入圖片的任務交到其他執行緒中去做,當圖片載入完成之後,再跟據某種機制(比如回撥)繪製到要顯示的控制元件中。
首先,貼出AsyncBitmapLoader.java,這個類是關鍵,主要做的就是當載入圖片的時候,去緩衝區查詢,如果有的話,則立刻返回Bitmap物件,省掉再去網路伺服器下載的時間和流量。
- <span style="font-size:18px;">package onerain.ald.async;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.ref.SoftReference;
- import java.util.HashMap;
- import onerain.ald.common.HttpUtils;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Handler;
- import android.os.Message;
- import android.widget.ImageView;
- /**
- * @author oneRain
- **/
- public class AsyncBitmapLoader
- {
- /**
- * 記憶體圖片軟引用緩衝
- */
- private HashMap<String, SoftReference<Bitmap>> imageCache = null;
- public AsyncBitmapLoader()
- {
- imageCache = new HashMap<String, SoftReference<Bitmap>>();
- }
- public Bitmap loadBitmap(final ImageView imageView, final String imageURL, final ImageCallBack imageCallBack)
- {
- //在記憶體快取中,則返回Bitmap物件
- if(imageCache.containsKey(imageURL))
- {
- SoftReference<Bitmap> reference = imageCache.get(imageURL);
- Bitmap bitmap = reference.get();
- if(bitmap != null)
- {
- return bitmap;
- }
- }
- else
- {
- /**
- * 加上一個對本地快取的查詢
- */
- String bitmapName = imageURL.substring(imageURL.lastIndexOf("/") + 1);
- File cacheDir = new File("/mnt/sdcard/test/");
- File[] cacheFiles = cacheDir.listFiles();
- int i = 0;
- for(; i<cacheFiles.length; i++)
- {
- if(bitmapName.equals(cacheFiles[i].getName()))
- {
- break;
- }
- }
- if(i < cacheFiles.length)
- {
- return BitmapFactory.decodeFile("/mnt/sdcard/test/" + bitmapName);
- }
- }
- final Handler handler = new Handler()
- {
- /* (non-Javadoc)
- * @see android.os.Handler#handleMessage(android.os.Message)
- */
- @Override
- public void handleMessage(Message msg)
- {
- // TODO Auto-generated method stub
- imageCallBack.imageLoad(imageView, (Bitmap)msg.obj);
- }
- };
- //如果不在記憶體快取中,也不在本地(被jvm回收掉),則開啟執行緒下載圖片
- new Thread()
- {
- /* (non-Javadoc)
- * @see java.lang.Thread#run()
- */
- @Override
- public void run()
- {
- // TODO Auto-generated method stub
- InputStream bitmapIs = HttpUtils.getStreamFromURL(imageURL);
- Bitmap bitmap = BitmapFactory.decodeStream(bitmapIs);
- imageCache.put(imageURL, new SoftReference<Bitmap>(bitmap));
- Message msg = handler.obtainMessage(0, bitmap);
- handler.sendMessage(msg);
- File dir = new File("/mnt/sdcard/test/");
- if(!dir.exists())
- {
- dir.mkdirs();
- }
- File bitmapFile = new File("/mnt/sdcard/test/" +
- imageURL.substring(imageURL.lastIndexOf("/") + 1));
- if(!bitmapFile.exists())
- {
- try
- {
- bitmapFile.createNewFile();
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- FileOutputStream fos;
- try
- {
- fos = new FileOutputStream(bitmapFile);
- bitmap.compress(Bitmap.CompressFormat.JPEG,
- 100, fos);
- fos.close();
- }
- catch (FileNotFoundException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }.start();
- return null;
- }
- /**
- * 回撥介面
- * @author onerain
- *
- */
- public interface ImageCallBack
- {
- public void imageLoad(ImageView imageView, Bitmap bitmap);
- }
- }
- </span>
PS:我這裡用到了兩個緩衝,一是記憶體快取,一個是本地快取(即SD卡快取),其中用到了SoftReference,這個類的主要作用是生成一個“軟引用”,你可以認為是一種隨時會由於JVM垃圾回收機制回收掉的Map物件(而平時我們所用到的引用不釋放的話不會被JVM回收),之所以用到軟引用,就是考慮到android對圖片的快取是有大小限制的,當超過這個大小時,就一定要釋放,如果你用引用,保持不釋放的話,那麼FC(Force close)就離你不遠了。。。我這裡還用到了一個本地快取的機制,是和參考部落格不太一樣的地方,只是提供一種思路,方法還沒有完善(主要因為和伺服器還沒約定好關於圖片的命名規則),主要作用是在使用者瀏覽過大量圖片之後(超過記憶體快取容量之後),保留在本地,一是為了提高讀取速度,二是可以減少流量消耗!
這個類設計好之後,在自定義的Adapter當中比之前會有些不同,先上程式碼再解釋
- <span style="font-size:18px;">package onerain.ald.adapter;
- import java.util.List;
- import onerain.ald.R;
- import onerain.ald.async.AsyncBitmapLoader;
- import onerain.ald.async.AsyncBitmapLoader.ImageCallBack;
- import onerain.ald.entity.BaseBookEntity;
- import onerain.ald.holder.ViewHolder;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
- /**
- * @author oneRain
- **/
- public class ListAdapter extends BaseAdapter
- {
- private Context context = null;
- private List<BaseBookEntity> bookList = null;
- private AsyncBitmapLoader asyncLoader = null;
- public ListAdapter(Context context, List<BaseBookEntity> bookList)
- {
- this.context = context;
- this.bookList = bookList;
- this.asyncLoader = new AsyncBitmapLoader();
- }
- @Override
- public int getCount()
- {
- // TODO Auto-generated method stub
- return bookList.size();
- }
- @Override
- public Object getItem(int position)
- {
- // TODO Auto-generated method stub
- return bookList.get(position);
- }
- @Override
- public long getItemId(int position)
- {
- // TODO Auto-generated method stub
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent)
- {
- // TODO Auto-generated method stub
- ViewHolder holder = null;
- if(convertView == null)
- {
- LayoutInflater inflater = LayoutInflater.from(context);
- convertView = inflater.inflate(R.layout.item, null);
- holder = new ViewHolder((ImageView)convertView.findViewById(R.id.imageView),
- (TextView)convertView.findViewById(R.id.textView));
- convertView.setTag(holder);
- }
- else
- {
- holder = (ViewHolder) convertView.getTag();
- }
- ImageView imageView = holder.getImageView();
- <span style="color:#FF0000;">imageView.setImageBitmap(null);</span>
- //根據圖片URL去查詢記憶體快取有沒有對應的Bitmap物件,並傳遞迴調方法,如果沒有,則等下載完畢回撥
- Bitmap bitmap = asyncLoader.loadBitmap(imageView,
- bookList.get(position).getBook_pic(),
- new ImageCallBack()
- {
- @Override
- public void imageLoad(ImageView imageView, Bitmap bitmap)
- {
- // TODO Auto-generated method stub
- imageView.setImageBitmap(bitmap);
- }
- });
- if(bitmap == null)
- {
- imageView.setImageResource(R.drawable.ic_launcher);
- }
- else
- {
- imageView.setImageBitmap(bitmap);
- }
- holder.getTextView().setText(bookList.get(position).getTitle());
- return convertView;
- }
- }
- </span>
在Adapter中,主要不同表現在
public View getView(int position, View convertView, ViewGroup parent)方法中(我這裡用到了一些優化方面的處理,會在其他時間再與大家分享,今天先不解釋),和非同步載入最相關的是這一段
- <span style="font-size:18px;">ImageView imageView = holder.getImageView();
- //根據圖片URL去查詢記憶體快取有沒有對應的Bitmap物件,並傳遞迴調方法,如果沒有,則等下載完畢回撥
- Bitmap bitmap = asyncLoader.loadBitmap(imageView,
- bookList.get(position).getBook_pic(),
- new ImageCallBack()
- {
- @Override
- public void imageLoad(ImageView imageView, Bitmap bitmap)
- {
- // TODO Auto-generated method stub
- imageView.setImageBitmap(bitmap);
- }
- });
- if(bitmap == null)
- {
- imageView.setImageResource(R.drawable.ic_launcher);
- }
- else
- {
- imageView.setImageBitmap(bitmap);
- }</span>
asyncLoader是我們定義的非同步載入類的物件,通過這個類的
public Bitmap loadBitmap(final ImageView imageView, final String imageURL, final ImageCallBack imageCallBack)
載入圖片,傳遞引數也與參考部落格有些不同,我覺得這樣更好理解一下,就是要顯示圖片的URL連結,和圖片要顯示對應的控制元件,當然最重要的還有這個介面實現的回撥物件,是線上程中下載完圖片之後,用以載入圖片的回撥物件。而這個回撥物件在傳遞的時候已經實現了介面方法,即將下載好的圖片繪製在對應的控制元件之中
- <span style="font-size:18px;">imageView.setImageBitmap(bitmap);</span>
好了,今天就分享到這裡,有言辭不清楚的地方歡迎留言!