本文正如標(biāo)題所說的用rxjava實(shí)現(xiàn)數(shù)據(jù)的三級緩存分別為內(nèi)存病袄,磁盤,網(wǎng)絡(luò)观话,剛好最近在看Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)(受里面的ImageLoader的設(shè)計(jì)啟發(fā))炒嘲。
我把代碼放到了我的hot項(xiàng)目中蹬铺,Hot是關(guān)于微信頭條分享的app
github地址
1.使用concat()和first()的操作符尝哆。
2.使用BehaviorSubject。
先說BehaviorSubject的實(shí)現(xiàn)方法丛塌,廢話不多說直接上代碼较解,
/**
* 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 = "從服務(wù)器獲取的數(shù)據(jù)";
BehaviorSubject<String> 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<String>() {
@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<String> o = Observable.just(networkData)
.doOnNext(new Action1<String>() {
@Override
public void call(String s) {
BehaviorSubjectFragment.this.diskData = s;
Log.d("寫入磁盤", "寫入磁盤");
}
});
o.subscribe(new Action1<String>() {
@Override
public void call(String s) {
cache.onNext(s);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
}
});
}
private Subscription subscriptionData(@NonNull Observer<String> observer) {
if (cache == null) {
cache = BehaviorSubject.create();
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
String data = diskData;
if (data == null) {
Log.d("來自網(wǎng)絡(luò)", "來自網(wǎng)絡(luò)");
loadNewWork();
} else {
Log.d("來自磁盤", "來自磁盤");
subscriber.onNext(data);
}
}
})
.subscribeOn(Schedulers.io())
.subscribe(cache);
} else {
Log.d("來自內(nèi)存", "來自內(nèi)存");
}
return cache.observeOn(AndroidSchedulers.mainThread()).subscribe(observer);
}
}
其中最主要的是subscriptionData()這個方法畜疾,就是先判斷 cache是否存在要是存在的話就返回內(nèi)存中數(shù)據(jù),再去判斷磁盤數(shù)據(jù)是否存在印衔,如果存在就返回啡捶,要是前面兩種都不存在的時候,再去網(wǎng)絡(luò)中獲取數(shù)據(jù)奸焙。還有最重要的是當(dāng)你從網(wǎng)絡(luò)獲取數(shù)據(jù)的時候要記得保存在內(nèi)存中和保存在磁盤中瞎暑,在磁盤獲取數(shù)據(jù)的時候把它賦值給內(nèi)存。
接下來就說說用concat()和first()的操作符來實(shí)現(xiàn)与帆,這是我在看Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)了赌,作者在第一章的時候就介紹ImageLoader的設(shè)計(jì)。
在內(nèi)存中存儲的方式LruCache來實(shí)現(xiàn)的玄糟,磁盤存儲的方式就是序列化存儲勿她。
1.定義一個接口:
/**
* Created by wukewei on 16/6/19.
*/
public interface ICache {
<T> Observable<T> get(String key, Class<T> cls);
<T> void put(String key, T t);
}
2.內(nèi)存存儲的實(shí)現(xiàn)
/**
* Created by wukewei on 16/6/19.
*/
public class MemoryCache implements ICache{
private LruCache<String, String> mCache;
public MemoryCache() {
final int maxMemory = (int) Runtime.getRuntime().maxMemory();
final int cacheSize = maxMemory / 8;
mCache = new LruCache<String, String>(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 <T> Observable<T> get(final String key, final Class<T> cls) {
return Observable.create(new Observable.OnSubscribe<T>() {
@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 <T> 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.磁盤存儲的實(shí)現(xiàn)
/**
* 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 <T> Observable<T> get(final String key, final Class<T> cls) {
return Observable.create(new Observable.OnSubscribe<T>() {
@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 <T> void put(final String key, final T t) {
Observable.create(new Observable.OnSubscribe<T>() {
@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();
}
/**
* 保存數(shù)據(jù)
*/
private <T> 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;
}
/**
* 獲取保存的數(shù)據(jù)
*/
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) {
}
}
}
/**
* 判斷緩存是否已經(jīng)失效
*/
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()方式中就是判斷當(dāng)前的數(shù)據(jù)是否失效,我是根據(jù)當(dāng)前的網(wǎng)絡(luò)狀況來分wifi狀況和非wifi狀況阵翎,wifi狀態(tài)下數(shù)據(jù)過期時間比較短逢并,其他狀態(tài)過期時間比較長。
4.CacheLoader的設(shè)計(jì)
**
* 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 <T> Observable<T> asDataObservable(String key, Class<T> cls, NetworkCache<T> networkCache) {
Observable observable = Observable.concat(
memory(key, cls),
disk(key, cls),
network(key, cls, networkCache))
.first(new Func1<T, Boolean>() {
@Override
public Boolean call(T t) {
return t != null;
}
});
return observable;
}
private <T> Observable<T> memory(String key, Class<T> cls) {
return mMemoryCache.get(key, cls).doOnNext(new Action1<T>() {
@Override
public void call(T t) {
if (null != t) {
Log.d("我是來自內(nèi)存","我是來自內(nèi)存");
}
}
});
}
private <T> Observable<T> disk(final String key, Class<T> cls) {
return mDiskCache.get(key, cls)
.doOnNext(new Action1<T>() {
@Override
public void call(T t) {
if (null != t) {
Log.d("我是來自磁盤","我是來自磁盤");
mMemoryCache.put(key, t);
}
}
});
}
private <T> Observable<T> network(final String key, Class<T> cls
, NetworkCache<T> networkCache) {
return networkCache.get(key, cls)
.doOnNext(new Action1<T>() {
@Override
public void call(T t) {
if (null != t) {
Log.d("我是來自網(wǎng)絡(luò)","我是來自網(wǎng)絡(luò)");
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.網(wǎng)絡(luò)獲取的NetworkCache:
/**
* Created by wukewei on 16/6/19.
*/
public abstract class NetworkCache<T> {
public abstract Observable<T> get(String key, final Class<T> cls);
}
6.接下來看怎么使用
/**
* Created by wukewei on 16/5/30.
*/
public class ItemPresenter extends BasePresenter<ItemContract.View> 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<ListPopular> networkCache;
public ItemPresenter(Activity activity, ItemContract.View view) {
super(activity, view);
}
@Override
public void getListData(String type) {
if (isRefresh()) mView.showLoading();
networkCache = new NetworkCache<ListPopular>() {
@Override
public Observable<ListPopular> get(String key, Class<ListPopular> 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郭卫,我是根據(jù)key來獲取數(shù)據(jù)的砍聊,還要就是給個類型。
但是這個我設(shè)計(jì)的這個緩存還是不是很理想贰军,接來下想要實(shí)現(xiàn)的就是在傳入的時候類的class都不用給明玻蝌,要是有好的實(shí)現(xiàn)的方式,歡迎告訴我词疼。再次給上我的github地址俯树。謝謝