在app开发过程中,我们经常需要从网络拉取数据然后在界面上显示,如果每次显示都从网络上请求数据,不仅会浪费流量,而且还会影响性能,一般我们会在本地缓存一份,当更新ui时有限从本地拿数据,手动更新时再从网络请求数据。这里分享一个使用Rxjava2实现的请求数据的工具类
public abstract class QueryDataHelper<T> {
/**
* 请求数据
* @param type 发起的请求的类型 Type.NORMAL表示正常类型,优先从本地拉取数据
* Type.REFRESH表示刷新类型,优先从网络拉取数据
* @return 返回数据的可订阅主题,默认的观察者在主线程,发布者在io线程
*/
@SuppressLint("CheckResult")
public Maybe<T> queryData(Type type){
switch (type){
case NORMAL:
return Observable.concat (queryFromLocal (),queryFromNet ())
.firstElement ()
.subscribeOn (Schedulers.io ())
.observeOn (AndroidSchedulers.mainThread ());
case REFRESH:
return Observable.concat (queryFromNet (), queryFromLocal ())
.firstElement ()
.subscribeOn (Schedulers.io ())
.observeOn (AndroidSchedulers.mainThread ());
default:
return Observable.concat (queryFromNet (), queryFromLocal ())
.firstElement ()
.subscribeOn (Schedulers.io ())
.observeOn (AndroidSchedulers.mainThread ());
}
}
/**
* 从网络获取数据
* @return 获取到的数据的主题,可订阅
*/
public abstract Observable<T> queryFromNet();
/**
* 从本地获取数据
* @return 获取到的数据的主题,可订阅
*/
public abstract Observable<T> queryFromLocal();
/**
* 访问类型
*/
public enum Type{
/**
* 正常的访问类型,优先从本地拿数据,比如读取地理位置的列表
*/
NORMAL,
/**
* 刷新式的访问类型,优先请求网络拿数据,比如刷新界面
*/
REFRESH
}
}
这里使用rxjava
的concat
操作符和firstElement
实现访问顺序的控制。concat
的作用是合并多个Observable
对象并按顺序发射(这里区别merga
,merga
也是合并多个Observable
对象,但是它不能保证发射的顺序),firstElement
的作用对于多个Observable
对象,按照它们的顺序,只发射第一个发射出的内容(与first
操作符不同的是,first
操作符需要一个默认的形参作为当所有Observable
对象都不发射时的返回值)。这样便实现了操作顺序的控制。
然后提供两个访问类型,NORMAL
类型和REFRESH
类型。NORMAL
用作处理普通的访问方式,即:先调用本地数据,当本地没有数据的时候再请求网络,而REFRESH
类型则是表示刷新类的访问方式,即:先从网络拿数据,如果失败了再从本地拿数据,不至于让ui空着没数据。
使用步骤
1.添加依赖
可以在gradle
中添加如下依赖,这个是jakewharton
提供的rxbinding
,其中包含有rxJava2
和RxAndroid2
。
implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha2'
或者分别导入rxJava2
和RxAndroid2
。
implementation "io.reactivex.rxjava2:rxjava:2.2.7"
implementation 'io.reactivex.rxjava2:rxjava:2.1.1'
最新版本可以访问项目主页RxAndroid进行查看。
2.重写抽象类
在需要查询数据的地方重写该抽象类并实现从网络访问
和从本地访问
的逻辑。比如我的现在有一个这样的业务逻辑:访问网络拿到天气预报里面的生活建议的数据,然后显示在ui里面显示。那么我们就可以写一个QuerySuggestionHelper
类让它继承QueryDataHelper并将泛型指定为Suggestion
(这里我用了Retrofit2
来做网络请求,Suggestion
是返回的json的javabean
)。再实现两个抽象方法,分别处理不同的逻辑即可。
class QuerySuggestionHelper extends QueryDataHelper<Suggestion>{
@Override
// 从网络拿数据并存到本地,这里是存储在sharePreference里面
public Observable<Suggestion> queryFromNet() {
return RetrofitManager.getInstance ()
.create ()
.querySuggestion (WEATHER_USER_KEY, mLocation)
.filter (new Predicate<com.xiaogege.jerry.model.gson.Suggestion> () {
@Override
public boolean test(com.xiaogege.jerry.model.gson.Suggestion suggestion) throws Exception {
return suggestion != null
&& suggestion.getHeWeather6 () != null
&& suggestion.getHeWeather6 ().size () > 0
&& WEATHER_STATUE_SUCCESS.equals (suggestion.getHeWeather6 ().get (0).getStatus ())
&& suggestion.getHeWeather6 ().get (0).getLifestyle () != null
&& suggestion.getHeWeather6 ().get (0).getLifestyle ().size () > 0;
}
})
.flatMap (new Function<com.xiaogege.jerry.model.gson.Suggestion, ObservableSource<Suggestion>> () {
@Override
public ObservableSource<Suggestion> apply(com.xiaogege.jerry.model.gson.Suggestion suggestion) throws Exception {
//拿到数据
String comfort = suggestion.getHeWeather6 ().get (0).getLifestyle ().get (0).getTxt ();
String sport = suggestion.getHeWeather6 ().get (0).getLifestyle ().get (3).getTxt ();
String carWash = suggestion.getHeWeather6 ().get (0).getLifestyle ().get (6).getTxt ();
String travel = suggestion.getHeWeather6 ().get (0).getLifestyle ().get (4).getTxt ();
Suggestion xmlSuggestion = new Suggestion (comfort, carWash, sport, travel);
//写入sharePreference
String suggestionJson = new Gson ().toJson (xmlSuggestion);
XmlIOUtils.xmlPut (SUGGESTION_JSON_KEY, suggestionJson, mContext);
return Observable.just (xmlSuggestion);
}
})
;
}
@Override
// 从本地拿数据,这里是从sharePreference中拿数据
public Observable<Suggestion> queryFromLocal() {
final String suggestionJson = XmlIOUtils.xmlGet (SUGGESTION_JSON_KEY, mContext);
return Observable.create (new ObservableOnSubscribe<Suggestion> () {
@Override
public void subscribe(ObservableEmitter<Suggestion> emitter) throws Exception {
if(suggestionJson != null){
Suggestion suggestion = new Gson ().fromJson (suggestionJson, Suggestion.class);
emitter.onNext (suggestion);
}
emitter.onComplete ();
}
});
}
}
3.调用queryData()
方法
在需要请求数据的地方调用queryData()
方法,并传入一个类型参数,指定当前请求数据的方式,是优先本地还是优先网络。
@Override
//普通的显示方式,优先显示缓存
public void queryAll() {
querySuggestion (QueryDataHelper.Type.NORMAL);
}
@Override
//刷新式的请求方式,优先显示网络数据
public void refresh() {
querySuggestion (QueryDataHelper.Type.REFRESH);
}
@SuppressLint("CheckResult")
//订阅返回的结果,并执行相应的逻辑
private void querySuggestion(QueryDataHelper.Type type){
new QuerySuggestionHelper ().queryData (type)
.subscribe (new Consumer<Suggestion> () {
@Override
public void accept(Suggestion suggestion) throws Exception {
//成功就显示出数据
mWeatherActivityView.showSuggestion (suggestion);
}
}, new Consumer<Throwable> () {
@Override
public void accept(Throwable throwable) throws Exception {
//失败显示错误信息
throwable.printStackTrace ();
mWeatherActivityView.showError (SUGGESTION_ERROR_MESSAGE);
}
});
}
这里只是简单的封装了一个小工具,代码量并没有减少,但是明显感觉结构更清晰,耦合度降低。方便维护和管理。
Rxjava2
真的是一个非常好用的工具,用好了可以大大提升我们的开发效率。搭配Retrofit2
更有意想不到的效果。这是Rxjava操作符的官网。我们需要的都在这里了。
后记:我是学习郭霖老师的第一行代码入坑的,学完rxjava
后我用rxjava2+retrofit2
重新写了一遍最后的实战天气项目,大家如果有空的话可以看看,提点建议呀,点点星星呀!!!
https://github.com/XiaogegeChen/Android-weather