8

android轻量级开源缓存框架——ASimpleCache(ACache)源码分析

 2 years ago
source link: http://www.androidchina.net/2640.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ASimpleCache框架源码链接

https://github.com/yangfuhai/ASimpleCache

杨神作品,大家最熟悉他的应该是afinal框架吧


官方介绍

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个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量

3、您来说…

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 = mCache.getAsString("test_key1");

源码分析

一、ACache类结构图

ASimpleCache里只有一个JAVA文件——ACache.java

首先我用思维导图制作了ACache类的详细结构图:
ACache类结构图


二、官方demo分析

通过分析官方给的demo来驱动源码分析吧

以字符串存储为例(官方给的demo里给出了很多种数据读取的例子,其实方法相似),打开SaveStringActivity.java:

package com.yangfuhai.asimplecachedemo;
import org.afinal.simplecache.ACache;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
/**
*
* @ClassName: SaveStringActivity
* @Description: 缓存string
* @Author Yoson Hao
* @WebSite www.haoyuexing.cn
* @Date 2013-8-7 下午9:59:43
*
*/
public class SaveStringActivity extends Activity {
private EditText mEt_string_input;
private TextView mTv_string_res;
private ACache mCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_save_string);
// 初始化控件
initView();
mCache = ACache.get(this);
}
/**
* 初始化控件
*/
private void initView() {
mEt_string_input = (EditText) findViewById(R.id.et_string_input);
mTv_string_res = (TextView) findViewById(R.id.tv_string_res);
}
/**
* 点击save事件
*
* @param v
*/
public void save(View v) {
if (mEt_string_input.getText().toString().trim().length() == 0) {
Toast.makeText(
this,
"Cuz u input is a nullcharacter ... So , when u press \"read\" , if do not show any result , plz don't be surprise",
Toast.LENGTH_SHORT).show();
}
//      mCache.put("testString", mEt_string_input.getText().toString());
mCache.put("testString", mEt_string_input.getText().toString(),300);
}
/**
* 点击read事件
*
* @param v
*/
public void read(View v) {
String testString = mCache.getAsString("testString");
if (testString == null) {
Toast.makeText(this, "String cache is null ...", Toast.LENGTH_SHORT)
.show();
mTv_string_res.setText(null);
return;
}
mTv_string_res.setText(testString);
}
/**
* 点击clear事件
*
* @param v
*/
public void clear(View v) {
mCache.remove("testString");
}
}

可以看到缓存字符串的读取方法很简单!!!

  1. 在onCreate里通过get方式获取缓存实例
    mCache = ACache.get(this);
  2. 在save按钮的点击事件里,通过put方法往缓存实例里保存字符串
    mCache.put(“testString”, mEt_string_input.getText().toString(),300);
  3. 在read按钮的点击事件里,通过getAsString方法从缓存实例里读取字符串
    mCache.getAsString(“testString”);
    其他数据读取,方法相似,也是这三个步骤。300为保存时间300秒。

三、ACache源码分析

1、获取缓存实例

那我们就从ACache.get()开始吧,其实查看上面的思维导图,ACache类的构造方法为private的,所以新建缓存实例只能通过ACache.get方式获取。

//实例化应用程序场景缓存
public static ACache get(Context ctx) {
return get(ctx, "ACache");
}
//新建缓存目录
public static ACache get(Context ctx, String cacheName) {
//新建文件夹,文件路径为应用场景缓存路径目录,文件夹名为ACache(new File()也可新建文件,带上后缀即可)
File f = new File(ctx.getCacheDir(), cacheName);
return get(f, MAX_SIZE, MAX_COUNT);
}
//新建缓存实例,存入实例map,key为缓存目录+每次应用开启的进程id
public static ACache get(File cacheDir, long max_zise, int max_count) {
//返回key为缓存目录+每次应用开启的进程id的map的value值,赋给缓存实例manager
ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid());
if (manager == null) { //缓存实例为空时,
manager = new ACache(cacheDir, max_zise, max_count);
mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager);//插入map
}
return manager;
}

在调用ACache.get(Context)方法过程中,其实执行了三个get方法

(1)get(Context ctx)->(2)get(Context ctx, String cacheName)->(3)get(File cacheDir, long max_zise, int max_count)

在(2)中新建了缓存目录,路径为:

/data/data/app-package-name/cache/ACache

缓存大小MAX_SIZE和数量MAX_COUNT均由final变量控制。

其实最终调用(3)获取实例:

mInstanceMap的key为缓存目录+每次应用开启的进程id,value为ACache.

初次运行,mInstanceMap没有任何键值对,所以manager == null。故通过ACache构造方法构造新实例,最后将该实例引用存入mInstanceMap。

接下来我们来看看ACache构造方法:

//ACache构造函数 为private私有(所以在其他类里获得实例只能通过get()方法)
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内部类的构造函数如下:

//内部类ACacheManager的构造函数
private ACacheManager(File cacheDir, long sizeLimit, int countLimit) {
this.cacheDir = cacheDir;
this.sizeLimit = sizeLimit;
this.countLimit = countLimit;
cacheSize = new AtomicLong();       //原子类实例cacheSize,不用加锁保证线程安全
cacheCount = new AtomicInteger();   //原子类实例cacheCount,不用加锁保证线程安全
calculateCacheSizeAndCacheCount();
}

构造函数得到原子类实例cacheSize和cacheCount,通过calculateCacheSizeAndCacheCount();方法计算cacheSize和cacheCount如下:

/**
* 计算 cacheSize和cacheCount
*/
private void calculateCacheSizeAndCacheCount() {
new Thread(new Runnable() {
@Override
public void run() {
//int size = 0;
long size = 0//这里long类型才对——by牧之丶
int count = 0;
File[] cachedFiles = cacheDir.listFiles();  //返回缓存目录cacheDir下的文件数组
if (cachedFiles != null) {
for (File cachedFile : cachedFiles) {   //对文件数组遍历
size += calculateSize(cachedFile);
count += 1;
lastUsageDates.put(cachedFile, cachedFile.lastModified());  //将缓存文件和最后修改时间插入map
}
cacheSize.set(size);        //设置为给定值
cacheCount.set(count);      //设置为给定值
}
}
}).start();
}

calculateCacheSizeAndCacheCount方法中开启线程进行大小和数量的计算。计算完后存入cacheSize和cacheCount,cacheSize和cacheCount在内部类中定义为AtomicLong和AtomicInteger量子类,也就是线程安全的。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入。


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

  1. 新建了缓存目录
  2. 通过ACache构造方法构造新实例,并且将该实例引用插入mInstanceMap
  3. 实例化ACacheManager,计算cacheSize和cacheCount

接下来就是数据存取操作。

2、往缓存实例存入数据

从上面的思维导图public method(各种数据的读写方法)中,有各种public的put和get等方法来缓存各种数据类型的数据。由上面的demo的put方法

<strong> mCache.put(“testString”, mEt_string_input.getText().toString(),300);</strong>

我们找到原形:

/**
* 保存 String数据 到 缓存中
*
* @param key
*            保存的key
* @param value
*            保存的String数据
* @param saveTime
*            保存的时间,单位:秒
*/
public void put(String key, String value, int saveTime) {
put(key, Utils.newStringWithDateInfo(saveTime, value));
}

这里的put方法可以指定缓存时间。调用他自身的另一个put方法:

/**
* 保存 String数据 到 缓存中
*
* @param key
*            保存的key
* @param value
*            保存的String数据
*/
public void put(String key, String value) {
File file = mCache.newFile(key);    //新建文件
BufferedWriter out = null;          //缓冲字符输出流,作用是为其他字符输出流添加一些缓冲功能
try {
out = new BufferedWriter(new FileWriter(file), 1024);   //获取file字符流
out.write(value);       //  把value写入
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mCache.put(file);   //更新cacheCount和cacheSize  lastUsageDates插入新建文件和时间的键值对
}
}

在put(String key, String value)方法中首先调用mCache.newFile(key)新建一个文件:

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

新建的文件名为key的整型哈希码。回到put(String key, String value)中,然后通过out.write(value);将数据存入文件。最后调用mCache.put(file);进行ACacheManager实例的更新操作

//更新cacheCount和cacheSize  lastUsageDates插入新建文件和时间的键值对
//文件放入程序缓存后,统计缓存总量,总数,文件存放到文件map中(value值为文件最后修改时间,便于根据设置的销毁时间进行销毁)
//缓存没有超过限制,则增加缓存总量,总数的数值
//缓存超过限制,则减少缓存总量,总数的数值
//通过removeNext方法找到最老文件的大小
private void put(File file) {
int curCacheCount = cacheCount.get();   //获取数量
while (curCacheCount + 1 > countLimit) {    //大于上限
long freedSize = removeNext();          //移除旧的文件,返回文件大小
cacheSize.addAndGet(-freedSize);        //更新cacheSize
curCacheCount = cacheCount.addAndGet(-1);//更新cacheCount
}
cacheCount.addAndGet(1);//更新cacheCount
long valueSize = calculateSize(file);       //计算文件大小
long curCacheSize = cacheSize.get();        //获取当前缓存大小
while (curCacheSize + valueSize > sizeLimit) {  //大于上限
long freedSize = removeNext();              //移除旧的文件,返回文件大小
curCacheSize = cacheSize.addAndGet(-freedSize);     //更新curCacheSize
}
cacheSize.addAndGet(valueSize);                         //更新cacheSize
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);          //设置文件最后修改时间
lastUsageDates.put(file, currentTime);      //插入map
}

分析完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; //当前时间-保存时间
}

返回字符串格式为 当前时间-保存时间“ ”


3、从缓存实例读取数据

由上面的demo的get方法mCache.getAsString(“testString”);我们找到原形:

/**
* 读取 String数据
*
* @param key
* @return String 数据
*/
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);    //移除缓存
}
}

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)即可。

比如BitMap的保存:

/**
* 保存 bitmap 到 缓存中,有保存时间
*
* @param key
*            保存的key
* @param value
*            保存的 bitmap 数据
* @param saveTime
*            保存的时间,单位:秒
*/
public void put(String key, Bitmap value, int saveTime) {
put(key, Utils.Bitmap2Bytes(value), saveTime);
}

Bitmap的读取:

/**
* 读取 bitmap 数据
*
* @param key
* @return bitmap 数据
*/
public Bitmap getAsBitmap(String key) {
if (getAsBinary(key) == null) {
return null;
}
return Utils.Bytes2Bimap(getAsBinary(key));
}

思想就是把bitmap转化为byte[], 再调用缓存byte的函数 。通过Utils.Bitmap2Bytes(value)完成Bitmap → byte[] 的转换。通过Utils.Bytes2Bimap(getAsBinary(key))完成byte[] → Bitmap的转换。

/*
* Bitmap → byte[]
*/
private static byte[] Bitmap2Bytes(Bitmap bm) {
if (bm == null) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();//byte[]输出流
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);//按指定的图片格式以及画质,将图片转换为输出流。压缩图片,不压缩是100,表示压缩率为0
return baos.toByteArray();
}
/*
* byte[] → Bitmap
*/
private static Bitmap Bytes2Bimap(byte[] b) {
if (b.length == 0) {
return null;
}
return BitmapFactory.decodeByteArray(b, 0, b.length);//解析
}

很简单吧。Drawable的缓存就是先转化为Bitmap,之后就是上面的步骤转换成byte。


总结

该开源库类简单,容易理解。

可以使用ACache把那些不需要实时更新的数据缓存起来,一来减少网络请求,二来本地加载速度也快。

可以设置缓存时间。

可以替换SharePreference当做配置文件,保存多种数据类型,比如可以保存头像信息。

转载请注明出处:http://blog.csdn.net/zhoubin1992/article/details/46379055

转载请注明:Android开发中文站 » android轻量级开源缓存框架——ASimpleCache(ACache)源码分析


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK