前言
在博客:Android大图加载内存优化(如何防止OutOfMemmory)中讲解了在加载图片的时候内存不完全加载原图或预估图片的大小,加载合适的尺寸的图片防止OOM。接下来讲解图片文件的本地缓存,网络图片必须经过本地缓存,才能提高资源的访问速度,内存的缓存必须配合SDCard的缓存,才能发挥它的优势。本文采用的是LRU本地缓存策略,由于本文侧重的是文件的缓存,所以没有引入内存的缓存,也没有发挥出前一篇博客降到的图片加载优势,不过在后续的博客中我将不断完善整个项目,带领大家一起揭秘第三方图片加载库。
LRU算法
在图片的加载中,还有一个重要的步骤,是网络图片的本地缓存,很多时候不知道缓存的图片不知道何时删除,这时候需要一个合理的本地图片缓存策略,保证图片文件不会无限制的占用存储空间导致存储空间不足,造成资源的浪费。在计算机操作系统里边对任务的调度引入了LRU算法 。通俗的讲就是把就是把最长时间内未使用的资源优先级放到最低,优先保证使用频率高的资源。
图片本地缓存核心代码
增加一个配置类,可以根据这个类配置图片文件的缓存位置,大小等参数,后续可能需要的配置都要在这个里边扩展。
/** * Created by CJstar on 15/8/24. */public final class FileCacheOptions { /** * the file cache root path */ private String cacheRootPath; /** * file cache count */ private int maxFileCount; /** * file cache max size: byte */ private int maxCacheSize; /** * if it is false, will not cache files */ private boolean isUseFileCache = true; public String getCacheRootPath() { return cacheRootPath; } public void setCacheRootPath(String cacheRootPath) { this.cacheRootPath = cacheRootPath; } public int getMaxFileCount() { return maxFileCount; } public void setMaxFileCount(int maxFileCount) { this.maxFileCount = maxFileCount; } /** * cache size in bytes * @return */ public int getMaxCacheSize() { return maxCacheSize; } public void setMaxCacheSize(int maxCacheSize) { this.maxCacheSize = maxCacheSize; } public boolean isUseFileCache() { return isUseFileCache; } public void setIsUseFileCache(boolean isUseFileCache) { this.isUseFileCache = isUseFileCache; } private FileCacheOptions(Builder builder){ setCacheRootPath(builder.getCacheRootPath()); setIsUseFileCache(builder.isUseFileCache()); setMaxCacheSize(builder.getMaxCacheSize()); setMaxFileCount(builder.getMaxFileCount()); } /** * This is the options set builder, we can create the options by this method */ public static class Builder{ private String cacheRootPath; private int maxFileCount; private int maxCacheSize; private boolean isUseFileCache; public Builder(){ } public String getCacheRootPath() { return cacheRootPath; } public Builder setCacheRootPath(String cacheRootPath) { this.cacheRootPath = cacheRootPath; return this; } public int getMaxFileCount() { return maxFileCount; } public Builder setMaxFileCount(int maxFileCount) { this.maxFileCount = maxFileCount; return this; } public int getMaxCacheSize() { return maxCacheSize; } public Builder setMaxCacheSize(int maxCacheSize) { this.maxCacheSize = maxCacheSize; return this; } public boolean isUseFileCache() { return isUseFileCache; } public Builder setIsUseFileCache(boolean isUseFileCache) { this.isUseFileCache = isUseFileCache; return this; } public FileCacheOptions builder(){ return new FileCacheOptions(this); } }}
接着就是核心的处理类:
/** * Created by CJstar on 15/8/24. */public class LRUFileCache implements FileCache { /** * cache config */ private FileCacheOptions options; /** * cache file suffix */ private static final String WHOLESALE_CONV = ".cach"; /** * mini free space on SDCard */ private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10*1024*1024; private static LRUFileCache mLRUFileCache; public static LRUFileCache getInstance(){ if(mLRUFileCache==null){ synchronized (LRUFileCache.class){ if(mLRUFileCache==null){ mLRUFileCache = new LRUFileCache(); } } } return mLRUFileCache; } public void setFileLoadOptions(FileCacheOptions options) { this.options = options; } /** * use default options */ private LRUFileCache() { this.options = new FileCacheOptions.Builder() .setCacheRootPath("FileCache") .setIsUseFileCache(true) .setMaxCacheSize(10 * 1024 * 1024)//10MB .setMaxFileCount(100) .builder(); } @Override public void addDiskFile(String key, InputStream inputStream) { if (TextUtils.isEmpty(key) || inputStream == null) { return; } String filename = convertUrlToFileName(key); String dir = options.getCacheRootPath(); File dirFile = new File(dir); if (!dirFile.exists()) dirFile.mkdirs(); File file = new File(dir + "/" + filename); OutputStream outStream; try { if(file.exists()){ file.delete(); } file.createNewFile(); outStream = new FileOutputStream(file); while (inputStream.available()!=0){ outStream.write(inputStream.read()); } outStream.flush(); outStream.close(); inputStream.close(); } catch (Throwable e) { Log.w("LRUFileCache", e.getMessage()); } // free the space at every time to add a new file freeSpaceIfNeeded(); } @Override public File getDiskFile(String key) { File file = new File(getFilePathByKey(key)); if(file!=null&&file.exists()){ updateFileTime(file); }else{ file = null; } return file; } @Override public boolean isExist(String key) { if (URLUtil.isNetworkUrl(key)) { return new File(options.getCacheRootPath() + "/" + convertUrlToFileName(key)).exists(); } else if (URLUtil.isFileUrl(key)) { return new File(key).exists(); } else { return false; } } @Override public void removeDiskFile(String key) { File file = getDiskFile(key); if (file != null &&file.exists()) { file.delete(); } } @Override public void removeAllDiskFiles() { new File(options.getCacheRootPath()).delete(); } /** * This method will free the files which had not been used at a long time */ private void freeSpaceIfNeeded(){ File dir = new File(options.getCacheRootPath()); File[] files = dir.listFiles(); if(files==null){ return; } int dirSize = 0; for (int i = 0; i < files.length; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { dirSize += files[i].length(); } } // if the dir size larger than max size or the free space on SDCard is less than 10MB //free 40% space for system if (dirSize > options.getMaxCacheSize() || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { // delete 40% files by LRU int removeFactor = (int) ((0.4 * files.length) + 1); // sort the files by modify time Arrays.sort(files, new FileLastModifSort()); // delete files for (int i = 0; i < removeFactor; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { files[i].delete(); } } } //if file count is larger than max count, delete the last if(files.length>options.getMaxFileCount()){ Arrays.sort(files, new FileLastModifSort()); // delete files for (int i = options.getMaxFileCount(); i < files.length; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { files[i].delete(); } } } } /** * Modify the file time * * @param file the file which need to update time */ public void updateFileTime(File file) { if(file!=null&&file.exists()){ long newModifiedTime = System.currentTimeMillis(); file.setLastModified(newModifiedTime); } } /** * get the free space on SDCard * * @return free size in MB */ private int freeSpaceOnSd() { StatFs stat = new StatFs(Environment.getExternalStorageDirectory() .getPath()); double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat .getBlockSize()); return (int) sdFreeMB; } /** * Get the file name by file url * * @param url * @return file name */ private String convertUrlToFileName(String url) { String[] strs = url.split("/"); return strs[strs.length - 1] + WHOLESALE_CONV; } public String getFilePathByKey(String key){ if(URLUtil.isFileUrl(key)){ return key; }else if(URLUtil.isNetworkUrl(key)){ return options.getCacheRootPath()+"/"+convertUrlToFileName(key); }else { return null; } } /** * The comparator for the file modify, sort the files by modify time. */ private class FileLastModifSort implements Comparator<File> { 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; } } }}
完整的代码地址是:https://github.com/CJstar/Android-ImageFileCache
接下来将讲解MemmoryCache,也是采用LRU算法实现的缓存,不过它比文件缓存复杂一点。
版权声明:本文为博主原创文章,未经博主允许不得转载。