前言
在博客: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算法实现的缓存,不过它比文件缓存复杂一点。