提兵百万西湖上,立马吴山第一峰!这篇文章主要讲述android缓存系列:ASimpleCache源码分析相关的知识,希望能为你提供帮助。
接触Acache是因为阅读oschina的开源android端代码,发现oschina采用了该框架缓存新闻分页数据。后来知道这是个杨福海的开源项目,他还开源过afinal框架,项目的地址如下:
https://github.com/yangfuhai/ASimpleCache
一.官方介绍ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。
1、它可以缓存什么东西?普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。
2、它有什么特色?特色主要是:
1:轻,轻到只有一个JAVA文件。
2:可配置,可以配置缓存路径,缓存大小,缓存数量等。
3:可以设置缓存超时时间,缓存超时自动失效,并被删除。
4:支持多进程。
3、它在android中可以用在哪些场景?1、替换SharePreference当做配置文件
2、可以缓存网络请求数据,比如oschina的android客户端可以缓存http请求的新闻内容,缓存时间假设为1个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量。
4、如何使用 ASimpleCache?以下有个小的demo,希望您能喜欢:
ACache mCache = ACache.get(this);
mCache.put("test_key1", "test value");
mCache.put("test_key2", "test value", 10);
//保存10秒,如果超过10秒去获取这个key,将为null
mCache.put("test_key3", "test value", 2 * ACache.TIME_DAY);
//保存两天,如果超过两天去获取这个key,将为null获取数据
ACache mCache = ACache.get(this);
String value = https://www.songbingjia.com/android/mCache.getAsString("test_key1");
二.源码分析ASimpleCache里只有一个JAVA文件——ACache.java
源码分析部分,我们也根据使用的步骤来进行分析:
1.获取缓存实例根据以上的demo,我们最先调用的是get方法来获得一个Acache的缓存实例。
Acache提供了多个get方法,通过调用不同的get方法传入不同的参数可以实现定义缓存保存的位置、缓存的数量、缓存的大小。其实这种方案一种最好的方式是使用建造者模式。不过这里因为配置项不多,使用建造者模式就显得过度设计了。get方法最终调用到的是如下的方法,这个方法其实就是一个典型单例实现。
看源码:
/**
* 进行获取缓存管理器,看缓存管理器的Map中是否已经存在指定的管理器,如果不存在进行创建,并且加入到map中
* @param cacheDir
* @param max_zise
* @param max_count
* @return
*/
public static ACache get(File cacheDir, long max_zise, int max_count) {
ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid());
if (manager == null) {
manager = new ACache(cacheDir, max_zise, max_count);
mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager);
}
return manager;
}private static String myPid() {
return "_" + android.os.Process.myPid();
}
Acache为不同进程设置了不同的缓存文件夹。通过一个map来管理不同进程的缓存实例。
接下来我们看下ACache的构造函数,我们发现它主要是new了ACacheManager对象。这里有个问题,作者为何要使用Acache和ACacheManager来实现这个开源项目?其实命名一个类就可以了,为什么呢?我感觉可能是作者是考虑到单一职责,ACacheManager内部简单的实现了LRU,Acache则为ACacheManager提供一层封装,负责对外提供接口。这样可以保证Acache的方法都在以为层次上。感性的考虑,也确实是,低层负责实现LRU来分配内存,高层负责提供对外交流的接口。如果使用一个类确实职责过于多了。
/**
* 进行根据缓存路径,缓存数量和缓存大小创建一个缓存管理器
* @param cacheDir
* @param max_size
* @param max_count
*/
private ACache(File cacheDir, long max_size, int max_count) {
if (!cacheDir.exists() &
&
!cacheDir.mkdirs()) {
throw new RuntimeException("can‘t make dirs in "
+ cacheDir.getAbsolutePath());
}
mCache = new ACacheManager(cacheDir, max_size, max_count);
}
2.ACacheManager接下来我们来看下ACacheManager这个类:
public class ACacheManager {
private final AtomicLong cacheSize;
private final AtomicInteger cacheCount;
private final long sizeLimit;
private final int countLimit;
private final Map<
File, Long>
lastUsageDates = Collections
.synchronizedMap(new HashMap<
File, Long>
());
protected File cacheDir;
private ACacheManager(File cacheDir, long sizeLimit, int countLimit) {
this.cacheDir = cacheDir;
this.sizeLimit = sizeLimit;
this.countLimit = countLimit;
cacheSize = new AtomicLong();
cacheCount = new AtomicInteger();
calculateCacheSizeAndCacheCount();
}/**
* 计算 cacheSize和cacheCount
*/
private void calculateCacheSizeAndCacheCount() {
new Thread(new Runnable() {
@Override
public void run() {
int size = 0;
int count = 0;
File[] cachedFiles = cacheDir.listFiles();
if (cachedFiles != null) {
for (File cachedFile : cachedFiles) {
size += calculateSize(cachedFile);
count += 1;
lastUsageDates.put(cachedFile,
cachedFile.lastModified());
}
cacheSize.set(size);
cacheCount.set(count);
}
}
}).start();
}private void put(File file) {
int curCacheCount = cacheCount.get();
while (curCacheCount + 1 >
countLimit) {
long freedSize = removeNext();
cacheSize.addAndGet(-freedSize);
curCacheCount = cacheCount.addAndGet(-1);
}
cacheCount.addAndGet(1);
long valueSize = calculateSize(file);
long curCacheSize = cacheSize.get();
while (curCacheSize + valueSize >
sizeLimit) {
long freedSize = removeNext();
curCacheSize = cacheSize.addAndGet(-freedSize);
}
cacheSize.addAndGet(valueSize);
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
lastUsageDates.put(file, currentTime);
}private File get(String key) {
File file = newFile(key);
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
lastUsageDates.put(file, currentTime);
return file;
}private File newFile(String key) {
return new File(cacheDir, key.hashCode() + "");
}private boolean remove(String key) {
File image = get(key);
return image.delete();
}private void clear() {
lastUsageDates.clear();
cacheSize.set(0);
File[] files = cacheDir.listFiles();
if (files != null) {
for (File f : files) {
f.delete();
}
}
}/**
* 移除旧的文件
*
* @return
*/
private long removeNext() {
if (lastUsageDates.isEmpty()) {
return 0;
}Long oldestUsage = null;
File mostLongUsedFile = null;
Set<
Map.Entry<
File, Long>
>
entries = lastUsageDates.entrySet();
synchronized (lastUsageDates) {
for (Map.Entry<
File, Long>
entry : entries) {
if (mostLongUsedFile == null) {
mostLongUsedFile = entry.getKey();
oldestUsage = entry.getValue();
} else {
Long lastValueUsage = entry.getValue();
if (lastValueUsage <
oldestUsage) {
oldestUsage = lastValueUsage;
mostLongUsedFile = entry.getKey();
}
}
}
}long fileSize = calculateSize(mostLongUsedFile);
if (mostLongUsedFile.delete()) {
lastUsageDates.remove(mostLongUsedFile);
}
return fileSize;
}private long calculateSize(File file) {
return file.length();
}
}
构造函数得到原子类实例cacheSize和cacheCount,通过calculateCacheSizeAndCacheCount(); 方法计算cacheSize和cacheCount.calculateCacheSizeAndCacheCount方法中开启线程进行大小和数量的计算。
计算完后存入cacheSize和cacheCount,cacheSize和cacheCount在内部类中定义为AtomicLong和AtomicInteger量子类,也就是线程安全的。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入。
接下来我们看下数据的存取工作,这里我们分析一个简单的和一个复杂的,其他的原理都一样。
3.往缓存实例存取Stringput(String key, String value)
public void put(String key, String value) {
File file = mCache.newFile(key);
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(file), 1024);
out.write(value);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mCache.put(file);
}
}
我们看下newFile方法:
private File newFile(String key) {
return new File(cacheDir, key.hashCode() + "");
}
其实这里就是通过key用父目录和子文件分隔的方式构造File对象。然后我们会把value保存到这个file中。
接下来看下put方法:
private void put(File file) {
int curCacheCount = cacheCount.get();
while (curCacheCount + 1 >
countLimit) {
long freedSize = removeNext();
cacheSize.addAndGet(-freedSize);
curCacheCount = cacheCount.addAndGet(-1);
}
cacheCount.addAndGet(1);
long valueSize = calculateSize(file);
long curCacheSize = cacheSize.get();
while (curCacheSize + valueSize >
sizeLimit) {
long freedSize = removeNext();
curCacheSize = cacheSize.addAndGet(-freedSize);
}
cacheSize.addAndGet(valueSize);
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
lastUsageDates.put(file, currentTime);
}
【android缓存系列(ASimpleCache源码分析)】这里在put的时候,我们首先会判断一下增加后缓存文件个数是否会超过限制的个数,如果超过就移除一个,后面我们再去分析removeNext方法,这里先假设没超过,接下来会给实际缓存文件个数增加1,然后计算该file的大小,如果当前缓存大小+该file大小> 缓存大小限制时,也会执行移除,这里假设没有超过,接下来会接下来会给实际缓存大小增加file的大小,并且设置该file的最后修改时间为系统的时间,并且把该flie文件和修改时间放到一个map集合中(lastUsageDates),即该缓存框架实现LRU的主要方式是通过lastUsageDates这个map。
下面我们分析一下移除方法removeNext的实现:
private long removeNext() {
if (lastUsageDates.isEmpty()) {
return 0;
}Long oldestUsage = null;
File mostLongUsedFile = null;
Set<
Map.Entry<
File, Long>
>
entries = lastUsageDates.entrySet();
synchronized (lastUsageDates) {
for (Map.Entry<
File, Long>
entry : entries) {
if (mostLongUsedFile == null) {
mostLongUsedFile = entry.getKey();
oldestUsage = entry.getValue();
} else {
Long lastValueUsage = entry.getValue();
if (lastValueUsage <
oldestUsage) {
oldestUsage = lastValueUsage;
mostLongUsedFile = entry.getKey();
}
}
}
}long fileSize = calculateSize(mostLongUsedFile);
if (mostLongUsedFile.delete()) {
lastUsageDates.remove(mostLongUsedFile);
}
return fileSize;
}
大概算法就是去遍历这个lastUsageDates map,然后移除最早的那个缓存文件,并且删除掉,返回因此留出的空余文件容量大小。
put(key, Utils.newStringWithDateInfo(saveTime, value))
分析完ACacheManager的put()后,我们回到put(key, Utils.newStringWithDateInfo(saveTime, value))
其中第二个参数value传入的是Utils.newStringWithDateInfo(saveTime, value),而newStringWithDateInfo是ACache的内部工具类的一个方法,在value内容前面加上了时间信息:
//返回时间信息+value
private static String newStringWithDateInfo(int second, String strInfo) {
return createDateInfo(second) + strInfo;
}
返回拼接createDateInfo(second)和value的字符串。createDateInfo()如下:
//时间信息
private static String createDateInfo(int second) {
String currentTime = System.currentTimeMillis() + "";
while (currentTime.length() <
13) {//小于13,前面补0
currentTime = "0" + currentTime;
}
return currentTime + "-" + second + mSeparator;
//当前时间-保存时间
}
返回字符串格式为 当前时间-保存时间“ ”
getAsString
public String getAsString(String key) {
File file = mCache.get(key);
if (!file.exists())
return null;
boolean removeFile = false;
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
String readString = "";
String currentLine;
while ((currentLine = in.readLine()) != null) {
readString += currentLine;
}
if (!Utils.isDue(readString)) {
return Utils.clearDateInfo(readString);
} else {
removeFile = true;
return null;
}
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (removeFile)
remove(key);
}
}
getAsString(String key)方法里首先通过缓存管理器的mCache.get(key)方法获取文件,然后用Utils.isDue(readString)**判断是否字符串数据到期,未到期返回去除时间信息的字符串内容;到期则移除缓存,返回空。**Utils.isDue(readString)调用了isDue(byte[] data)判断:
/**
* 判断缓存的byte数据是否到期
*
* @param data
* @return true:到期了 false:还没有到期
*/
private static boolean isDue(byte[] data) {
String[] strs = getDateInfoFromDate(data);
if (strs != null &
&
strs.length == 2) {
String saveTimeStr = strs[0];
while (saveTimeStr.startsWith("0")) {
saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length());
}
long saveTime = Long.valueOf(saveTimeStr);
long deleteAfter = Long.valueOf(strs[1]);
if (System.currentTimeMillis() >
saveTime + deleteAfter * 1000) {
return true;
}
}
return false;
}
至此整个缓存字符串读取过程在ACache的源码分析完成,其他缓存数据类型读取方法分析过程一样。
JsonObject、JsonArray、Bitmap、Drawable、序列化的存入缓存都是转化为字符串/byte格式,再调用函数put(String key, String value)即可。
参考资料http://blog.csdn.net/zhoubin1992/article/details/46379055
http://blog.csdn.net/developer_jiangqq/article/details/49406361?hmsr=toutiao.io& utm_medium=toutiao.io& utm_source=toutiao.io
http://stormzhang.com/android/2014/10/17/android-simple-cache/
推荐阅读
- Android内存泄漏的各种原因详解
- android view的自定义
- Android安全加密(数字签名和数字证书)
- JQ实现判断iPhoneAndroid设备
- android 解决 多品牌手机拍照问题,尤其是小米手机
- android 多行 RadioButton的使用
- Android中是否推荐使用枚举Enum
- 如何利用Thread制作简单的android动画
- android问题笔记集