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 :

比如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);
    }
}
时间: 2024-10-19 14:41:29

Android开源框架Universal-Image-Loader学习六——硬盘缓存策略的相关文章

开源项目Universal Image Loader for Android 说明文档 (1) 简介

 When developing applications for Android, one often facesthe problem of displaying some graphical content from the Internet. So, youshould provide image loading from the Web in an Android app, their processingand displaying with limited memory aga

六款值得推荐的Android开源框架简介

技术不再多,知道一些常用的.不错的就够了.下面就是最近整理的“性价比”比较高的Android开源框架,应该是相对实用的. 1.volley 项目地址 https://github.com/smanikandan14/Volley-demo JSON,图像等的异步下载: 网络请求的排序(scheduling) 网络请求的优先级处理 缓存 多级别取消请求 和Activity和生命周期的联动(Activity结束时同时取消所有网络请求) 2.android-async-http 项目地址:https:

Android开源框架Universal-Image-Loader学习使用1

一.工作流程: 1.当请求显示图片,调用ImageLoader.displayImage(),首先会 1)计算显示图片的尺寸大小 2)判断该图片是否已存在缓存中,若No,跳到步骤3,若Yes,跳到步骤9 3)判断图片是否已存在本地,若No,跳到步骤4:若Yes,跳到步骤6 4)判断图片允许存储在本地,若No,跳转到步骤6:若Yes,跳到步骤5 5)将图片存储至本地 6)将Image文件decode成Bitmap文件 7)判断是否允许图片存储到缓存中,若No,跳到步骤9:若yes,跳到步骤8 8)

Android开源框架ViewPageIndicator和ViewPager实现Tab导航

前言: 关于使用ViewPageIndicator和ViewPager实现Tab导航,在开发社区里已经有一堆的博客对其进行了介绍,假设我还在这里写怎样去实现.那简直就是老生常谈,毫无新奇感,并且.我也不觉得自己对ViewPageIndicator的理解会比别人好,毕竟我也是看着大神的帖子.在学习实践着. 那我还写这个有啥意义呢?事实上么,就是想在这里记录下.在使用ViewPageIndicator和ViewPager实现Tab导航时,大家有可能会遇到的坑.这个坑.须要我们开发时尽量去避免的. 啥

Android进阶笔记14:RoboBinding(实现了数据绑定 Presentation Model(MVVM) 模式的Android开源框架)

1.RoboBinding RoboBinding是一个实现了数据绑定 Presentation Model(MVVM) 模式的Android开源框架.从简单的角度看,他移除了如addXXListener(),findViewById()这些不必要的代码,连如BufferKnife那样的InjectView都不需要,因为你的代码一般不需要依赖于这些界面组件信息.下面以一个最简单的AndroidMVVM为例. (1)layout 布局 <LinearLayout xmlns:android="

Android开源框架 Android-Universal-Image-Loader

Android开源框架Universal-Image-Loader就像图片加载守护者,为我们提供了丰富的功能特性: (1)多线程加载图像(异步或同步): (2)高度可定制化imageloader配置(线程池.图片下载器.解码器.内存和磁盘缓存.显示图像选项等): (3)每一个显示图像有许多自定义选项(存根图片,缓存开关,解码选项,位图处理和显示等): (4)支持内存和磁盘上的图像缓存(设备的文件系统和SD卡): (5)监听加载过程(包括下载进度): 下来我们详解如何配置使用Universal-I

Android 开源框架Universal-Image-Loader全然解析(一)--- 基本介绍及使用

大家好!差点儿相同两个来月没有写文章了.前段时间也是在忙换工作的事,准备笔试面试什么的事情,如今新工作找好了,新工作自己也比較惬意.唯一遗憾的就是自己要去一个新的城市,新的环境新的開始.希望自己能尽快的适应新环境,如今在准备交接的事情,自己也有一些时间了,所以就继续给大家分享Android方面的东西. 相信大家平时做Android应用的时候,多少会接触到异步载入图片,或者载入大量图片的问题.而载入图片我们常常会遇到很多的问题,比方说图片的错乱.OOM等问题,对于新手来说,这些问题解决起来会比較吃

Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解

本篇文章继续为大家介绍Universal-Image-Loader这个开源的图片加载框架,介绍的是图片缓存策略方面的,如果大家对这个开源框架的使用还不了解,大家可以看看我之前写的一篇文章Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用,我们一般去加载大量的图片的时候,都会做缓存策略,缓存又分为内存缓存和硬盘缓存,我之前也写了几篇异步加载大量图片的文章,使用的内存缓存是LruCache这个类,LRU是Least Recently Used 近

greenDao android开源框架数据库更新表的问题

最近使用greenDao当android应用升级数据库新增表或者修改表,发现数据被清空的问题 查找资料也没有找到解决方案,最后查看代码发现需要自己修改SQLiteOpenHelper 1.找到greenDao生成的DaoMaster.java文件,里面有SQLiteOpenHelper实现 2.修改DevOpenHelper类里的   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 方法 通过old