本文正如标题所说的用rxjava实现数据的三级缓存分别为内存,磁盘,网络,刚好最近在看Android源码设计模式解析与实战(受里面的ImageLoader的设计启发)。
我把代码放到了我的hot项目中,Hot是关于微信头条分享的app
github地址
1.使用concat()和first()的操作符。
2.使用BehaviorSubject。
先说BehaviorSubject的实现方法,废话不多说直接上代码,
/**
* Created by wukewei on 16/6/20.
*/
public class BehaviorSubjectFragment extends BaseFragment {
public static BehaviorSubjectFragment newInstance() {
BehaviorSubjectFragment fragment = new BehaviorSubjectFragment();
return fragment;
}
String diskData = null;
String networkData = "从服务器获取的数据";
BehaviorSubject cache;
View mView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.fragment_content, container, false);
init();
return mView;
}
private void init() {
mView.findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
subscriptionData(new Observer() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
Log.d("onNext", s);
}
});
}
});
mView.findViewById(R.id.memory).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BehaviorSubjectFragment.this.cache = null;
}
});
mView.findViewById(R.id.memory_disk).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BehaviorSubjectFragment.this.cache = null;
BehaviorSubjectFragment.this.diskData = null;
}
});
}
private void loadNewWork() {
Observable o = Observable.just(networkData)
.doOnNext(new Action1() {
@Override
public void call(String s) {
BehaviorSubjectFragment.this.diskData = s;
Log.d("写入磁盘", "写入磁盘");
}
});
o.subscribe(new Action1() {
@Override
public void call(String s) {
cache.onNext(s);
}
}, new Action1() {
@Override
public void call(Throwable throwable) {
}
});
}
private Subscription subscriptionData(@NonNull Observer observer) {
if (cache == null) {
cache = BehaviorSubject.create();
Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super String> subscriber) {
String data = diskData;
if (data == null) {
Log.d("来自网络", "来自网络");
loadNewWork();
} else {
Log.d("来自磁盘", "来自磁盘");
subscriber.onNext(data);
}
}
})
.subscribeOn(Schedulers.io())
.subscribe(cache);
} else {
Log.d("来自内存", "来自内存");
}
return cache.observeOn(AndroidSchedulers.mainThread()).subscribe(observer);
}
}
其中最主要的是subscriptionData()这个方法,就是先判断 cache是否存在要是存在的话就返回内存中数据,再去判断磁盘数据是否存在,如果存在就返回,要是前面两种都不存在的时候,再去网络中获取数据。还有最重要的是当你从网络获取数据的时候要记得保存在内存中和保存在磁盘中,在磁盘获取数据的时候把它赋值给内存。
接下来就说说用concat()和first()的操作符来实现,这是我在看Android源码设计模式解析与实战,作者在第一章的时候就介绍ImageLoader的设计。
在内存中存储的方式LruCache来实现的,磁盘存储的方式就是序列化存储。
1.定义一个接口:
/**
* Created by wukewei on 16/6/19.
*/
public interface ICache {
Observable get(String key, Class cls);
void put(String key, T t);
}
2.内存存储的实现
/**
* Created by wukewei on 16/6/19.
*/
public class MemoryCache implements ICache{
private LruCache mCache;
public MemoryCache() {
final int maxMemory = (int) Runtime.getRuntime().maxMemory();
final int cacheSize = maxMemory / 8;
mCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, String value) {
try {
return value.getBytes("UTF-8").length;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return value.getBytes().length;
}
}
};
}
@Override
public Observable get(final String key, final Class cls) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super T> subscriber) {
String result = mCache.get(key);
if (subscriber.isUnsubscribed()) {
return;
}
if (TextUtils.isEmpty(result)) {
subscriber.onNext(null);
} else {
T t = new Gson().fromJson(result, cls);
subscriber.onNext(t);
}
subscriber.onCompleted();
}
});
}
@Override
public void put(String key, T t) {
if (null != t) {
mCache.put(key, new Gson().toJson(t));
}
}
public void clearMemory(String key) {
mCache.remove(key);
}
}
3.磁盘存储的实现
/**
* Created by wukewei on 16/6/19.
*/
public class DiskCache implements ICache{
private static final String NAME = ".db";
public static long OTHER_CACHE_TIME = 10 * 60 * 1000;
public static long WIFI_CACHE_TIME = 30 * 60 * 1000;
File fileDir;
public DiskCache() {
fileDir = CacheLoader.getApplication().getCacheDir();
}
@Override
public Observable get(final String key, final Class cls) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super T> subscriber) {
T t = (T) getDiskData1(key + NAME);
if (subscriber.isUnsubscribed()) {
return;
}
if (t == null) {
subscriber.onNext(null);
} else {
subscriber.onNext(t);
}
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
@Override
public void put(final String key, final T t) {
Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super T> subscriber) {
boolean isSuccess = isSave(key + NAME, t);
if (!subscriber.isUnsubscribed() && isSuccess) {
subscriber.onNext(t);
subscriber.onCompleted();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
}
/**
* 保存数据
*/
private boolean isSave(String fileName, T t) {
File file = new File(fileDir, fileName);
ObjectOutputStream objectOut = null;
boolean isSuccess = false;
try {
FileOutputStream out = new FileOutputStream(file);
objectOut = new ObjectOutputStream(out);
objectOut.writeObject(t);
objectOut.flush();
isSuccess=true;
} catch (IOException e) {
Log.e("写入缓存错误",e.getMessage());
} catch (Exception e) {
Log.e("写入缓存错误",e.getMessage());
} finally {
closeSilently(objectOut);
}
return isSuccess;
}
/**
* 获取保存的数据
*/
private Object getDiskData1(String fileName) {
File file = new File(fileDir, fileName);
if (isCacheDataFailure(file)) {
return null;
}
if (!file.exists()) {
return null;
}
Object o = null;
ObjectInputStream read = null;
try {
read = new ObjectInputStream(new FileInputStream(file));
o = read.readObject();
} catch (StreamCorruptedException e) {
Log.e("读取错误", e.getMessage());
} catch (IOException e) {
Log.e("读取错误", e.getMessage());
} catch (ClassNotFoundException e) {
Log.e("错误", e.getMessage());
} finally {
closeSilently(read);
}
return o;
}
private void closeSilently(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception ignored) {
}
}
}
/**
* 判断缓存是否已经失效
*/
private boolean isCacheDataFailure(File dataFile) {
if (!dataFile.exists()) {
return false;
}
long existTime = System.currentTimeMillis() - dataFile.lastModified();
boolean failure = false;
if (NetWorkUtil.getNetworkType(CacheLoader.getApplication()) == NetWorkUtil.NETTYPE_WIFI) {
failure = existTime > WIFI_CACHE_TIME ? true : false;
} else {
failure = existTime > OTHER_CACHE_TIME ? true : false;
}
return failure;
}
public void clearDisk(String key) {
File file = new File(fileDir, key + NAME);
if (file.exists()) file.delete();
}
}
isCacheDataFailure()方式中就是判断当前的数据是否失效,我是根据当前的网络状况来分wifi状况和非wifi状况,wifi状态下数据过期时间比较短,其他状态过期时间比较长。
4.CacheLoader的设计
**
* Created by wukewei on 16/6/19.
*/
public class CacheLoader {
private static Application application;
public static Application getApplication() {
return application;
}
private ICache mMemoryCache, mDiskCache;
private CacheLoader() {
mMemoryCache = new MemoryCache();
mDiskCache = new DiskCache();
}
private static CacheLoader loader;
public static CacheLoader getInstance(Context context) {
application = (Application) context.getApplicationContext();
if (loader == null) {
synchronized (CacheLoader.class) {
if (loader == null) {
loader = new CacheLoader();
}
}
}
return loader;
}
public Observable asDataObservable(String key, Class cls, NetworkCache networkCache) {
Observable observable = Observable.concat(
memory(key, cls),
disk(key, cls),
network(key, cls, networkCache))
.first(new Func1() {
@Override
public Boolean call(T t) {
return t != null;
}
});
return observable;
}
private Observable memory(String key, Class cls) {
return mMemoryCache.get(key, cls).doOnNext(new Action1() {
@Override
public void call(T t) {
if (null != t) {
Log.d("我是来自内存","我是来自内存");
}
}
});
}
private Observable disk(final String key, Class cls) {
return mDiskCache.get(key, cls)
.doOnNext(new Action1() {
@Override
public void call(T t) {
if (null != t) {
Log.d("我是来自磁盘","我是来自磁盘");
mMemoryCache.put(key, t);
}
}
});
}
private Observable network(final String key, Class cls
, NetworkCache networkCache) {
return networkCache.get(key, cls)
.doOnNext(new Action1() {
@Override
public void call(T t) {
if (null != t) {
Log.d("我是来自网络","我是来自网络");
mDiskCache.put(key, t);
mMemoryCache.put(key, t);
}
}
});
}
public void clearMemory(String key) {
((MemoryCache)mMemoryCache).clearMemory(key);
}
public void clearMemoryDisk(String key) {
((MemoryCache)mMemoryCache).clearMemory(key);
((DiskCache)mDiskCache).clearDisk(key);
}
}
5.网络获取的NetworkCache:
/**
* Created by wukewei on 16/6/19.
*/
public abstract class NetworkCache {
public abstract Observable get(String key, final Class cls);
}
6.接下来看怎么使用
/**
* Created by wukewei on 16/5/30.
*/
public class ItemPresenter extends BasePresenter implements ItemContract.Presenter {
private static final String key = "new_list";
protected int pn = 1;
protected void replacePn() {
pn = 1;
}
private boolean isRefresh() {
return pn == 1;
}
private NetworkCache networkCache;
public ItemPresenter(Activity activity, ItemContract.View view) {
super(activity, view);
}
@Override
public void getListData(String type) {
if (isRefresh()) mView.showLoading();
networkCache = new NetworkCache() {
@Override
public Observable get(String key, Class cls) {
return mHotApi.getPopular(ItemPresenter.this.pn, Constants.PAGE_SIZE, type)
.compose(SchedulersCompat.applyIoSchedulers())
.compose(RxResultHelper.handleResult())
.flatMap(populars -> {
ListPopular popular = new ListPopular(populars);
return Observable.just(popular);
});
}
};
Subscription subscription = CacheLoader.getInstance(mActivity)
.asDataObservable(key + type + ItemPresenter.this.pn, ListPopular.class, networkCache)
.map(listPopular -> listPopular.data)
.subscribe(populars -> {
mView.showContent();
if (isRefresh()) {
if (populars.size() == 0) mView.showNotdata();
mView.addRefreshData(populars);
} else {
mView.addLoadMoreData(populars);
}
}, throwable -> {
if (isRefresh())
mView.showError(ErrorHanding.handleError(throwable));
handleError(throwable);
});
addSubscrebe(subscription);
}
}
一定要给个key,我是根据key来获取数据的,还要就是给个类型。
但是这个我设计的这个缓存还是不是很理想,接来下想要实现的就是在传入的时候类的class都不用给明,要是有好的实现的方式,欢迎告诉我。再次给上我的github地址。谢谢