转载请标明出处,本文出自:chaossss的博客


Android-Universal-ImageLoader Github 地址


Cache

我们要对图片进行缓存。有两种方式:内存缓存和本地缓存。

这两种方式的区别在于。内存缓存是缓存在 Android 系统为应用分配的执行内存之中,读取速度快。可是可能会带来 OOM 的问题;本地缓存一般缓存在 SD 卡中,读取速度较慢。可是缓存空间足。

那么我们要怎么来实现内存缓存和本地缓存呢?依据单一职责原则,假设 MemoryCache 和 DiskCache 的抽象不一致的话,我们就须要分别创建 MemoryCache 和 DiskCache 的抽象基类,分别实现各自的细节。

而显然,两者抽象是不一致的,由于 MemoryCache 面对的对象是图片(Bitmap)。DiskCache 面对的对象是文件(File)。因此,我们应该分开实现 MemoryCache 和 DiskCache。

MemoryCache

MemoryCache 总体设计及相关基类的实现

那我们如今该干啥呢?想 MemoryCache 的功能啊!对于进行内存缓存我们能想到什么应用场景呢:

  1. 首先每个 MemoryCache 肯定有相应的增删取功能
  2. 当 MemoryCache 被存满了(Android 应用的内存资源是非常宝贵的),我们该怎么处理呢:
    • 回收近期没实用过的
    • 回收最早在内存中缓存的
    • 回收使用频率最低的
    • 在缓存时,key 同样的图片,回收旧的图片,缓存新的图
  3. 有时候我们可能须要缓存高分辨率的高清图片。而这样的图片非常大。缓存到内存中就会 OOM,在这样的情况下。为了让应用能正常执行。我们应该能在缓存时限制图片的大小

我就想到这么多哈,肯定还会有非常多不一样的情况,毕竟需求是层出不穷的……那么依据如今得到的应用场景。我们就要開始设计 MemoryCache 啦。

依据分析得到的结果我们能够发现:内存缓存有不一样的缓存策略和缓存限制。可是具有同样的抽象。所以我们首先须要实现 MemoryCache 的抽象:

public interface MemoryCache {

    boolean put(String key, Bitmap value);

    Bitmap get(String key);

    Bitmap remove(String key);

    Collection<String> keys();

    void clear();
}

实现了抽象,就得開始考虑详细实现拉。我们刚刚也说了,MemoryCache 具有不同的缓存策略和缓存限制。策略不一样的实现类一般不会存在继承关系,而具有同样限制的 MemoryCache 则可能存在抽象。那么我们能够得到:

public abstract class BaseMemoryCache implements MemoryCache
public abstract class FuzzyKeyMemoryCache implements MemoryCache
public abstract class LruMemoryCache implements MemoryCache

非常多人会奇怪了。为什么 LruMemoryCache 和 FuzzyKeyMemoryCache 不是继承于 BaseMemoryCache 呢?我们最好还是想象为什么须要 BaseMemoryCache,我们之所以引入 BaseMemoryCache,是由于限制不同的 MemoryCache 具有同样的抽象,在 AUImgLoader 中。不同的限制体如今:缓存图片的大小限制和缓存图片的引用方式限制。

有关引用的知识能够看这Java中的强引用、软引用、弱引用和虚引用

所以,在 BaseMemoryCache 的实现中。我们加入了相应的抽象方法:

protected abstract Reference<Bitmap> createReference(Bitmap value);

反观 LruMemoryCache。我在深入源代码剖析LruCache中给大家解说过 LruCache 的原理,我们在 LruMemoryCache 中进行缓存。是仅仅使用强引用进行缓存的。换言之,LruMemoryCache 的抽象和 BaseMemory 的实现是不一样的,由于 BaseMemoryCache 中多了一个 createReference() 抽象方法,而 LruMemoryCache 不须要这个抽象方法。

而 FuzzyKeyMemoryCache 是一个仅仅考虑缓存策略的内存缓存类。它仅仅考虑怎么去处理 key 同样的图片,详细你用什么方式实现。就由开发人员自己决定。由于 FuzzyKeyMemory 的处理方法都是调用抽象接口完毕的(源代码我就不放了哈,大家能够自己下载)

BaseMemoryCache

经过刚刚的分析。我们得到了 MemoryCache 的三个基类:BaseMemoryCache、LruMemoryCache、FuzzyKeyMemoryCache。接下来。我们就来依据 BaseMemoryCache 实现我们想要的细节。

我们已经提到。BaseMemoryCache 是用于处理缓存限制的基类,而所谓的限制体如今:大小限制和限制引用方式。

那么非常显然。引用方式仅仅有强引用、弱引用值得我们进行区分(Android 2.3 已经不鼓舞开发人员使用软引用了,由于在进行垃圾回收时软引用和弱引用都具有被回收的倾向),所以我们能够得到以下两个实现类:

public abstract class LimitedMemoryCache extends BaseMemoryCache
public class WeakMemoryCache extends BaseMemoryCache

千呼万唤始出来,我们最终的细节实现类 LimitedMemoryCache 最终出现了……在 LimitedMemoryCache 中。我们将使用强引用缓存图片。那么。LimitedMemoryCache 还须要提供的就是图片大小的限制:

public LimitedMemoryCache(int sizeLimit) {
this.sizeLimit = sizeLimit;
cacheSize = new AtomicInteger();
if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {
L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB);
}
}

剩下的,就是依据我们的缓存策略对 LimitedMemoryCache 进行拓展啦。

MemoryCahce 的工具类

那么经过刚刚的努力。我们如今已经能过在内存中进行缓存。即使 AUImgLoader 自带的缓存实现类不能满足我们的需求,由于 AUImgLoader 的内存缓存功能模块是通过装饰者模式架构的,我们要对已有的类进行拓展也是非常easy的。可是如今内存缓存模块仅仅是提供了必要的“内存缓存功能”,我们还需不须要其它的工具来简化我们的使用呢?

大家最好还是想想。我们载入了一些尺寸较小的图片在内存中。它们占用的总内存并不大,还不须要将它们缓存到本地,但我们仍须要对它们进行细粒度的管理(否则当图片数量增多。内存空间不够时我们对图片的处理会带来各种问题)。那么我们就须要一个工具类协助我们进行内存缓存的管理。最好还是创建一个叫做 MemoryCacheUtils 的类:

public final class MemoryCacheUtils {

    private MemoryCacheUtils() {
}
}

大家会注意到这个类将是一个无法被继承的类,也无法通过构造方法获得实例对象。毕竟一方面,这个工具类不须要反复创建实例对象。仅仅须要调用类去执行方法即可了;还有一方面。工具类并不须要继承,须要什么加入进去即可了。

这里说 MemoryCacheUtils 无法创建实例对象是指普通情况下无法创建,实际上假设使用 Java 的反射机制的话还是能够创建对象的。

那么这个类要帮我们完毕什么工作呢?分析实际的使用场景。我们能够得到:

  1. 为即将加入内存缓存的图片生成相应的
  2. 使用 Comparator 推断键是否相等时,可能出现不同的 Uri 生成的键同样的情况。此类须要提供方法解决问题
  3. 当你输入一个 Uri 时可能会在内存缓存中找到多个响应图片,所以方法类应返回响应图片列表
  4. 当你输入一个 Uri 时可能会在内存缓存中找到多个已缓存的键,所以方法类应返回键列表
  5. 将相应输入 Uri 的全部缓存图片从内存中移除
public static String generateKey(String imageUri, ImageSize targetSize){}

public static Comparator<String> createFuzzyKeyComparator(){}

public static List<Bitmap> findCachedBitmapsForImageUri(String imageUri, MemoryCache memoryCache){}

public static List<String> findCacheKeysForImageUri(String imageUri, MemoryCache memoryCache){}

public static void removeFromCache(String imageUri, MemoryCache memoryCache){}

详细实现我就不在这多说拉,大家能够自行查看源代码进行阅读。里面的逻辑并不难。

MemoryCahce 的结构图

DiskCache

DiskCache 总体设计及相关基类的实现

实际上 DiskCache 的设计思想和 MemoryCache 是非常接近的。我们仅仅要遵循刚刚的思路就能够拉。

那么首先须要实现 DiskCache 的抽象:

public interface DiskCache {
File getDirectory(); File get(String imageUri); boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException; boolean save(String imageUri, Bitmap bitmap) throws IOException; boolean remove(String imageUri); void close(); void clear();
}

然后获得两个基类:

public class LruDiskCache implements DiskCache
public abstract class BaseDiskCache implements DiskCache

同样的。我们依据缓存的限制实现 BaseDiskCache 的细节,就能够完毕本地缓存功能模块拉

DiskCache 的辅助工具类

尽管 DiskCache 总体设计和实现都挺简单的,可是大家还须要考虑到一个问题,就是我们每一张图片缓存到本地之后都须要为其命名,那么我们就应该为事实上现相关的命名类咯。

能够区分每一张图片的命名方式无非就是:哈希和MD5,引用网上一张图,我们应该设计一个文件命名功能模块。提供给 DiskCache 使用:

那么首先实现命名类的抽象:

public interface FileNameGenerator {
String generate(String imageUri);
}

然后分别实现细节就能够拉:

public class HashCodeFileNameGenerator implements FileNameGenerator {
@Override
public String generate(String imageUri) {
return String.valueOf(imageUri.hashCode());
}
}
public class Md5FileNameGenerator implements FileNameGenerator {

    private static final String HASH_ALGORITHM = "MD5";
private static final int RADIX = 10 + 26; // 10 digits + 26 letters @Override
public String generate(String imageUri) {
byte[] md5 = getMD5(imageUri.getBytes());
BigInteger bi = new BigInteger(md5).abs();
return bi.toString(RADIX);
} private byte[] getMD5(byte[] data) {
byte[] hash = null;
try {
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
digest.update(data);
hash = digest.digest();
} catch (NoSuchAlgorithmException e) {
L.e(e);
}
return hash;
}
}

DiskCache 的工具类

DiskCache 除了在进行本地缓存时须要工具类帮助其生成文件名称,还须要工具类帮它完毕其它工作:本地缓存管理、本地存储策略等……于是我们须要引入多个工具类协助完毕这些职责。

大家须要注意到的是。工具类和辅助工具类将处于不同的包中。由于辅助工具类是完毕职责不可缺少的一环。而工具类是降低使用者的使用成本,两者间的区别使得类所在的包不一致。

DiskCacheUtils

和 MemoryCacheUtils 一样,DiskCacheUtils 也是一个无法被继承,无法创建实例对象的类。

public final class DiskCacheUtils {

    private DiskCacheUtils() {
}
}

这个工具类的主要使用场景为:

  1. 找到 Uri 相应的本地缓存文件
  2. 移除 Uri 相应的本地缓存文件

所以我们分别实现相应的方法就能够拉:

public static File findInCache(String imageUri, DiskCache diskCache){}

public static boolean removeFromCache(String imageUri, DiskCache diskCache){}

StorageUtils

为了将图片存储到本地 SD 卡中,我们须要获得本地缓存相应的文件夹,于是引入了 StorageUtils 负责完毕相关的事项。由于工具类都是无法继承和创建对象的类。我就不再放出响应的构造方法和类声明了。

在 StorageUtils 中。我们可能存在的使用场景有:

  1. 获得本地缓存文件夹
  2. 新建额外的本地缓存文件夹
  3. 为某些图片提供相应的特定缓存文件夹
  4. 为单个图片提供缓存文件夹

实现各自相应的方法,就OK拉。

DiskCahce 的结构图

反思

大家会发现,不管是 DiskCache 还是 MemoryCache。还是它们的工具类,代码结构清晰,类间耦合度低,很多开发人员看到这样的代码都会感叹:这代码写得真美丽。但我相信观察力敏锐的同学会一眼看出,MemoryCache 和 DiskCache 都使用了装饰者模式进行设计,功能的拓展仅仅要针对相应的抽象进行“装饰”就能够了。工具类则严格遵循设计模式中的设计原则。尽可能独立不同的工具模块。所以大家在开发的时候也应该注意这些开发细节,不断重构代码,让代码结构变得清晰。

从设计到实现,一步步教你实现Android-Universal-ImageLoader-缓存的更多相关文章

  1. 一步步教你轻松学关联规则Apriori算法

    一步步教你轻松学关联规则Apriori算法 (白宁超 2018年10月22日09:51:05) 摘要:先验算法(Apriori Algorithm)是关联规则学习的经典算法之一,常常应用在商业等诸多领 ...

  2. 一步步教你搭建VS环境下用C#写WebDriver脚本

    一步步教你搭建VS环境下用C#写WebDriver脚本http://www.automationqa.com/forum.php?mod=viewthread&tid=3529&fro ...

  3. 我写了个教程《一步步教你把ubuntu安装到U盘》

    一步步教你把ubuntu安装到U盘 作者 Val 2452013147@qq.com 原因: 由于某些原因(学生党),需要把ubuntu安装到U盘到处走,百度了一下,教程都不是很好,要么很复杂,要么不 ...

  4. 一步步教你读懂NET中IL(附带图)

    一步步教你读懂NET中IL(附带图) 接触NET也有1年左右的时间了,NET的内部实现对我产生了很大的吸引力,在msdn上找到一篇关于NET的IL代码的图解说明,写的挺不错的.个人觉得:能对这些底部的 ...

  5. 一步步教你轻松学奇异值分解SVD降维算法

    一步步教你轻松学奇异值分解SVD降维算法 (白宁超 2018年10月24日09:04:56 ) 摘要:奇异值分解(singular value decomposition)是线性代数中一种重要的矩阵分 ...

  6. 一步步教你轻松学支持向量机SVM算法之案例篇2

    一步步教你轻松学支持向量机SVM算法之案例篇2 (白宁超 2018年10月22日10:09:07) 摘要:支持向量机即SVM(Support Vector Machine) ,是一种监督学习算法,属于 ...

  7. 一步步教你轻松学支持向量机SVM算法之理论篇1

    一步步教你轻松学支持向量机SVM算法之理论篇1 (白宁超 2018年10月22日10:03:35) 摘要:支持向量机即SVM(Support Vector Machine) ,是一种监督学习算法,属于 ...

  8. 一步步教你轻松学主成分分析PCA降维算法

    一步步教你轻松学主成分分析PCA降维算法 (白宁超 2018年10月22日10:14:18) 摘要:主成分分析(英语:Principal components analysis,PCA)是一种分析.简 ...

  9. 一步步教你轻松学K-means聚类算法

    一步步教你轻松学K-means聚类算法(白宁超  2018年9月13日09:10:33) 导读:k-均值算法(英文:k-means clustering),属于比较常用的算法之一,文本首先介绍聚类的理 ...

  10. 一步步教你轻松学朴素贝叶斯模型算法Sklearn深度篇3

    一步步教你轻松学朴素贝叶斯深度篇3(白宁超   2018年9月4日14:18:14) 导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果.所以很受欢迎,对 ...

随机推荐

  1. Gitlab备份、迁移、恢复和升级

    Gitlab备份.迁移.恢复和升级 自建的Gitlab服务器常常会因为使用时间的增长,其空间容量等硬件需求都需要升级,或者迁移至更高配置的服务器上.备份.迁移.恢复.升级过程如下 1.gitlab备份 ...

  2. cocos2d-x 3.1.1 学习笔记[11] http请求 + json解析

    //http须要引入的头文件和命名空间 #include <network/HttpClient.h> using namespace network; //json须要引入的头文件 #i ...

  3. struts2 接口如何接收客户端提交的json数据

      struts2 接口如何接收客户端提交的json数据 CreationTime--2018年6月20日15点54分 Author:Marydon 1.情景还原 使用struts2写的接口(服务端) ...

  4. 【POJ 1080】 Human Gene Functions

    [POJ 1080] Human Gene Functions 相似于最长公共子序列的做法 dp[i][j]表示 str1[i]相应str2[j]时的最大得分 转移方程为 dp[i][j]=max(d ...

  5. LRU的C++实现引申出的迭代器问题

    leetcode上刷题.碰到一题实现LRU算法的题目. LRU,Least recently used.是一种常见的cache和页面替换算法.算法和原理可以参阅相关wiki. leetcode上的这一 ...

  6. Android代码实现控件闪烁效果

    代码地址如下:http://www.demodashi.com/demo/13162.html 前言 在项目开发过程中,我们有时会遇到需要控件闪烁和停止的问题,这个用xml是可以实现的,但是为了在使用 ...

  7. 门窗ERP——让门窗幕墙加工更简单

    系统特点: 本系统包括:生产销售.采购.库存.财务模块 型材按重量算成本,玻璃按面积算成本 单据采用推送的方式推进单据流程,层层递进严格把握管理流程.保证数据的严密.严谨性. 销售订单支持门窗.幕墙. ...

  8. 分别在.NET Framework 与 .NET Core 框架下 编写Windows Service(windows服务程序)

    前言,为什么会分别在两个框架下编写Windows Service,是因为最近在做区块链这块,使用的是NEO(小蚁区块链)的相关技术,NEO使用的是.net core 2.1,业务上需要写两个程序,一个 ...

  9. C语言学习笔记(四) 流程控制

    流程控制 流程控制,说通俗一点就是程序代码执行的顺序.不管对于哪门语言来说,流程控制都是很重要的一部分内容: 流程控制的分类,可以分为三大类: 1.顺序 这个很好理解,顺序执行就是代码从上往下一行行的 ...

  10. json.Decoder vs json.Unmarshal

    128down voteaccepted It really depends on what your input is. If you look at the implementation of t ...