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



硬盘缓存策略:

技术分享

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 :

比如InputStreamOutputStream都实现了 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);
    }
}


























郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。