您的位置:首页 > 移动开发 > Android开发

Android开源框架Universal-Image-Loader学习六——硬盘缓存策略

2015-05-04 16:47 615 查看

硬盘缓存策略:



LimitedAgeDiscCache(设定文件存活的最长时间,当超过这个值,就删除该文件)

UnlimitedDiscCache(这个缓存类没有任何的限制)

继承关系:
public class LimitedAgeDiscCache extends BaseDiscCache
public abstractclass BaseDiscCache implements DiskCache
public interface DiskCache extends DiscCacheAware
public interface DiscCacheAware


自底向上解析得:
1、DiscCacheAware源码:
/** Interface for disk cache */
@Deprecated
public interface DiscCacheAware {

/** 返回硬盘缓存的root directory*/
File getDirectory();

/** 返回缓存图片的file
* @param imageUri Original image URI
* @return File of cached image or null - 图片未缓存
*/
File get(String imageUri);

/**
* 保存image bitmap到硬盘缓存中.
* @param imageUri    Original image URI
* @param imageStream image输入流
* @param listener    保存进程监听器;在ImageLoader中不使用.core.listener.ImageLoadingProgressListener情况下可以忽略该listener
* @return true - 保存成功; false - 保存失败.
* @throws IOException
*/
boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;

/**
* (重载)保存image bitmap到硬盘缓存中.
* @param  imageUri - Original image URI
* @param  bitmap   Image bitmap
* @return true - 保存成功; false - 保存失败.
* @throws IOException
*/
boolean save(String imageUri, Bitmap bitmap) throws IOException;

/**
* 根据给定URI删除对应的image file
* @param  imageUri - 图片URI
* @return true - 图片删除成功;
false- 指定URI图片不存在或者图片文件无法删除
*/
boolean remove(String imageUri);

/** 关闭硬盘缓存,释放资源. */
void close();

/** 清除硬盘缓存*/
void clear();
}

I)上面代码用用到IoUtils.CopyListener listener:

/** Listener and controller for copy process */
public static interface CopyListener {
/**
* @param current 已经加载的bytes
* @param total 需要加载的总共的bytes
* @return true - 如果copying操作需要继续进行
false - 如果copying操作需要中断
*/
boolean onBytesCopied(int current, int total);
}
II)以及ImageLoadingProgressListener:
/** Listener for image loading progress.*/
public interface ImageLoadingProgressListener {
/**
* 当加载进程改变时被调用
* @param imageUri Image URI
* @param view     image的View控件,可以为null.
* @param current  已经下载的bytes大小
* @param total    总共的bytes大小
*/
void onProgressUpdate(String imageUri, View view, intcurrent, inttotal);
}

2、DiskCache源码:(形式意义同MemoryCache 之于MemoryCacheAware<K,
V>,不过是换个名称)

/**Interface for disk cache*/
public interface DiskCache extendsDiscCacheAware {
}

3、BaseDiscCache源码:

/**
* Base disk cache.
*/
public abstract class BaseDiscCache implements DiskCache {
/** {@value */
public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb
public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
public static final int DEFAULT_COMPRESS_QUALITY = 100;

private static final String ERROR_ARG_NULL = " argument must be not null";
private static final String TEMP_IMAGE_POSTFIX = ".tmp";

protected final File cacheDir;
protected final File reserveCacheDir;
protected final FileNameGenerator fileNameGenerator;

protected int bufferSize = DEFAULT_BUFFER_SIZE;// 32 Kb
protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;//Bitmap.CompressFormat.PNG
protected int compressQuality = DEFAULT_COMPRESS_QUALITY;//100

public BaseDiscCache(File cacheDir) {
this(cacheDir, null);
}

public BaseDiscCache(File cacheDir, File reserveCacheDir) {
this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator());
}

/**
* @param cacheDir          Directory for file caching
* @param reserveCacheDir   可以为null;
Reserve directory for file caching. It's used when the primary directory isn't available.
* @param fileNameGenerator FileNameGenerator(Generates names for files at disk cache) for cached files
*/
public BaseDiscCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
if (cacheDir == null) {
throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
}
if (fileNameGenerator == null) {
throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
}

this.cacheDir = cacheDir;
this.reserveCacheDir = reserveCacheDir;
this.fileNameGenerator = fileNameGenerator;
}

@Override
/** 重写DiscCacheAware.getDirectory()*/
public File getDirectory() {
return cacheDir;
}

@Override
/** 重写DiscCacheAware.get()*/
public File get(String imageUri) {
return getFile(imageUri);
}

@Override
/**保存image bitmap到硬盘缓存中.参数含义见DisCacheAware*/
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
File imageFile = getFile(imageUri);//根据imageUri获取相关File
File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);//定义为.tmp文件
boolean loaded = false;//加载标志
try {
OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);//bufferSize=32 Kb
try {
loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);//imageStream写入os,见注释III
} finally {
IoUtils.closeSilently(os); //释放资源,见注释IV
}
} finally {
IoUtils.closeSilently(imageStream);
if (loaded && !tmpFile.renameTo(imageFile)) {//见注释V
loaded = false;
}
if (!loaded) {
tmpFile.delete();//失败注意释放资源
}
}
return loaded;
}

@Override
/** 保存image bitmap到硬盘缓存中,没有IoUtils.CopyListener情况*/
public boolean save(String imageUri, Bitmap bitmap) throws IOException {
File imageFile = getFile(imageUri);
File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
boolean savedSuccessfully = false;
try {
savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);//图片压缩
} finally {
IoUtils.closeSilently(os);
if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
savedSuccessfully = false;
}
if (!savedSuccessfully) {
tmpFile.delete();
}
}
bitmap.recycle();
return savedSuccessfully;
}

@Override
public boolean remove(String imageUri) {
return getFile(imageUri).delete();
}

@Override
public void close() {
// Nothing to do
}

@Override
public void clear() {
File[] files = cacheDir.listFiles();
if (files != null) {
for (File f : files) {
f.delete();
}
}
}

/** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
protected File getFile(String imageUri) {
String fileName = fileNameGenerator.generate(imageUri);
File dir = cacheDir;
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
dir = reserveCacheDir;
}
}
return new File(dir, fileName);//Constructs a new file using the specified directory and name.
}

public void setBufferSize(intbufferSize) {
this.bufferSize = bufferSize;
}

public void setCompressFormat(Bitmap.CompressFormat compressFormat) {
this.compressFormat = compressFormat;
}

public void setCompressQuality(intcompressQuality) {
this.compressQuality = compressQuality;
}
}

I)用到的CompressFormat.PNG枚举类

/** Specifies the known formats a bitmap can be compressed into*/
public enum CompressFormat {
JPEG    (0),
PNG     (1),
WEBP    (2);

CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}

II)工具类:FileNameGenerator

/** Generates names for files at disk cache*/
public interface FileNameGenerator {
/** Generates unique file name for image defined by URI */
String generate(String imageUri);
}

III)IoUtils.copyStream(imageStream, os, listener, bufferSize);

/**
* 拷贝stream, fires progress events by listener, can be interrupted by listener.
*
* @param is         Input stream
* @param os         Output stream
* @param listener   可以为null; 拷贝进程的Listener以及拷贝中断的控制器controller
* @param bufferSize copying的Buffer Size;也代表了每一次触发progress listener callback回调的“一步”
————即每次copied bufferSize个bytes大小后即触发progress event
* @returntrue -    stream拷贝成功; false - 拷贝操作被listener中断
* @throws IOException
*/
public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener, int bufferSize)
throws IOException {
int current = 0;
int total = is.available();
if (total <= 0) {
total = DEFAULT_IMAGE_TOTAL_SIZE;
}

final byte[] bytes = new byte[bufferSize];
int count;
if (shouldStopLoading(listener, current, total)) return false;
while ((count = is.read(bytes, 0, bufferSize)) != -1) {
os.write(bytes, 0, count);//写入os中
current += count;         //更新当前加载的bytes数
if (shouldStopLoading(listener, current, total)) return false;
}
os.flush();
return true;
}

private static boolean shouldStopLoading(CopyListener listener, int current, int total) {
if (listener != null) {
boolean shouldContinue = listener.onBytesCopied(current, total);//参加上面CopyListener
if (!shouldContinue) {
if (100 * current / total < CONTINUE_LOADING_PERCENTAGE) {
return true; // 当加载超过75%,则直接加载,不中断;否则,return true,产生中断
}
}
}
return false;
}

IV)IoUtils.closeSilently()方法

publicstaticvoid closeSilently(Closeable closeable) {
try {
closeable.close();
} catch (Exception e) {
// Do nothing
}
}
下面分析JDK中的Closeable :

比如InputStream,OutputStream都实现了 Closeable接口

public abstract class InputStream extends Object implementsCloseable
public abstract class OutputStream implements Closeable, Flushable

AutoCloseable源码:

package java.lang;
/**
* 定义一个interface for 那些一旦不再使用就可以(或者需要)被关闭的classes
*  一般用法:
*   Closable foo = new Foo();
*   try {
*      ...;
*   } finally {
*      foo.close();
*   }
* }
*/
public interface AutoCloseable {
/** Close 相应 Object 并释放它所持有的所有系统资源(system resources)*/
void close() throws Exception;
}

Closeable源码:

package java.io;
public interface Closeable extends AutoCloseable {

/**
* 与AutoCloseable区别:虽然只有第一次call会产生有效作用,但本close方法
* 在同一个object上被多次调用是安全的。而 AutoCloseable.close()最多只能被调用一次
*/
void close() throws IOException;
}

V)  File.renameTo()方法

/**
* Renames this file to {@code newPath}. 该操作支持files 和 directories
* 此操作有很多导致failures的方法,包括:
* (1) 写权限(Write permission)Write permission is required on the directories containing both the source and
* destination paths.
* (2) 搜索权限(Search permission) is required for all parents of both paths.
*/
public boolean renameTo(File newPath) {
try {
Libcore.os.rename(path, newPath.path);
return true;
} catch (ErrnoException errnoException) {
return false;
}
}

4、LimitedAgeDiscCache 源码:

/**
* 时间策略,删除最早加载即loaded的时间超过限定时间的文件. Cache size是无限制的.
*/
public class LimitedAgeDiscCache extends BaseDiscCache {

private final long maxFileAge;

private final Map<File, Long> loadingDates = Collections.synchronizedMap(new HashMap<File, Long>());

public LimitedAgeDiscCache(File cacheDir, long maxAge) {
this(cacheDir, null, DefaultConfigurationFactory.createFileNameGenerator(), maxAge);
}

public LimitedAgeDiscCache(File cacheDir, File reserveCacheDir, long maxAge) {
this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxAge);
}

/**
* @param cacheDir          Directory for file caching
* @param reserveCacheDir   可为null; Reserve directory for file caching. It's used when the primary directory isn't available.
* @param fileNameGenerator Name generator for cached files
* @param maxAge            Max file age (in seconds). If file age will exceed this value then it'll be removed on next
*                          treatment (and therefore be reloaded).
*/
public LimitedAgeDiscCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long maxAge) {
//调用public BaseDiscCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator)
super(cacheDir, reserveCacheDir, fileNameGenerator);
this.maxFileAge = maxAge * 1000; // 转化为milliseconds
}

@Override
public File get(String imageUri) {
File file = super.get(imageUri);
if (file != null && file.exists()) {
boolean cached;
Long loadingDate = loadingDates.get(file);//Map<File, Long> loadingDates
if (loadingDate == null) {
cached = false;
loadingDate = file.lastModified();
} else {
cached = true;
}
//删除策略
if (System.currentTimeMillis() - loadingDate > maxFileAge) {
file.delete();
loadingDates.remove(file);
} else if (!cached) {
loadingDates.put(file, loadingDate);
}
}
return file;
}

@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
boolean saved = super.save(imageUri, imageStream, listener);
rememberUsage(imageUri);//更新相关文件的最新修改时间
return saved;
}

@Override
public boolean save(String imageUri, Bitmap bitmap) throws IOException {
boolean saved = super.save(imageUri, bitmap);
rememberUsage(imageUri);
return saved;
}

@Override
public boolean remove(String imageUri) {
loadingDates.remove(getFile(imageUri));
return super.remove(imageUri);
}

@Override
public void clear() {
super.clear();
loadingDates.clear();
}

private void rememberUsage(String imageUri) {
File file = getFile(imageUri);
long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
loadingDates.put(file, currentTime);
}
}

5、UnlimitedDiscCache 源码:

/**
* UIL框架中默认的DiskCache实现,Cache size是无限制的
*/
public class UnlimitedDiscCache extends BaseDiscCache {

public UnlimitedDiscCache(File cacheDir) {
super(cacheDir);
}

public UnlimitedDiscCache(File cacheDir, File reserveCacheDir) {
super(cacheDir, reserveCacheDir);
}

/**
* @param cacheDir Directory for file caching
* @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
* @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
* Name generator} for cached files
*/
public UnlimitedDiscCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
super(cacheDir, reserveCacheDir, fileNameGenerator);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐