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

标签: android 性能 优化 | 发表时间:2015-03-11 00:32 | 作者:stzy00
分享到:
出处:http://blog.csdn.net

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




作者:stzy00 发表于2015/3/11 0:32:42 原文链接
阅读:58 评论:0 查看评论

相关 [android 性能 优化] 推荐:

Android 性能优化

- - CSDN博客综合推荐文章
如果应用程序需要使用Service来执行后台任务的话,只有当任务正在执行的时候才应该让Service运行起来. 当启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,系统可以在LRUcache当中缓存的进程数量也会减少,导致切换程序的时候耗费更多性能. 我们可以使用IntentService,当后台任务执行结束后会自动停止,避免了Service的内存泄漏.

Android性能优化典范

- - 移动开发 - ITeye博客
2015新年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App. 课程专题不仅仅介绍了Android系统中有关性能问题的底层工作原理,同时也介绍了如何通过工具来找出性能问题以及提升性能的建议. 主要从三个方面展开,Android的渲染机制,内存与GC,电量优化.

Android-性能优化-内存优化

- - CSDN博客移动开发推荐文章
Android-性能优化-内存优化. 详见: JVM 内存分配机制. 详见: JVM 垃圾回收机制. Dalvik 虚拟机(DVM)是 Android 系统在 java虚拟机(JVM)基础上优化得到的,DVM 是基于寄存器的,而 JVM 是基于栈的,由于寄存器高效快速的特性,DVM 的性能相比 JVM 更好.

Android性能优化-线程性能优化

- - CSDN博客推荐文章
熟练使用Android上的线程可以帮助你提高应用程序的性能. 本篇文章讨论了使用线程的几个方面:使用UI或主线程; 应用程序生命周期和线程优先级之间的关系; 以及平台提供的帮助管理线程复杂性的方法. 在每一部分,本篇都描述了潜在的陷阱以及如何避免它们的策略. 当用户启动你的应用程序时,Android会创建一个新的  Linux process 以及一个执行线程.

Android开发性能优化简介

- - CSDN博客推荐文章
       随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要求远远高于PC的桌面应用程序. 以上理由,足以需要开发人员更加专心去实现和优化你的代码了. 选择合适的算法和数据结构永远是开发人员最先应该考虑的事情.

Google《Android性能优化》学习笔记

- - 外刊IT评论
现在有不少App为了达到很华丽的视觉效果,会需要在界面上层叠很多的视图组件,但是这会很容易引起性能问题. 如何平衡Design与Performance就很需要智慧了. 大多数手机的屏幕刷新频率是60hz,如果在1000/60=16.67ms内没有办法把这一帧的任务执行完毕,就会发生丢帧的现象. 丢帧越多,用户感受到的卡顿情况就越严重.

Android性能优化之渲染篇

- - 移动开发 - ITeye博客
关注微信号:javalearns   随时随地学Java. Google近期在Udacity上发布了Android性能优化的在线课程,分别从渲染,运算与内存,电量几个方面介绍了如何去优化性能,这些课程是Google之前在Youtube上发布的Android性能优化典范专题课程的细化与补充. 下面是渲染篇章的学习笔记,部分内容和前面的性能优化典范有重合,欢迎大家一起学习交流.

android性能优化实战理论篇

- - CSDN博客推荐文章
本文地址:http://blog.csdn.net/iamws/article/details/51636175.          通过之前前篇介绍的工具,我们知道了应该怎么样去获取要分析的数据,但是也仅仅局限在于怎么样获取数据,而没有深入数据分析,这一篇主要讲解的是UI刷新这块部分android理论知识,有了这些知识后,对于上面的数据该怎么分析,你就胸有成竹了.

Android性能优化之内存泄漏

- - CSDN博客推荐文章
  内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存. 那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况. 在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现.

Android性能优化篇:从代码角度进行优化

- - 移动开发 - ITeye博客
关注微信号:javalearns   随时随地学Java. 通常我们写程序,都是在项目计划的压力下完成的,此时完成的代码可以完成具体业务逻辑,但是性能不一定是最优化的. 一般来说,优秀的 程序员在写完代码之后都会不断的对代码进行重构. 重构的好处有很多,其中一点,就是对代码进行优化,提高软件的性能.