简介
提供Android缓存功能,包括对SD卡
,内存
、Sharedpreference
以及同时存储SD卡
和内存
的双层缓存操作,缓存对象包括:实现序列化的对象
,Bitmap
以及字符数组
。下载项目https://github.com/bh4614910/RxCache。
1.使用
导入项目依赖
implementation "io.reactivex:rxandroid:1.2.1"
implementation "io.reactivex:rxjava:1.1.6"
在调用缓存API之前需要初始化缓存配置,推荐在Application当中进行初始化.
//初始化缓存配置,包括磁盘缓存路径,缓存大小,内存缓存大小,加密策略等。
// 最后调用.install(this)方法完成初始化
CacheInstaller.get()
.configDiskCache("TestCache", 50 * 1024 * 1024, 1)
.install(this);
完成初始化之后就可以正常使用缓存操作了。
存储
项目本身一共两种缓存的调用方式:
- 直接在项目当中进行链式的调用。
- 一种是类似于retrofit的接口调用方式。
存储的对象可以是实现序列化的对象
,Bitmap
以及字符数组
。以缓存bitmap
为例,看一下调用实例:
调用方式一
/**
* 定义接口
*/
public interface TestInerface {
//注解标明请求方式,超时时间等等
//method设置当前操作为put,调用缓存到SD卡以及内存当中的双层缓存
@Method(methodType = MethodType.PUT,cacheType = CacheType.TWO_LAYER)
//设置过期时间为1天
@Lifecycle(time = 1,unit = TimeUnit.DAYS)
Observable putData( @CacheKey String key,@CacheValue T value, @CacheClass Class clazz);
}
//调用缓存存储bitmap
TestInerface testInerface = RetrofitCache.create(TestInerface.class);
testInerface.putData("testKey", bitmap, Bitmap.class).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1() {
@Override
public void call(Boolean aBoolean) {
Toast.makeText(TestActivity.this, aBoolean + "", Toast.LENGTH_SHORT).show();
}
});
整个存储过程可分为两步:
- 定义接口,并通过注解标明请求方式,请求参数等。
- 在项目中调用缓存API。
整个API的调用过程与Retrofit很相似,在定义接口时的注解说明如下:
注解 |
类型 |
说明 |
@Lifecycle |
方法注解 |
设置过期时间,包括时长和单位,存储时调用 |
@Method |
方法注解 |
设置缓存方法以及存储方式 |
@ShareName |
方法注解 |
sharedPreference缓存时的文件名 |
@Strategy |
方法注解 |
设置超时策略,读取缓存时调用 |
@CacheClass |
参数注解 |
设置缓存类,标注一个Class对象 |
@CacheKey |
参数注解 |
设置缓存的key值,标注一个String对象 |
@CacheValue |
参数注解 |
设置缓存内容 |
调用方式二
直接通过链式调用
//调用put方法存储数据
RxCache.get().setTimeout(1, TimeUnit.DAYS)
.putData2TwoLayer("diskKey", bitmap).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1() {
@Override
public void call(Boolean aBoolean) {
Toast.makeText(TestActivity.this, aBoolean + "", Toast.LENGTH_SHORT).show();
}
});
//setTimeout方法设置超时时间
//putData2TwoLayer调用双层缓存,参数为缓存的key值以及缓存内容
调用存储方法putXX
后返回一个Observable
对象,当返回true时代表缓存成功,返回false代表缓存失败。
两种方法各有利弊
- 方式一方便对缓存的管理,并省去在项目中对缓存策略等的配置内容。
- 方式二调用方式更直接,代码也相对更少一些。
注意:无论哪种调用方式,都需要先初始化配置信息。
读取
读取方式和存储类似,也分为两种,详细调用内容不再赘述,直接看代码。
//----------------------方式一------------------------------
//定义接口
public interface TestInerface {
//注解标明请求方式,超时策略等等
//请求方式为get,读取对象为从SD卡中读取
@Method(methodType = MethodType.GET,cacheType = CacheType.DISK)
//设置超时策略,当数据超时时返回null
@Strategy(key = ExpirationPolicies.ReturnNull)
Observable getData(@CacheKey String key, @CacheClass Class clazz);
}
testInerface.getData("testKey",Bitmap.class).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1() {
@Override
public void call(Bitmap s) {
if (s != null) {
testImage.setImageBitmap(s);
} else {
Toast.makeText(TestActivity.this, "数据为null", Toast.LENGTH_SHORT).show();
}
}
});
//----------------------方式二------------------------------
RxCache.get().getDataTwoLayer("diskKey", Bitmap.class).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1() {
@Override
public void call(Bitmap s) {
if (s != null) {
testImage.setImageBitmap(s);
} else {
Toast.makeText(TestActivity.this, "数据为null", Toast.LENGTH_SHORT).show();
}
}
});
读取缓存会返回一个Observable对象,通过subscribe()订阅后可以拿到返回的数据,并进行操作。
注意:默认执行subscribe()的线程为调用时所在线程,如果需要修改线程,需自行调用observeOn()方法修改调用线程。
另外还有删除
和清空缓存
等API,调用方式与存储
、读取
类似,省略这部分内容,感兴趣的可以自己下载试一下。https://github.com/bh4614910/RxCache
2.架构设计
说完对整个API的使用,再来详细看一下整个缓存的项目结构。
整个项目可以大体分为三层
- 基础层:主要负责存储的基础操作,包括对
SD卡
、内存
以SharedPreference
的基础操作。
- 控制层:负责根据对不同的事务类型进行分发。
- API:对外暴露的API,目前提供两种API调用方式。
3.基础层实现
基础操作分为三种:sharedPreference
、memory
以及disk
。对于三种存储方式,提供统一的供上层调用的API接口CacheWrapper
。
/**
* 缓存控制类接口
*/
public interface CacheWrapper {
/**
* 读取缓存类
*
* @param 缓存值类型,需要实现Parcelable接口
* @param key 缓存的key值
* @return 返回CacheResult类型
*/
CacheResource get(String key, Class clazz);
/**
* 存储缓存类
*
* @param key 缓存的key值
* @param value 缓存值
* @param 缓存值类型,需要实现Parcelable接口
* @return 返回true或者false表示缓存是否成功
*/
boolean put(String key, CacheResource value);
/**
* 清空缓存
*/
void clear();
/**
* 删除某个值
*
* @param key 需要删除的缓存值对应key
* @return 返回true或者false表示删除是否成功
*/
boolean remove(String key);
/**
* 构造用工厂接口
*/
interface Factory {
}
interface Factory2 {
CacheWrapper create(Context context, CacheType type, String shareName);
}
}
各个存储方法再各自实现对应的存储内容。
sharedPreference
是我们在项目当中经常用到的,为了让它也满足上层API的调用,我们对它的基础操作进行封装PreferenceProvider
,之后再对接口进行具体实现DiskCacheWrapper
。
memory
也就是我们的内存缓存,我们选用LruCache
作为基础操作类型,LruCache
的核心思想就是要维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。有兴趣的可以去了解一下LruCache
的具体实现。
使用时我们先初始化LruCache
并重写sizeOf
方法,计算存储数据的大小,这里我提供了一个SizeUtil
方便大小的计算,之后的调用方式非常简单,直接看代码
/**
* 内存缓存控制类
*/
public class MemoryCacheWrapper implements CacheWrapper {
private LruCache memoryCache;
private static final int DEFAULT_MEMORY_CACHE_SIZE = (int) (Runtime.getRuntime().maxMemory() / 8);
public static MemoryCacheWrapper get(){
return MemoryCacheHolder.mInstance;
}
private MemoryCacheWrapper() {
memoryCache = new LruCache(getCacheSize()) {
@Override
protected int sizeOf(String key, Object value) {
if (Bitmap.class.isAssignableFrom(value.getClass())) {
return (int)SizeUtil.getBitmapSize((Bitmap) value);
} else {
return (int) SizeUtil.getValueSize(value);
}
}
};
}
/**
* 获取缓存大小
*
* @return
*/
private int getCacheSize() {
int cacheSize = CacheInstaller.get().getMemorySize();
if (cacheSize <= 0) {
cacheSize = DEFAULT_MEMORY_CACHE_SIZE;
}
return cacheSize;
}
@Override
public CacheResource get(String key, Class clazz) {
CacheResource value = (CacheResource) memoryCache.get(key);
if (value != null) {
return value;
}
return null;
}
@Override
public boolean put(String key, CacheResource value) {
if (value != null && memoryCache.get(key) == null) {
memoryCache.put(key, value);
return true;
} else {
LogUtil.log("value值为空或key值以及存在");
}
return false;
}
@Override
public void clear() {
memoryCache.evictAll();
}
@Override
public boolean remove(String key) {
Object object = memoryCache.remove(key);
if (object == null) {
return false;
} else {
return true;
}
}
private static class MemoryCacheHolder {
public static MemoryCacheWrapper mInstance = new MemoryCacheWrapper();
private MemoryCacheHolder() {
}
}
}
有些缓存模块没有使用LruCache
,而是使用HashMap
作为存储结构,两种方案都是可行的,这里使用LruCache
主要是为了方便图片的存储。
细心的朋友会发现这里put
的参数和get
返回的数据都是CacheResource
类型,我们把存储的数据,以及超时时间等统一的存储进这个数据结构,也就是说CacheResource
作为控制层和基础层传递的介质。
之后就是disk
也就是SD卡的存储。这一部分使用DiskLruCache
作为基础操作类型,和sharedPreference一样,首先我们对DiskLruCache
的操作进行封装,以统一对上层调用的API。
/**
* Created by liubohua on 2018/7/24.
* 提供本地缓存基础操作。
*/
public class DiskCacheProvider {
private DiskLruCache diskLruCache;
private Converter objectConverter;
private Converter bitmapConverter;
private Converter byteArrayConverter;
public DiskCacheProvider(File directory, int appVersion, long maxSize) {
objectConverter = new ObjectConverter();
bitmapConverter = new BitmapConverter();
byteArrayConverter = new ByteArrayConverter();
try {
diskLruCache = DiskLruCache.open(directory, appVersion, 1, maxSize);
} catch (IOException e) {
e.printStackTrace();
}
}
public CacheResource getBitmap(String key) {
DiskLruCache.Snapshot snapShot = null;
try {
snapShot = diskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
CacheResource value = null;
value = bitmapConverter.read(is);
if (value != null) {
return value;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
snapShot.close();
}
return null;
}
public CacheResource