说明
在资讯类应用中为了优化用户体验,一般会使用缓存数据。以下代码就是使用rxjava 同时获取网络和缓存数据。网络数据可以覆盖缓存数据,缓存数据无法覆盖网络数据。网络出错不会影响缓存的显示,缓存出错不会影响网络的显示。两则同时出错,则会显示网络错误。使用的时候也很简单。只需要一行代码。
支持只显示一次的请求。
RxDataUtil.getData(net, cache,false)
和网上已有的方法相比,这个方法更好理解。没用特别复杂的操作符。而且没用bug。
下面这篇文章中如果网络和缓存都出错,下游将接受不到事件,界面会一直处于loading中。
RxJava2 实战知识梳理(8) - 使用 publish + merge 优化先加载缓存,再读取网络数据的请求过程
源码
package com.trs.nmip.common.util;
import android.util.Log;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class RxDataUtil {
public static void main() {
//测试
Observable<String> net = getNetData(2100, true);
Observable<String> cache = getCacheData(2000, false);
Disposable disposable = RxDataUtil.getData(net, cache, true)
.subscribe(str -> {
Log.i("zzz", "UI 显示数据->" + str);
}, e -> {
//显示网络错误
Log.i("zzz", "UI 显示错误->" + e.getMessage());
e.printStackTrace();
});
}
/**
* 同时获取网络和缓存数据,网络数据可以覆盖缓存数据,缓存无法覆盖网络
* 某一个请求出错不会影响另一个
* 如果两个都出错将显示网络的错误
* showOnce 为ture的话将会使用网络或者缓存数据,因为是在最后使用take操作符截取了一次请求,
* 所以不影响网络请求,效果是会从网络上更新数据到okhttp的缓存中。
*
* @param net 网络请求
* @param cache 缓存请求
* @param showOnce 是否只显示一次,如果为true的话,只会向下游发送一次数据。
* @param <T> 数据的泛型
* @return
*/
public static <T> Observable<T> getData(Observable<T> net, Observable<T> cache, boolean showOnce) {
Observable<T> observable = Observable.create(new ObservableOnSubscribe<T>() {
Throwable[] throwables = new Throwable[2];
int exceptionNumber = 0;
Disposable disposableCache;
CompositeDisposable compositeDisposable = new CompositeDisposable();
Disposable disposableNet;
int completedNumber = 0;
@Override
public void subscribe(ObservableEmitter<T> emitter) throws Exception {
emitter.setDisposable(compositeDisposable);
disposableCache = cache
.subscribe(t -> {
if (!disposableCache.isDisposed()) {
emitter.onNext(t);
}
}, e -> reportError(0, e, emitter)
, () -> completed(emitter));
compositeDisposable.add(disposableCache);
disposableNet = net
.doOnNext(t -> disposableCache.dispose())//如果网络数据先发射,则取消缓存数据,避免缓存覆盖网络
.subscribe(t -> {
if (!disposableNet.isDisposed()) {
emitter.onNext(t);
}
}
, e -> reportError(1, e, emitter),
() -> completed(emitter));
compositeDisposable.add(disposableNet);
}
private synchronized void completed(ObservableEmitter<T> emitter) {
completedNumber++;
if (completedNumber == 2) {
emitter.onComplete();
}
}
private synchronized void reportError(int index, Throwable e, ObservableEmitter<T> emitter) {
throwables[index] = e;
exceptionNumber++;
if (exceptionNumber == 2) {
//表示缓存和网络请求都失败了,向下游发生网络错误
emitter.onError(throwables[1]);
}
}
});
return showOnce ? observable.take(1) : observable;
}
private static Observable<String> getCacheData(final long useTime, boolean success) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
Log.i("zzz", "开始请求缓存数据");
sleep(useTime);
if (!success) {
emitter.onError(new RuntimeException("缓存错误"));
return;
}
Log.i("zzz", "开始发送缓存数据");
emitter.onNext("缓存数据");
Log.i("zzz", "缓存数据发送完成");
}
}).subscribeOn(Schedulers.io());
}
private static void sleep(long time) {
long end = time + System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis() > end) {
break;
}
}
}
private static Observable<String> getNetData(long useTime, boolean success) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
Log.i("zzz", "开始请求网络数据");
sleep(useTime);
if (!success) {
emitter.onError(new RuntimeException("网络错误"));
return;
}
Log.i("zzz", "开始发送网络数据");
emitter.onNext("网络数据");
Log.i("zzz", "网络数据发送完成");
}
}).subscribeOn(Schedulers.io());
}
}
测试
1.网络先于缓存
网络比缓存快了0.1秒
结果:UI只显示了网络数据
缓存快于网络
这是最正常的情况,测试参数如下
结果网络可以覆盖缓存数据
网络缓存同时出错
测试参数
结果正常
网络出错不会影响缓存
测试参数
结果
只显示一次
这种需求在一些不好刷新的地方使用。比如你的项目的一级栏目是通过服务器配置的。你希望同时获取缓存和网络,只要二者有其一就马上显示出来,这样可以减少用户等待的时间。但是你又不希望数据发射两次,因为两次的发射会造成界面大的变动,用户体验不好。但是又希望没有都有网络请求。可以更新缓存数据。下次进来就是最新的数据。
测试,网络和缓存都能成功的情况下,只显示最快的数据源。同时获取网络请求。这次让缓存快一些。
结果,当缓存比较快的时候显示了缓存。网络请求也发送了。如果使用了okhttp自带的缓存,那么你的缓存也就更新了。
让网络更快
显示了网络数据,只显示了一次