Android开源框架Universal-Image-Loader学习六——硬盘缓存策略
硬盘缓存策略:
UnlimitedDiscCache(这个缓存类没有任何的限制)
public class LimitedAgeDiscCache extends BaseDiscCache public abstractclass BaseDiscCache implements DiskCache public interface DiskCache extends DiscCacheAware public interface 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); } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。