为了账号安全,请及时绑定邮箱和手机立即绑定

Android ASimpleCache缓存源码分析

标签:
Android

ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)
仓库地址:https://github.com/yangfuhai/ASimpleCache

官方介绍

1、它可以缓存什么东西?

普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。

2、它有什么特色?

  • 特色主要是:

    • 1:轻,轻到只有一个JAVA文件。

    • 2:可配置,可以配置缓存路径,缓存大小,缓存数量等。

    • 3:可以设置缓存超时时间,缓存超时自动失效,并被删除。

    • 4:支持多进程。

##3、它在android中可以用在哪些场景?

  • 1、替换SharePreference当做配置文件

  • 2、可以缓存网络请求数据,比如oschina的android客户端可以缓存http请求的新闻内容,缓存时间假设为1个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量。

  • 3、您来说…

##4、如何使用 ASimpleCache?
以下有个小的demo,希望您能喜欢:

1
2
3
4
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

获取数据

1
2
ACache mCache = ACache.get(this);
String value = mCache.getAsString("test_key1");


分析

ACache 构造

可以由上个简单的例子可以看出 ACache是核心类,但是起构造方法是 private 的,我们只能通过ACache.get()获得其实例,由此做切入分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static ACache get(Context ctx) {
	return get(ctx, "ACache");
}

public static ACache get(Context ctx, String cacheName) {
	File f = new File(ctx.getCacheDir(), cacheName);
	return get(f, MAX_SIZE, MAX_COUNT);
}

public static ACache get(File cacheDir) {
	return get(cacheDir, MAX_SIZE, MAX_COUNT);
}

public static ACache get(Context ctx, long max_zise, int max_count) {
	File f = new File(ctx.getCacheDir(), "ACache");
	return get(f, max_zise, max_count);
}

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;
}
  1. mCache = ACache.get(this);

  2. ACache get(Context ctx, String cacheName) ,在 /data/data/app-package-name/cache/ACache创建了缓存目录

  3. ACache get(File cacheDir, long max_zise, int max_count),后两个参数在未传入默认值,在初次运行时manager ==null,最终会调用到 ACache的 private 的构造方法,并且将结果放入mInstanceMap中,key 值为路径+Pid,value 为实例化的ACache对象

1
2
private static final int MAX_SIZE = 1000 * 1000 * 50; // 50 mb
private static final int MAX_COUNT = Integer.MAX_VALUE; // 不限制存放数据的数量

继续看ACache 的构造方法

1
2
3
4
5
6
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);
}

如果目录文件不存在会抛出异常,同时最终将全局变量 ACacheManager初始化。

ACacheManager

ACacheManager 是一个缓存管理器,ACache的内部类,最终的缓存的 put 和 get都由这个类管理

1
2
3
4
5
6
7
8
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 都是原子量,不用加锁保证了线程安全,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 计算 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) {
				//遍历 cacheDir 中的文件,并计算大小和数量
					size += calculateSize(cachedFile);
					count += 1;
					//lastUsageDates Map 中保存了<File,最后修改时间>
					lastUsageDates.put(cachedFile, cachedFile.lastModified());
				}
				cacheSize.set(size);
				cacheCount.set(count);
			}
		}
	}).start();
}

在 calculateCacheSizeAndCacheCount,开了一个线程计算cacheSizecacheCount

到这里获取缓存实例工作完成,主要完成了如下工作:

  1. 新建了缓存目录

  2. 通过ACache构造方法构造新实例,并且将该实例引用插入mInstanceMap

  3. 实例化ACacheManager,计算cacheSize和cacheCount

###缓存写数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
 * 保存 String数据 到 缓存中
 * 
 * @param key
 *            保存的key
 * @param value
 *            保存的String数据
 * @param saveTime
 *            保存的时间,单位:秒
 */
public void put(String key, String value, int saveTime) {
	put(key, Utils.newStringWithDateInfo(saveTime, value));
}

/**
 * 保存 String数据 到 缓存中
 * 
 * @param key
 *            保存的key
 * @param value
 *            保存的String数据
 */
public void put(String key, String value) {
	//新建文件,每一个 key 对应一个文件
	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
		mCache.put(file);
	}
}

------
   private File newFile(String key) {
       return new File(cacheDir, key.hashCode() + "");     //新建文件,文件名为key的整型哈希码
   }

put(String key, String value, int saveTime)第三个是缓存的有效时间

Utils.newStringWithDateInfo(saveTime, value)会将time 和 value 整合成一个 String

在看看mCache.put(file);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void put(File file) {
	int curCacheCount = cacheCount.get();
	//检查文件个数是不是超过显示
	while (curCacheCount + 1 > countLimit) {
		long freedSize = removeNext();//移除旧的文件,返回文件大小
		cacheSize.addAndGet(-freedSize);//更新cacheSize
		curCacheCount = cacheCount.addAndGet(-1);//更新curCacheCount
	}
	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 Map 中保存了<File,最后修改时间>
	lastUsageDates.put(file, currentTime); //更新 map
}

移除最不常用的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
**
 * 移除旧的文件
 * @return
 */
private long removeNext() {
	if (lastUsageDates.isEmpty()) {
		return 0;
	}

	Long oldestUsage = null;
	File mostLongUsedFile = null;
	Set<Entry<File, Long>> entries = lastUsageDates.entrySet();//获得 Entry<K,V> 的集合
	synchronized (lastUsageDates) {
		for (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();
}
}

缓存读数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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)) { //String数据未到期
		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);
}
}
```	


```java
/**
 * 判断缓存的String数据是否到期
 * 
 * @param str
 * @return true:到期了 false:还没有到期
 */
private static boolean isDue(String str) {
	return isDue(str.getBytes());
}

/**
 * 判断缓存的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;
}

JsonObject、JsonArray、Bitmap、Drawable、序列化的存入缓存都是转化为字符串/byte格式,再调用函数put(String key, String value)即可。

原文链接:http://www.apkbus.com/blog-705730-60998.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消