zoukankan      html  css  js  c++  java
  • 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

    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    给JavaScript新手的24条实用建议
    javascript之HTML(select option)详解
    PHP的正则处理函数总结分析
    多级关联菜单:
    理解json两种结构:数组和对象
    dede标签学习笔记(一)
    Jewel_M PHP定时执行任务的实现
    网站刷新器
    PHP_SELF、 SCRIPT_NAME、 REQUEST_URI区别
    RemoveXSS()
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4618038.html
Copyright © 2011-2022 走看看