標籤:android imageloader
在ListView中載入圖片是非常常見的情境,圖片的載入要滿足下面的幾個要求:
(1)不管圖片是位於網路還是本地,載入都不應該是同步的,而是應該非同步去載入,比如用AsyncTask。
(2)為了避免重複下載圖片和頁面展示的速度,一般要做緩衝,比如最常見的LruCache。
(3)為了提高Listview的效能,我們一般會用holder來重用Listview的item。
代碼大概就是這樣的:
public class MainActivity extends Activity {private ImageLoader imageLoader;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);imageLoader = new ImageLoader(new ImageDownloader(){@Overridepublic Bitmap download(String path, int width, int height) {return HttpUtil.download(path);}});final ListView listview = (ListView)this.findViewById(R.id.listview);Button btn = (Button)this.findViewById(R.id.btn);btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {List<ItemBean> dataList = getDataList();listview.setAdapter(new ListViewAdapter(MainActivity.this, dataList));}});}@Overrideprotected void onDestroy() {super.onDestroy();imageLoader.destory();}private class ListViewAdapter extends BaseAdapter{private Context context;private List<ItemBean> dataList;public ListViewAdapter(Context context, List<ItemBean> dataList){this.context = context;this.dataList = dataList;}@Overridepublic int getCount() {return dataList.size();}@Overridepublic Object getItem(int position) {return dataList.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {Holder holder = null;if(convertView == null){holder = new Holder();convertView = new ItemView(context);holder.itemView = (ItemView)convertView;convertView.setTag(holder);}else{holder = (Holder)convertView.getTag();}ItemView itemView = holder.itemView;ImageView itemImageView = itemView.getImageView();ItemBean item = dataList.get(position);// 先設定一個預設的圖片// 假如不設定,當頁面滑到了某個正在載入的item上,恰好這個item是複用的前面的已經顯示的item// 那麼這個item首先會顯示前一個item的圖片,等自己的下載完成以後,再替換掉這個圖片,// 假如下載時間很長,會讓使用者感覺圖片錯亂了!itemImageView.setImageResource(R.drawable.ic_launcher);//隨後下載實際的圖片imageLoader.loadImage(item.getImagePath(), 50, 50, itemImageView);return itemView;}class Holder{ItemView itemView;}}
現在問題就出現了,考慮下面的情境:
下載一幅圖片的時間很長,比如說10s,每一頁顯示3個item。
使用者第一次開啟頁面,第一頁應該展示item0,item1,item2。在item0還沒下載完的時候,使用者滑到了第3頁,第3頁應該展示的是item6,item7,item8。那麼這一頁的item肯定是重用的第一頁的那些item。此時,使用者等待頁面載入。假如,item6重用的是item0,item7重用的是item1,item8重用的是item2,當item0下載完成以後,item6上展示的是item0上的圖片,這就混亂了!只有當item6自己的圖片下載完以後,item6展示的才是正確的圖片!如果在載入的過程中,使用者不停的滑動,那麼使用者看到的頁面就是完全錯亂的!
本文的圖片載入器就可以避免這個問題,是一個同事寫的,感覺很不錯,就直接拿過來了,看下代碼:
public class ImageLoader {private static final String TAG = "ImageLoader";private ImageCache cache;private HashSet<String> cacheKeys = new HashSet<String>();private ImageDownloader downloader;// 儲存filepath和ImageView的關係,因為ImageView會複用,所以只有這個關係才是正確的關係// 一個imageView只能對應一個filepath,一個filepath對應一個物理檔案private WeakHashMap<ImageView, String> imageView2FileMap = new WeakHashMap<ImageView, String>();// 一個filepath可能對應多個imageView,因為有可能會有多個imageView顯示同一張圖片private HashMap<String, HashSet<ImageViewReference>> file2ImageViewMap = new HashMap<String, HashSet<ImageViewReference>>();// 正在讀的或者已經在列隊裡的filepath,讀完刪除private HashSet<String> fileInLoadSet = new HashSet<String>();public ImageLoader(ImageDownloader downloader) {if(downloader == null){throw new RuntimeException("ImageDownloader can not be null");}this.cache = ImageCache.getInstance();this.downloader = downloader;}/** * 給imageView設定圖片 * * @param filePath * 圖片路徑 * @param width * 寬 * @param height * 高 * @param imageView * @return 緩衝中有,直接設定,並返回true,沒有非同步讀取,讀完再設定,返回false */public boolean loadImage(String filePath, int width, int height, ImageView imageView) {String filePathKey = getKeyForFilePath(filePath, width, height);Bitmap bmp = cache.get(filePathKey);if (bmp == null) {ImageViewReference imageViewRef = new ImageViewReference(imageView);// 更新imageView和filepath的最新的關係imageView2FileMap.put(imageView, filePathKey);HashSet<ImageViewReference> imageViewSet = file2ImageViewMap.get(filePathKey);if (imageViewSet == null) {imageViewSet = new HashSet<ImageViewReference>();file2ImageViewMap.put(filePathKey, imageViewSet);}imageViewSet.add(imageViewRef);// 不會重複下載if (fileInLoadSet.contains(filePathKey)) {return false;} else {fileInLoadSet.add(filePathKey);}Holder holder = new Holder();holder.width = width;holder.height = height;holder.filePath = filePath;holder.filePathKey = filePathKey;holder.imageViewRef = imageViewRef;new ImageLoadTask().execute(holder);return false;} else {imageView.setImageBitmap(bmp);return true;}}private class ImageLoadTask extends AsyncTask<Holder, Void, Holder> {@Overrideprotected Holder doInBackground(Holder... params) {Holder holder = params[0];int width = holder.width;int height = holder.height;String filePath = holder.filePath;String filePathKey = holder.filePathKey;// 找到key對應的所有imageView,如果imageView的數量是0說明不用下載了int count = getCountOfImageViewForKey(filePathKey);if (count <= 0) {return null;}try {Random rnd = new Random();Thread.sleep((int) (1000 * rnd.nextDouble()));} catch (Exception e) {e.printStackTrace();}// 開始讀取,放入cacheif(downloader != null){//Bitmap bmp = ImageUtil.compressBitmap(filePath, width, height);Bitmap bmp = downloader.download(filePath, width, height);if(bmp != null){cache.put(filePathKey, bmp);cacheKeys.add(filePath);holder.imageData = bmp;}}return holder;}@Overrideprotected void onPostExecute(Holder holder) {super.onPostExecute(holder);// 讀完圖片,把key移除String filePathKey = holder.filePathKey;fileInLoadSet.remove(filePathKey);Bitmap data = holder.imageData;if(data == null){return;}ArrayList<ImageView> imageViewArrayList = getImageViewListForKey(filePathKey);if (imageViewArrayList.size() == 0) {return;}// 遍曆imageview列表,通過imageView2FileMap尋找該imageView對應的最新的latestFilePathKey是不是剛剛下載好的這個filePathKey// 只有一直才需要顯示,如果不一致,說明該imageView已經被複用,對應到了新的keyfor (ImageView imageView : imageViewArrayList) {String latestFilePathKey = imageView2FileMap.get(imageView);if (latestFilePathKey != null && latestFilePathKey.equals(filePathKey)) {if (imageView != null) {imageView.setImageBitmap(data);Log.e(TAG, "設定圖片 ");/* * boolean isSet; * try{ * isSet=(Boolean) * imageView.getTag(); * }catch(Exception e) { * isSet=true; * } * if(isSet) { * imageView.setImageBitmap(result); * Log.e(TAG,"設定圖片 "); * } */}// 即使不remove,也會自動回收imageView2FileMap.remove(imageView);} else {}}file2ImageViewMap.remove(filePathKey);}}class Holder {int width,height;String filePath, filePathKey;Bitmap imageData;ImageViewReference imageViewRef;}private String getKeyForFilePath(String imagePath, int width, int height) {return imagePath + "_" + width + "_" + height;}/** * 銷毀ImageLoader * * */public void clear(){imageView2FileMap.clear();file2ImageViewMap.clear();fileInLoadSet.clear();for(String cacheKey : cacheKeys){cache.remove(cacheKey);}cacheKeys.clear();imageView2FileMap = null;file2ImageViewMap = null;fileInLoadSet = null;cacheKeys = null;downloader = null;cache = null;}/** * 銷毀ImageLoader, 應用退出的時候調用 * * */public void destory() {clear();ImageCache.destroy();}public interface ImageDownloader{public Bitmap download(String path,int width, int height);}/** * 通過file2ImageViewMap擷取filePath對應的所有imageView列表 同時刪除被回收的imageView, * * @param filePathKey * @return */private ArrayList<ImageView> getImageViewListForKey(String filePathKey) {ArrayList<ImageView> imageViewArrayList = new ArrayList<ImageView>();HashSet<ImageViewReference> imageViewReferences = file2ImageViewMap.get(filePathKey);if(imageViewReferences == null){return null;}Iterator<ImageViewReference> it = imageViewReferences.iterator();while (it.hasNext()) {ImageViewReference reference = it.next();if (reference.get() != null) {imageViewArrayList.add(reference.get());} else {it.remove();}}return imageViewArrayList;}/** * 擷取指定的filePath對應的有效imageView的數量 * * @param filePathKey * @return */private int getCountOfImageViewForKey(String filePathKey) {ArrayList<ImageView> imageViewArrayList = getImageViewListForKey(filePathKey);if(imageViewArrayList == null){return 0;}else{return imageViewArrayList.size();}}private static class ImageCache extends LruCache<String, Bitmap> {private static final int cacheSize = 10 * 1024 * 1024;private static ImageCache instance = new ImageCache(cacheSize);public static ImageCache getInstance(){return instance;}private ImageCache(int maxSize) {super(maxSize);}@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount();}public static void destroy(){if(instance == null){return;}instance.evictAll();instance = null;}}private static class ImageViewReference extends WeakReference<ImageView> {public ImageViewReference(ImageView r) {super(r);}@Overridepublic boolean equals(Object o) {ImageViewReference other=(ImageViewReference)o;return this.get()==other.get();}@Overridepublic int hashCode() {ImageView imageView = this.get();if(imageView != null){return imageView.hashCode();}return 0;}}}
源碼在這裡:http://download.csdn.net/download/goldenfish1919/7320823