前言
开发过程中,一般都会对网络框架进行再次封装,以配置各种参数,并适合自己的编码风格。各种网络框架比较下来,还是Retrofit2+Rxjava2看着最爽,今天把这个东西整理了一下,发出来,示例给出了一般写法和MVP的写法。
Retrofit2+Rxjava2
Retrofit2
和Rxjava2
基础的东西就不说了,直接进入主题。我们的需求是:
1、尽可能简洁
2、可控制不同请求的加载框,
3、错误统一处理
4、页面销毁时取消订阅
5、可根据不同请求处理不同的异常
先看一下最终的效果:
Api.getDefaultService()
.calendarBean("2017-06-29")
.map(new RxFunction<Calendar>())
.compose(RxSchedulers.<Calendar>io_main())
.subscribe(new RxObserver<Calendar>(this, TAG, 0, false) {
@Override
public void onSuccess(int whichRequest, Calendar calendar) {
tv_.setText(calendar.getLunar());
}
@Override
public void onError(int whichRequest, Throwable e) {
}
});
这个例子其中就包含了以上5点要求,其中大多是对RxObserver
的处理。RxObserver
实现Observer
接口,在Rxjava2
中作为观察者,在执行onNext
之前先做一些处理,就能相应地减少在View
层的处理。
public RxObserver(Context context, String key, int whichRequest, boolean isShowDialog) {
this.mContext = context;
this.mKey = key;
this.isShowDialog = isShowDialog;
this.mWhichRequest = whichRequest;
mDialog = new ProgressDialog(context);
mDialog.setTitle("请稍后");
mRxManager = RxManager.getInstance();
}
参数说明:
key
:key
是用来区分不同类中联网的CompositeDisposable
的,以便在这个类销毁时,可以取消该类中订阅关系,建议采用包名+类名作为key
,这个后面会详细说。
whichRequest
:区分不同的请求,用于多个联网请求结束后对不同请求的处理。
isShowDialog
:是否显示加载框,RxObserver
内部实例化了一个加载框,可根据需求设置是否显示。
RxObserver
实现了Observer
的四个方法 void onSubscribe(@NonNull Disposable d);
,void onNext(@NonNull T t);
,void onError(@NonNull Throwable e);
,void onComplete();
。
@Override
public final void onSubscribe(Disposable d) {
mRxManager.add(mKey, d);
if (isShowDialog) {
mDialog.show();
}
onStart(mWhichRequest);
}
onSubscribe(Disposable d)
方法,相当于Rxjava1
中的onStar()
方法,其中的参数是Disposable
,用于取消该订阅关系,所以在方法中把它添加进了RxManager
中,以方便取消订阅。同时也判断了isShowDialog
了是否显示加载框,添加了一个方法onStart()
方法,同时把mWhichRequest
传出去方便在外部回调。
@Override
public final void onNext(T value) {
onSuccess(mWhichRequest, value);
}
onNext(T value)
方法比较简单,联网结果成功返回会执行,参数是结果。在这个方法中写了抽象方法onSuccess(mWhichRequest, value);
同样把mWhichRequest
传出,方便处理。
@Override
public final void onComplete() {
if (mDialog.isShowing()) {
mDialog.dismiss();
}
}
onComplete()
方法是联网正常返回,联网过程结束时执行,在该方法中判断这个加载框的显示与否。
@Override
public final void onError(Throwable e) {
if (mDialog.isShowing()) {
mDialog.dismiss();
}
if (e instanceof EOFException || e instanceof ConnectException || e instanceof SocketException || e instanceof BindException || e instanceof SocketTimeoutException || e instanceof UnknownHostException) {
Toast.makeText(mContext, "网络异常,请稍后重试!", Toast.LENGTH_SHORT).show();
} else if (e instanceof ApiException) {
onError(mWhichRequest, e);
} else {
Toast.makeText(mContext, "未知错误!", Toast.LENGTH_SHORT).show();
}
}
onError(Throwable e)
方法稍微复杂一些,整个过程出现异常是会执行这个方法,这里不只是联网的过程,还包括对返回数据处理上的异常,比如json
解析失败等。如果发生异常就不会再走onComplete()
,所以同样需要判断加载框的显示。下面的是对一些异常的处理,这里只处理了一些网络方面的异常,可以根据需求添加异常判断,这里处理的异常只是出现意外的异常。这里还自定义了一个异常ApiException
,抛出这个异常都是业务上的问题,比如空数据,格式不对等等,这个是根据返回的状态值判断的,方便统一处理错误信息。
public class RxFunction<T> implements Function<HttpResult<T>, T> {
@Override
public T apply(@NonNull HttpResult<T> httpResult) throws Exception {
int retCode = httpResult.getRetCode();
if (retCode != 200) {
switch (retCode) {
case 21001:
throw new ApiException("查询的日期格式错误,格式:yyyy-MM-dd");
// case 2111:
// throw ........
}
}
return httpResult.getResult();
}
}
这就是这个ApiException
产生的地方,也是这句话map(new RxFunction<T>())
用到的逻辑,这个是在Retrofit2
解析json
后得到HttpResult<T>
,转化成T
的map
操作符,在转化过程中根据HttpResult
的状态值,判断是返回T
还是抛出ApiException
异常,这里可以根据返回状态值添加不同的错误提示信息,异常会在RxObserver
的onError(Throwable e)
被捕捉,统一传到View层去处理。
compose(RxSchedulers.<Calendar>io_main())
这句话是切换了一下线程,等同于这两句
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
执行在io
线程,表现在安卓主线程。RxSchedulers
完整的代码:
public class RxSchedulers {
public static <T> ObservableTransformer<T, T> io_main() {
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply( Observable<T> upstream) {
return upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
还有一个类RxManager
,这个类是用来管理订阅的,在RxObserver
中,执行onSubscribe
方法时,把参数Disposable
添加进了RxManager
中,看一下RxManager
对应的add方法:
public void add(String key, Disposable disposable) {
Set<String> keySet = map.keySet();
if (keySet.contains(key)) {
CompositeDisposable compositeDisposable = map.get(key);
compositeDisposable.add(disposable);
} else {
CompositeDisposable compositeDisposable = new CompositeDisposable();
compositeDisposable.add(disposable);
map.put(key,compositeDisposable );
}
}
这里面,给每一个key
初始化了一个CompositeDisposable
并存入map
,把这个Disposable
加入到对应的CompositeDisposable
中。
RxManager
中还有一个方法clear(String key)
:
public void clear(String key) {
Set<String> keySet = map.keySet();
if (keySet.contains(key)) {
CompositeDisposable compositeDisposable = map.get(key);
compositeDisposable.clear();
map.remove(key);
}
}
根据key
得到对应的CompositeDisposable
,并执行compositeDisposable.clear()
来取消compositeDisposable中所有的订阅关系。这个RxManager
的clear
方法建议放在BaseActivity
的onDestroy()
方法中,这也是为什么前面说的建议这个key
采用包名+类名的方式的原因,当这个类销毁的时候,该类中所有的联网订阅关系都会被取消,避免内存泄漏。为保证这个map
唯一,RxManager
采用了单利模式:
private static RxManager rxManager;
private Map<String, CompositeDisposable> map;
private RxManager() {
map = new HashMap<>();
}
public static RxManager getInstance() {
if (rxManager == null) {
rxManager = new RxManager();
}
return rxManager;
}
BaseActivity的onDestroy:
@Override
protected void onDestroy() {
super.onDestroy();
RxManager.getInstance().clear(TAG);
}
以上就是封装的netWork
module
的基本使用。
最能体现Retrofit2+Rxjava2
优势的自然是MVP
的结构,下面就演示一下MVP
的写法。
MVP
这里同样不讨论mvp
的基础知识,也不讨论几种mvp
哪种写法更正宗,我觉得只要是代码逻辑清楚,耦合性低,都是好的代码架构。
这里为求简便,Presenter
和Model
没有写成接口,直接写的各自的实现类。先看Model
层:
public class MVPModel {
public Observable<Calendar> getCalendar(String date) {
return Api.getDefaultService().calendarBean(date).map(new RxFunction<Calendar>()).compose(RxSchedulers.<Calendar>io_main());
}
}
Model
切换线程,对请求的数据进行解析,并转化成我们需要的Observable<Calendar>
对象。再看View
层:
public interface MVPView {
void setResult(Calendar calendar);
void onError(int whichRequest ,Throwable t);
void onStartLoading(int whichRequest);
void onEndLoading(int whichRequest);
}
View
需要写成接口,让对应的Activity
或者Fragment
去实现,View
中除了setResult
和onError
两个必要的方法外,又写了onStartLoading
和onEndLoading
两个方法,这两个方法是分别在联网操作开始和结束时候的回调,已满足其他需要在操作开始和结束做的操作,比如不用RxObserver
中的加载框,需要其他加载框的,如如SwipeRefreshLayout
。再看Presenter
:
public class MVPPresenter {
private MVPModel mvpModel;
private MVPView mvpView;
public MVPPresenter(MVPView mvpView) {
this.mvpView = mvpView;
mvpModel = new MVPModel();
}
public void getCalendar(Context context,String date, String key,int whichRequest ,boolean isShowDialog) {
mvpModel.getCalendar(date).subscribe(new RxObserver<Calendar>(context,key,whichRequest,isShowDialog) {
@Override
public void onStart(int whichRequest) {
super.onStart(whichRequest);
mvpView.onStartLoading(whichRequest);
}
@Override
public void onSuccess(int whichRequest, Calendar calendar) {
mvpView.onEndLoading(whichRequest);
mvpView.setResult(calendar);
}
@Override
public void onError(int whichRequest, Throwable e) {
mvpView.onError(whichRequest, e);
mvpView.onEndLoading(whichRequest);
}
});
}
}
Presenter
同时持有Model
和View
的对象,把从Model
得到的数据传到View
去处理。最后看View
的实现类MVPActivity
:
public class MVPActivity extends BaseActivity implements MVPView{
TextView tv_;
private MVPPresenter mvpPresenter;
@Override
protected int getLayoutId() {
return R.layout.activity_mvp;
}
@Override
protected void initData() {
tv_ = (TextView) findViewById(R.id.tv_);
mvpPresenter = new MVPPresenter(this);
}
public void request(View view) {
mvpPresenter.getCalendar(this, "2018-10-01", TAG, 0, false);
}
@Override
public void setResult(Calendar calendar) {
tv_.setText(calendar.getWeekday());
}
/**
* 如果有多个请求可根据whichRequest处理不同请求的异常
* @param whichRequest
* @param t
*/
@Override
public void onError(int whichRequest, Throwable t) {
}
/**
* 如果不使用RxObserver自带的Dialog,可以在RxObserver中设置false,
* 在onStartLoading和onEndLoading设置需要其他dialog的显示和消失,如SwipeRefreshLayout
* @param whichRequest
*/
@Override
public void onStartLoading(int whichRequest) {
}
@Override
public void onEndLoading(int whichRequest) {
}
}
整个Activity
看起来非常清爽,由mvpPresenter
发出getCalendar()
的请求,在setResult
中获得结果,onError
中处理异常,onStartLoading
和onEndLoading
处理加载框,如果一个页面需要多个请求,可以给RxObserver
传入不同的whichRequest
值,根据whichRequest
判断在对应的方法中做不同的操作。
以上就是封装的这个Retrofit2+Rxjava2
的全部内容了,例子中对Retrofit
的配置不是很多,如果需要缓存等其他配置,可自行添加。