Android之图片缓存管理
来源:互联网 发布:电脑软件不在桌面上 编辑:程序博客网 时间:2024/05/19 19:15
如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就从缓存中加载就可以了。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。
因此,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;
整个缓存策略是使用弱引用缓存和强引用缓存配合使用,并结合LRUCache,在尽可能地利用缓存的基础上,也大大提高了缓存命中率。我个人觉得这个类有改进的地方,比如,当LRUCache在移除元素的时候,默认是直接删除掉。这里更好的方式是重写LRUCache的entryRemoved方法,使得强引用缓存满的时候,会根据LRU算法将最近最久没有被使用的图片自动移入弱引用缓存,如下:
因此,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;
3、从网络下载图片,并更新到内存缓存和文件缓存。
后两个步骤纯碎属于业务逻辑,暂且不表,这里来看一下手Q使用的图片缓存管理策略。
说到缓存管理,首先谈一下java中的强引用和弱引用
- 强引用:最普遍的引用,若一个对象具有强引用,那么GC绝不会回收它。如A a = new A()
- 弱引用: 弱引用又分为以下三类:
- 软引用(SoftReference): 这类引用只有当内存空间不足GC才会回收它
- 弱引用(WeakReference): 这类引用拥有更短的生命周期,GC扫描过程中一旦发现了此类引用,不管当前内存是否足够,立即回收
- 虚引用(PhantomRefence): 这类引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,则任何时刻都可能被回收
下面来看看这样一个图片缓存类,为了更大限度使用缓存,它使用了强引用缓存(强引用)和弱引用缓存(弱引用)双重缓存,强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入弱引用缓存。**
ImageCache.javapublic class ImageCache { private static final String TAG = "ImageCache"; //CustomLruCache是一个继承了LruCache的继承类,它代表强引用缓存,它的缓存大小一般由业务方提供 private CustomLruCache<String, Drawable> mMemoryCache;// Default memory cache size //这里设置的是弱引用缓存以及它所占据的空间大小 private static final int DEFAULT_MEM_CACHE_SIZE = 5; // 5MB private final HashMap<String, WeakReference<Drawable>> mRefCache = new HashMap<String, WeakReference<Drawable>>(); public ImageCache(int memSize) { memSize = Math.max(memSize, DEFAULT_MEM_CACHE_SIZE); QLog.d(TAG, "Memory cache size = " + memSize + "MB"); mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) { //这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数 @Override protected int sizeOf(String key, Drawable drawable) { if (drawable instanceof BitmapDrawable) { Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); if (bitmap != null) { //若是bitmap位图则直接计算它的大小 return bitmap.getRowBytes() * bitmap.getHeight(); } return 0; } else if (drawable instanceof AnimationDrawable) { //若是逐帧动画,则首先获取它所有的帧数,再计算总共的大小 AnimationDrawable anim = (AnimationDrawable) drawable; int count = anim.getNumberOfFrames(); int memSize = 0; for (int i = 0; i < count; i++) { Drawable dr = anim.getFrame(i); if (dr instanceof BitmapDrawable) { Bitmap bitmap = ((BitmapDrawable) dr).getBitmap(); if (bitmap != null) { memSize += bitmap.getRowBytes() * bitmap.getHeight(); } } } return memSize; } return 0; } }; } //从缓存中获取图片 public Drawable getImageFromMemCache(String key) { Drawable memDrawable = null; if (mMemoryCache != null) { //首先从强引用缓存中获取图片,若找到的话,把元素移动到CustomLruCache的最后面,从而保证它在LRU算法中最后被删除? //疑问,其实LinkedHashMap本身就存在LRU的算法机制,因此,get的时候,会自动移入到队列尾部 memDrawable = mMemoryCache.remove(key); if (memDrawable != null) { memDrawable = memDrawable.getConstantState().newDrawable(); mMemoryCache.put(key, memDrawable); return memDrawable; } } //强引用缓存中没有找到,开始在弱引用缓存中查找 WeakReference<Drawable> ref = mRefCache.get(key); if (ref != null) { //若找到的话,这里是否添加一步,将其从弱引用缓存移入强引用缓存中比较好 memDrawable = ref.get(); if (memDrawable == null) { mRefCache.remove(key); } } return memDrawable; } //添加图片到缓存,这里不理解为什么要向强引用缓存和弱引用缓存都要添加一份 public void addImageToCache(String data, Drawable drawable) { // Add to memory cache if (mMemoryCache != null && mMemoryCache.get(data) == null) { mMemoryCache.put(data, drawable); mRefCache.put(data, new WeakReference<Drawable>(drawable)); } } //从缓存中删除资源 public void removeImageFromCache(String data) { if (mRefCache != null) { mRefCache.remove(data); } if (mMemoryCache != null) { mMemoryCache.remove(data); } } public Drawable getImageFromDiskCache(String pathName) { // TODO 暂不支持disk cache return null; } public void clearCaches() { // mDiskCache.clearCache(); mMemoryCache.evictAll(); mRefCache.clear(); }}
整个缓存策略是使用弱引用缓存和强引用缓存配合使用,并结合LRUCache,在尽可能地利用缓存的基础上,也大大提高了缓存命中率。我个人觉得这个类有改进的地方,比如,当LRUCache在移除元素的时候,默认是直接删除掉。这里更好的方式是重写LRUCache的entryRemoved方法,使得强引用缓存满的时候,会根据LRU算法将最近最久没有被使用的图片自动移入弱引用缓存,如下:
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) { //这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数 @Override protected int sizeOf(String key, Drawable drawable) { ......... } 当强引用缓存满时,会自动调用这个方法,这时候,将移除的元素添加到弱引用缓存中 @Override protected void entryRemoved(boolean evicted, String key, Drawable oldDrawable, Drawable newDrawable) { if (oldDawable != null) { mRefCache.put(data, new WeakReference<Drawable>(oldDawable)); } } };
接下来看内存缓存类:ImageMemoryCachepublic class ImageMemoryCache { private static final int SOFT_CACHE_SIZE = 15; //软引用缓存容量 private static LruCache mLruCache; //硬引用缓存 private static LinkedHashMap> mSoftCache; //软引用缓存 public ImageMemoryCache(Context context) { int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); int cacheSize = 1024 * 1024 * memClass / 4; //硬引用缓存容量,为系统可用内存的1/4 mLruCache = new LruCache(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { if (value != null) return value.getRowBytes() * value.getHeight(); else return 0; } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { if (oldValue != null) // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存 mSoftCache.put(key, new SoftReference(oldValue)); } }; mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) { private static final long serialVersionUID = 6040103833179403725L; @Override protected boolean removeEldestEntry(Entry> eldest) { if (size() > SOFT_CACHE_SIZE){ return true; } return false; } }; } public Bitmap getBitmapFromCache(String url) { Bitmap bitmap; //先从硬引用缓存中获取 synchronized (mLruCache) { bitmap = mLruCache.get(url); if (bitmap != null) { //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除 mLruCache.remove(url); mLruCache.put(url, bitmap); return bitmap; } } //如果硬引用缓存中找不到,到软引用缓存中找 synchronized (mSoftCache) { SoftReference bitmapReference = mSoftCache.get(url); if (bitmapReference != null) { bitmap = bitmapReference.get(); if (bitmap != null) { //将图片移回硬缓存 mLruCache.put(url, bitmap); mSoftCache.remove(url); return bitmap; } else { mSoftCache.remove(url); } } } return null; } public void addBitmapToCache(String url, Bitmap bitmap) { if (bitmap != null) { synchronized (mLruCache) { mLruCache.put(url, bitmap); } } } public void clearCache() { mSoftCache.clear(); }}接下来看内存缓存类:ImageMemoryCachepublic class ImageFileCache { private static final String CACHDIR = "ImgCach"; private static final String WHOLESALE_CONV = ".cach"; private static final int MB = 1024*1024; private static final int CACHE_SIZE = 10; private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10; public ImageFileCache() { //清理文件缓存 removeCache(getDirectory()); } public Bitmap getImage(final String url) { final String path = getDirectory() + "/" + convertUrlToFileName(url); File file = new File(path); if (file.exists()) { Bitmap bmp = BitmapFactory.decodeFile(path); if (bmp == null) { file.delete(); } else { updateFileTime(path); return bmp; } } return null; } public void saveBitmap(Bitmap bm, String url) { if (bm == null) { return; } //判断sdcard上的空间 if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { //SD空间不足 return; } String filename = convertUrlToFileName(url); String dir = getDirectory(); File dirFile = new File(dir); if (!dirFile.exists()) dirFile.mkdirs(); File file = new File(dir +"/" + filename); try { file.createNewFile(); OutputStream outStream = new FileOutputStream(file); bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream); outStream.flush(); outStream.close(); } catch (FileNotFoundException e) { Log.w("ImageFileCache", "FileNotFoundException"); } catch (IOException e) { Log.w("ImageFileCache", "IOException"); } } private boolean removeCache(String dirPath) { File dir = new File(dirPath); File[] files = dir.listFiles(); if (files == null) { return true; } if (!android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) { return false; } int dirSize = 0; for (int i = 0; i < files.length; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { dirSize += files[i].length(); } } if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { int removeFactor = (int) ((0.4 * files.length) + 1); Arrays.sort(files, new FileLastModifSort()); for (int i = 0; i < removeFactor; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { files[i].delete(); } } } if (freeSpaceOnSd() <= CACHE_SIZE) { return false; } return true; } public void updateFileTime(String path) { File file = new File(path); long newModifiedTime = System.currentTimeMillis(); file.setLastModified(newModifiedTime); } private int freeSpaceOnSd() { StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB; return (int) sdFreeMB; } private String convertUrlToFileName(String url) { String[] strs = url.split("/"); return strs[strs.length - 1] + WHOLESALE_CONV; } private String getDirectory() { String dir = getSDPath() + "/" + CACHDIR; return dir; } private String getSDPath() { File sdDir = null; boolean sdCardExist = Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED); //判断sd卡是否存在 if (sdCardExist) { sdDir = Environment.getExternalStorageDirectory(); //获取根目录 } if (sdDir != null) { return sdDir.toString(); } else { return ""; } } private class FileLastModifSort implements Comparator { public int compare(File arg0, File arg1) { if (arg0.lastModified() > arg1.lastModified()) { return 1; } else if (arg0.lastModified() == arg1.lastModified()) { return 0; } else { return -1; } } } }从网络获取图片:public class ImageGetFromHttp { private static final String LOG_TAG = "ImageGetFromHttp"; public static Bitmap downloadBitmap(String url) { final HttpClient client = new DefaultHttpClient(); final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; try { inputStream = entity.getContent(); FilterInputStream fit = new FlushedInputStream(inputStream); return BitmapFactory.decodeStream(fit); } finally { if (inputStream != null) { inputStream.close(); inputStream = null; } entity.consumeContent(); } } } catch (IOException e) { getRequest.abort(); Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e); } catch (IllegalStateException e) { getRequest.abort(); Log.w(LOG_TAG, "Incorrect URL: " + url); } catch (Exception e) { getRequest.abort(); Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e); } finally { client.getConnectionManager().shutdown(); } return null; } static class FlushedInputStream extends FilterInputStream { public FlushedInputStream(InputStream inputStream) { super(inputStream); } @Override public long skip(long n) throws IOException { long totalBytesSkipped = 0L; while (totalBytesSkipped < n) { long bytesSkipped = in.skip(n - totalBytesSkipped); if (bytesSkipped == 0L) { int b = read(); if (b < 0) { break; // we reached EOF } else { bytesSkipped = 1; // we read one byte } } totalBytesSkipped += bytesSkipped; } return totalBytesSkipped; } }}最后,获取一张图片的流程就如下代码所示:public Bitmap getBitmap(String url) { // 从内存缓存中获取图片 Bitmap result = memoryCache.getBitmapFromCache(url); if (result == null) { // 文件缓存中获取 result = fileCache.getImage(url); if (result == null) { // 从网络获取 result = ImageGetFromHttp.downloadBitmap(url); if (result != null) { fileCache.saveBitmap(result, url); memoryCache.addBitmapToCache(url, result); } } else { // 添加到内存缓存 memoryCache.addBitmapToCache(url, result); } } return result;}
0 0
- Android之图片缓存管理
- Android缓存:图片缓存管理
- android 图片缓存管理
- Android图片缓存管理
- Android图片缓存管理
- Android之图片缓存
- Android之图片缓存
- android图片缓存之softReference
- android 图片缓存之 createBitmap
- Android之缓存网络图片
- Android之图片缓存数据流
- Android技术积累:图片缓存管理
- Android技术积累:图片缓存管理
- Android技术积累:图片缓存管理
- Android技术积累:图片缓存管理
- Android技术积累:图片缓存管理
- Android技术积累:图片缓存管理
- Android技术积累:图片缓存管理
- C++中继承覆写导致基类的成员不可见
- 温故知新: 10个最常见的HTML5面试题
- 编程语言类型(转)
- Rails读书笔记第四章
- 数据库框架FMDB的使用
- Android之图片缓存管理
- Javah Error android.app.Activity not found的问题
- java中反射的用法
- hihoCoder挑战赛15 Best Route in a Grid
- J2EE Web项目Tomcat跑不起来的一些问题解决过程汇总
- Vmware中安装ubuntu无法通过NAT上网
- Smarty模板学习入门教程
- react要点解析
- 设计模式--迪米特法则