首页 > 代码库 > Android-异步图片加载器

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 = http://www.mamicode.com/bmp;>源码在这里:http://download.csdn.net/download/goldenfish1919/7320823