Android-异步图像装载机
在ListView加载图像是非常常见的场景,图像加载几个要求满足以下的:
(1)是否画面位于网络或本地上,装载不应同步。但应该异步加载,例如,使用AsyncTask。
(2)为了避免重复下载图片和网页显示速度,通常做缓存,比方最常见的LruCache。
(3)为了提高Listview的性能。我们通常会用holder来重用Listview的item。
代码大概就是这种:
public class MainActivity extends Activity { private ImageLoader imageLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageLoader = new ImageLoader(new ImageDownloader(){ @Override public 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() { @Override public void onClick(View v) { List<ItemBean> dataList = getDataList(); listview.setAdapter(new ListViewAdapter(MainActivity.this, dataList)); } }); } @Override protected 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; } @Override public int getCount() { return dataList.size(); } @Override public Object getItem(int position) { return dataList.get(position); } @Override public long getItemId(int position) { return position; } @Override public 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> { @Override protected 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(); } // 開始读取,放入cache if(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; } @Override protected 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已经被复用。相应到了新的key for (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); } @Override protected 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); } @Override public boolean equals(Object o) { ImageViewReference other=(ImageViewReference)o; return this.get()==other.get(); } @Override public int hashCode() { ImageView imageView = this.get(); if(imageView != null){ return imageView.hashCode(); } return 0; } } }源代码在这里:http://download.csdn.net/download/goldenfish1919/7320823
版权声明:本文博客原创文章,博客,未经同意,不得转载。