Android性能优化之实现双缓存的图片异步加载工具(LruCache+SoftReference) - 拿来即用

之前在郭大神的博客看到使用LruCache算法实现图片缓存的.这里仿效他的思路,自己也写了一个. 并加入ConcurrentHashMap<String, SoftReference<Bitmap>>去实现二级缓存,因为ConcurrentHashMap是多个锁的线程安全,支持高并发.很适合这种频繁访问读取内存的操作.


下面整个思路是,使用了系统提供的LruCache类做一级缓存, 大小为运行内存的1/8,当LruCache容量要满的时候,会自动将系统移除的图片放到二级缓存中,但为了避免OOM的问题,这里将SoftReference软引用加入来,当系统快要OOM的时候会自动清除里面的图片内存,当然内存充足时就会继续保存这些二级缓存的图片.强调一点,不要用SoftReference去做一级缓存,现在的java中垃圾回收加强了对SoftReference软引用的回收机制,它只适合临时的保存一些数据缓存,并不适合长期的(相对临时而言,并不是真正的长期).


直接上代码,拿来即用哦:

/**
 * Created on 3/11/2015
 * <br>图片异步加载工具(支持本地图片加载,网络图片URL和项目内图片资源加载) 
 * <br>支持双缓存: LruCache和SoftReference
 * @author Mr.Et
 *
 */
public class ImageLoadManager {
	/** 图片源类型: 文件,网络,资源ID **/
	public enum IMAGE_LOAD_TYPE
	{
		FILE_PATH,FILE_URL,FILE_RESOURCE_ID
	}
	
	private String TAG = "ImageLoadManager...";
	
	private Context context;
	
	private Set<ImageLoadTask> taskCollection;
	
	/** 最大内存 **/
	final static int maxCacheSize = (int)(Runtime.getRuntime().maxMemory() / 8);
	
	/** 建立线程安全,支持高并发的容器 **/
	private static ConcurrentHashMap<String, SoftReference<Bitmap>> currentHashmap
		= new ConcurrentHashMap<String, SoftReference<Bitmap>>();
	
	
	
	
	
	
	
	public ImageLoadManager(Context context)
	{
		super();
		this.context = context;
		taskCollection = new HashSet<ImageLoadManager.ImageLoadTask>();
	}
	
	private static LruCache<String, Bitmap> BitmapMemoryCache = new LruCache<String, Bitmap>(maxCacheSize)
	{
		@Override
		protected int sizeOf(String key, Bitmap value)
		{
			if(value != null)
			{
				return value.getByteCount();
				//return value.getRowBytes() * value.getHeight();	//旧版本的方法
			}
			else
			{
				return 0;
			}
		}
		
		//这个方法当LruCache的内存容量满的时候会调用,将oldValue的元素移除出来腾出空间给新的元素加入
		@Override
		protected void entryRemoved(boolean evicted, String key,Bitmap oldValue, Bitmap newValue)
		{
			if(oldValue != null)
			{
				// 当硬引用缓存容量已满时,会使用LRU算法将最近没有被使用的图片转入软引用缓存    
				currentHashmap.put(key, new SoftReference<Bitmap>(oldValue));
			}
		}
		
	};
	
	/**
	 * 针对提供图片资源ID来显示图片的方法
	 * @param loadType	图片加载类型
	 * @param imageResourceID	图片资源id
	 * @param imageView	显示图片的ImageView
	 */
	public void setImageView(IMAGE_LOAD_TYPE loadType, int imageResourceID, ImageView imageView)
	{
		if(loadType == IMAGE_LOAD_TYPE.FILE_RESOURCE_ID)
		{
//			if(ifResourceIdExist(imageResourceID))
//			{
//				imageView.setImageResource(imageResourceID);
//				
//			}else{	//映射无法获取该图片,则显示默认图片
//				imageView.setImageResource(R.drawable.pic_default);
//			}
			try 
			{
				imageView.setImageResource(imageResourceID);
				return;
			} catch (Exception e) {
				Log.e(TAG, "Can find the imageID of "+imageResourceID);
				e.printStackTrace();
			}
			//默认图片
			imageView.setImageResource(R.drawable.pic_default);
		}
	}
	
	/**
	 * 针对提供图片文件链接或下载链接来显示图片的方法
	 * @param loadType  图片加载类型
	 * @param imageFilePath	图片文件的本地文件地址或网络URL的下载链接
	 * @param imageView	显示图片的ImageView
	 */
	public void setImageView(IMAGE_LOAD_TYPE loadType, String imageFilePath, ImageView imageView)
	{
		if(imageFilePath == null || imageFilePath.trim().equals(""))
		{
			imageView.setImageResource(R.drawable.pic_default);
			
		}else{
			Bitmap bitmap = getBitmapFromMemoryCache(imageFilePath);
			if(bitmap != null)
			{
				imageView.setImageBitmap(bitmap);
			}
			else
			{
				imageView.setImageResource(R.drawable.pic_default);
				ImageLoadTask task = new ImageLoadTask(loadType, imageView);
				taskCollection.add(task);
				task.execute(imageFilePath);
			}
		}
	}
	
	/**
	 * 从LruCache中获取一张图片,如果不存在就返回null
	 * @param key  键值可以是图片文件的filePath,可以是图片URL地址
	 * @return Bitmap对象,或者null
	 */
	public Bitmap getBitmapFromMemoryCache(String key)
	{	
		try 
		{
			if(BitmapMemoryCache.get(key) == null)
			{
				if(currentHashmap.get(key) != null)
				{
					return currentHashmap.get(key).get();
				}
			}
			return BitmapMemoryCache.get(key);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return BitmapMemoryCache.get(key);
	}
	
	/**
	 * 将图片放入缓存
	 * @param key
	 * @param bitmap
	 */
	private void addBitmapToCache(String key, Bitmap bitmap)
	{
		BitmapMemoryCache.put(key, bitmap);
	}
	
	
	/**
	 * 图片异步加载
	 * @author Mr.Et
	 *
	 */
	private class ImageLoadTask extends AsyncTask<String, Void, Bitmap>
	{
		private String imagePath;
		private ImageView imageView;
		private IMAGE_LOAD_TYPE loadType;
		
		public ImageLoadTask(IMAGE_LOAD_TYPE loadType , ImageView imageView)
		{
			this.loadType = loadType;
			this.imageView = imageView;
		}
		
		@Override
		protected Bitmap doInBackground(String...params)
		{
			imagePath = params[0];
			try 
			{
				if(loadType == IMAGE_LOAD_TYPE.FILE_PATH)
				{
					if(new File(imagePath).exists())
					{	//从本地FILE读取图片
						BitmapFactory.Options opts = new BitmapFactory.Options();
						opts.inSampleSize = 2;
						Bitmap bitmap = BitmapFactory.decodeFile(imagePath, opts);
						//将获取的新图片放入缓存
						addBitmapToCache(imagePath, bitmap);
						return bitmap;
					}
					return null;
				}
				else if(loadType == IMAGE_LOAD_TYPE.FILE_URL)
				{	//从网络下载图片
					byte[] datas = getBytesOfBitMap(imagePath);
					if(datas != null)
					{
//						BitmapFactory.Options opts = new BitmapFactory.Options();
//						opts.inSampleSize = 2;
//						Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length, opts);
						Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length);
						addBitmapToCache(imagePath, bitmap);
						return bitmap;
					}
					return null;
				}
				
			} catch (Exception e) {
				e.printStackTrace();
				FileUtils.saveExceptionLog(e);
				//可自定义其他操作
			}
			return null;
		}
		
		@Override
		protected void onPostExecute(Bitmap bitmap)
		{
			try 
			{
				if(imageView != null)
				{
					if(bitmap != null)
					{
						imageView.setImageBitmap(bitmap);
					}
					else
					{
						Log.e(TAG, "The bitmap result is null...");
					}
				}
				else
				{
					Log.e(TAG, "The imageView is null...");
					//获取图片失败时显示默认图片
					imageView.setImageResource(R.drawable.pic_default);
				}
				
			} catch (Exception e) {
				e.printStackTrace();
				FileUtils.saveExceptionLog(e);
			}
		}
		
		
	}
	
	
	/**
	 * InputStream转byte[]
	 * @param inStream
	 * @return
	 * @throws Exception
	 */
	private byte[] readStream(InputStream inStream) throws Exception{  
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();  
        byte[] buffer = new byte[2048];  
        int len = 0;  
        while( (len=inStream.read(buffer)) != -1){  
            outStream.write(buffer, 0, len);  
        }  
        outStream.close();  
        inStream.close();  
        return outStream.toByteArray();  
    }
	
	/**
	 * 获取下载图片并转为byte[]
	 * @param urlStr
	 * @return
	 */
	private byte[] getBytesOfBitMap(String imgUrl){
		try {
			URL url = new URL(imgUrl);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setConnectTimeout(10 * 1000);  //10s
			conn.setReadTimeout(20 * 1000);
	        conn.setRequestMethod("GET");  
	        conn.connect();
	        InputStream in = conn.getInputStream();
	        return readStream(in);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 该资源ID是否有效
	 * @param resourceId 资源ID
	 * @return
	 */
	private boolean ifResourceIdExist(int resourceId)
	{
		try 
		{
			Field field = R.drawable.class.getField(String.valueOf(resourceId));
			Integer.parseInt(field.get(null).toString());
			return true;
			
		} catch (Exception e) {
			e.printStackTrace();
		} 
		return false;
	}
	
	/**
	 * 取消所有任务
	 */
	public void cancelAllTask()
	{
		if(taskCollection != null){
			for(ImageLoadTask task : taskCollection)
			{
				task.cancel(false);
			}
		}
	}
	
	
}

In addition, 如果需要更加完美的体验,还可以加入第三级的缓存机制, 比如将图片缓存到本地的磁盘存储空间中.但是又不想这些缓存在本地的图片被其他应用扫描到或者被用户看到怎么办? 这里有几个思路, 比如将图片用加密算法转为字符串存储,或者将图片转为自定义格式的未知文件去放在隐蔽的地方(很多应用都采取了这种方式). 这个不妨自己去尝试实现哦~




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。