-----------------------------------------------------------------------------CSDN上第一篇文章
每次kao代码遇到自己解决不了的问题的时候,就往CSDN,github,baidu各种论坛、搜索引擎一顿查,解决了很多问题,也学到了很多东西。但是从没有自己整理发表过一篇能让别人用得上的东西,感到特别惭愧(惭愧:你惭愧?确定不是水平不够?),额,好吧!确实是水平不够。不过,为了缓解一下日日夜夜煎熬在内心的惭愧感,就整合一下前人的东西,写一点Rxjava2在项目中怎么去结合Retrofit使用并进行封装的实际东西,给那些使用过Retrofit,对Rxjava有写了解的人吧(此处要划重点,对这两个都完全不了解的话,那,我也不能拿你怎么样,只能在文末甩你几个链接让你了解一下。本文主要是讲Rxjava2+Retrofit在项目中的实际使用)!
首先.........哎呀!首先要干啥呀!博文有没有什么“三部曲”,“五部曲”啥的没。不知道咋开头呀。
咳咳,那按项目步骤来吧,尴尬.jpg.
compile 'com.squareup.retrofit2:retrofit:2.3.0' //retrofit依赖导入
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' //retrofit支持Rxjava依赖导入
compile 'com.squareup.retrofit2:converter-gson:2.3.0'//Gson解析
compile 'io.reactivex.rxjava2:rxjava:2.1.0'//Rxjava
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'//Rxjava-android
这个看起来没啥问题吧,嗯!没人说话,就没问题
是的,用Rxjava和不用Rxjava的Retrofit还是有一定变化的,那变化在哪里呢?请看代码
public interface IMGoldBird {
/**
* @param options
* @return
*/
@GET(APIManage.GET_MY_STORE_HOME)
Observable getMyStoreHome(@QueryMap Map options);
/**
* @param options
* @return
*/
@FormUrlEncoded
@POST(APIManage.NO_LOGIN_RECOMMEND_GOODS)
Observable> getGoodsList(@FieldMap Map options);
/**
* 用户信息
*
* @return
*/
@GET(APIManage.GET_USER_INFO)
Observable> getUserInfo(@Query("user_id") int user_id);
// Call getUserInfo(@Query("user_id") int user_id);
}
这个接口类是不是不一样了,不一样了呀。没错,正常情况下返回的Call<>变成Observable<>了,什么?ObserVable是什么?
唉,看来我开篇的话白说了,那我还能怎么样,还不是像爸....像兄弟一样把你关爱。反手就是两个链接======》
给 Android 开发者的 RxJava 详解 :基于Rxjava1.x,虽然是1.x,但是讲的特别详细,看不懂的话,那就是智商......嗯,看的遍数不够(好险,自己也看了好多遍,而且也没全懂)
当然还有Rxjava2.x的介绍拉,我是那么不负责任的人吗?(责任:你是。我:???)
Rxjava2.x 使用详细介绍
链接都甩出来了,那我必须向他们表示衷心的感谢了呀!嗯,感谢,感谢。本文中也使用了上面链接中的一些封装方法(方法:你确定不是你这demo中的所有封装方法?我:哼,这次我可以很确定的说不是,嗯!因为还有一些链接还没甩出来)
好了,回归正文,Observable是Rxjava中的一个被观察者。具体的我就不赘述了,前辈们已经帮我们整理的明明白白了。
额,不会有人问那个APIManage是什么东西吧?好吧好吧,给你们看:
public class APIManage {
//主机地址
public static final String BASE_URL="https://app.gzlvzhiliao.com/";
//推荐商品
public static final String NO_LOGIN_RECOMMEND_GOODS="api/v2/goods/getGoodsList/theme/new";
//我的店铺
public static final String GET_MY_STORE_HOME="api/v2/user/myStoreHome";
//获取用户信息
public static final String GET_USER_INFO="api/v2/user/getUserInfo";
}
哈哈,就是个api的常量类而已拉。
那还有什么不同了,在实际使用进行网络请求的时候会不同,这个后面再看,因为许多东西都进行了封装,所以直接贴代码怕看不懂(读者:什么意思,看不起我们。我:???,有吗?我跟你讲,我当初就没看懂。摸头.无奈.png)。大家先看看封装类吧。
如果有在实际项目中使用Retrofit的人应该知道,每次网络请求的时候都要写这么一段代码
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(APIManage.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())//将服务器返回的json数据转换为实体类对象
.build();
程序员最烦的是什么?就是这种同样的代码,虽然可以复制粘贴一把梭,但是它占格子呀!所以嘛,还说什么,封起来。
/**
* Created by JiaHang Wu on 2019/8/23.
* 封装 Retrofit2 +OkHttpCliet (头部可以进行一些其他拓展,比如公共参数(例:认证头部auth))
*/
public class RetrofitManager {
private static RetrofitManager instance;
private static Retrofit retrofit;
private static IMGoldBird imGoldBird;//服务器请求Api封装类
public static RetrofitManager getInstance(){
if (instance==null){
//锁 同一时刻只有一个线程可以调用以下代码块
synchronized (RetrofitManager.class){
if (instance==null){
instance=new RetrofitManager();
}
}
}
return instance;
}
/**
* 在调用getIMGoldBird前必须确保已调用此方法初始化
* 注:最好在Application中调用init()方法初始化此管理类 让其生命周期跟随Application
*/
public void init(){
OkHttpClient.Builder builder=new OkHttpClient.Builder();
builder.connectTimeout(15, TimeUnit.SECONDS);//设置网络连接超时时间
builder.readTimeout(15, TimeUnit.SECONDS);//设置数据读取超时时间
builder.writeTimeout(15, TimeUnit.SECONDS);//设置数据写入超时时间
OkHttpClient client=builder.build();
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(APIManage.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//使用Rxjava的Observable方式必须添加此项 使用Retrofit默认Call形式则不需要
.addConverterFactory(GsonConverterFactory.create())//将服务器返回的json数据转换为实体类对象
.build();
}
/**
* 获取服务器api请求封装类对象
* 使用此方法前必须调用 init() 否则retrofit将为空
* @return
*/
public static IMGoldBird getIMGoldBird(){
if (imGoldBird==null){
synchronized (IMGoldBird.class){
if (imGoldBird==null){
imGoldBird=retrofit.create(IMGoldBird.class);
}
}
}
return imGoldBird;
}
}
这个就不细说了,代码注释中都有。唯一需要注意的是使用的时候记得先初始化init();所以最好是在Application中进行全局初始化。还有一个需要注意的地方就是结合Rxjava时这句代码记得写.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
//初始化Retrofit管理类
RetrofitManager.getInstance().init();
}
}
简单来说,请求服务器数据时,如果请求失败,对本地异常(例:json解析异常,网络无连接),服务器异常(参数不全,token错误)进行预先统一处理。请求成功,同理。那么怎么实现的呢?
4.1统一格式Respose
首先我们为服务器返回数据建立一个统一的响应实体类。
是的,统一格式。打个比方,后台返回的数据为:
{state:0,data:{},message:""}
data里面包含我们所需要的所有数据,那么统一响应实体类就很好弄了
package com.wjh.test.testdemo;
/**
* Created by JiaHang Wu on 2019/8/22.
* 固定服务器返回数据格式
*/
public class Response {
private int state;//返回码
private T data;//数据
private String message;//后台消息提示
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
是的,用泛型。是不是很简单,很简单。那既然响应Respose这样写了,在请求的api里面是不是就要改写什么呢?没错大家回头看看这段代码
/**
* 用户信息
*
* @return
*/
@GET(APIManage.GET_USER_INFO)
Observable> getUserInfo(@Query("user_id") int user_id);
// Call getUserInfo(@Query("user_id") int user_id);
区别很明显不是吗?嗯,so easy
那接下来干什么呢?(你说呢,肯定是对数据进行预处理呀)
4.2 本地异常,服务器异常预处理封装
这个地方是对Rxjava的ObservableTransformer的使用,它可以变换Observable。然后再联合compose操作符使用,减少重复代码。(别问compose是啥,问就是一个链接:RxJava2.x详细介绍(操作符相关)
然后,出来吧,我以我的名义召唤你(粘贴你),我的代码。
public class ResponseTransformer {
public static ObservableTransformer,T> handleResult(){
return new ObservableTransformer, T>() {
@Override
public ObservableSource apply(Observable> observable) {
return observable.onErrorResumeNext(new ErrorResumeFunction())
.flatMap(new ResponseFunction());
}
};
}
/**
* 非服务器产生的异常,比如本地无网络请求,Json数据解析错误等等。
* @param
*/
private static class ErrorResumeFunction implements Function>>{
@Override
public ObservableSource extends Response> apply(@NonNull Throwable throwable) throws Exception {
return ObservableError.error(CustomException.handleException(throwable));
}
}
private static class ResponseFunction implements Function,ObservableSource>{
@Override
public ObservableSource apply(@NonNull Response tResponse) throws Exception {
int state=tResponse.getState();//服务器返回的的code
String message=tResponse.getMessage();//服务器返回的message
if (state==0){
//0表示请求正确 服务器返回了数据
return Observable.just(tResponse.getData());
}else {
//非200 表示请求有误 服务器返回message错误提示
return Observable.error(new ApiException(state,message));
}
}
}
}
代码不贴还好,一贴出来就有人问了,ApiException是啥,CustomException是啥?
别问,问就是出来吧!咦? 出来吧,我的代码。
CustomException类:自定义本地异常
/**
* Created by JiaHang Wu on 2019/8/22.
* 本地异常处理。包括解析异常等其他异常
*/
public class CustomException {
/**
* 未知错误
*/
public static final int UNKNOWN = 1000;
/**
* 解析错误
*/
public static final int PARSE_ERROR = 1001;
/**
* 网络错误
*/
public static final int NETWORK_ERROR = 1002;
/**
* 协议错误
*/
public static final int HTTP_ERROR = 1003;
public static ApiException handleException(Throwable e) {
ApiException ex;
if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
//解析错误
ex = new ApiException(PARSE_ERROR, e.getMessage());
return ex;
} else if (e instanceof HttpException){
//Http错误
ex=new ApiException(HTTP_ERROR,e.getMessage());
return ex;
} else if (e instanceof ConnectException) {
//网络错误
ex = new ApiException(NETWORK_ERROR, e.getMessage());
return ex;
} else if (e instanceof UnknownHostException || e instanceof SocketTimeoutException) {
//连接错误 1.未知主机异常 2.连接超时异常
ex = new ApiException(NETWORK_ERROR, e.getMessage());
return ex;
} else {
//未知错误
ex = new ApiException(UNKNOWN, e.getMessage());
return ex;
}
}
}
ApiException类:自定义异常处理
public class ApiException extends Exception {
private int code;
private String displayMessage;
public ApiException(int code, String displayMessage) {
this.code = code;
this.displayMessage = displayMessage;
}
public ApiException(int code, String message, String displayMessage) {
super(message);
this.code = code;
this.displayMessage = displayMessage;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getDisplayMessage() {
return displayMessage;
}
public void setDisplayMessage(String displayMessage) {
this.displayMessage = displayMessage;
}
}
诺,简单吗,很简单嘛!就是对异常信息进行再次封装,方便自己处理。
然后在ResponseTransformer类中通过Observable.error(new ApiException(state,message))把这些异常发射出去,最后我们在正式使用的时候在onError(Throwable throwable)中对异常再进行处理就可以了。当然可以统一处理的肯定是在ResponseTransformer中直接处理掉是最好的。
其实封装大概就是上面那样差不多了,但是既然已经想封装的越简单越好,那肯定对于重复要写的代码去重是最好的呀。
这里主要是针对Retrofit结合Rxjava使用的时候,每次都要写的重复的那几句线程切换的代码。
我们来看代码 这是没有封装的时候
RetrofitManager.getIMGoldBird().getGoodsList(map)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer>() {
@Override
public void accept(@NonNull Response goodsBeanResponse) throws Exception {
}
});
对于.subscribeOn(Schedulers.io())和.observeOn(AndroidSchedulers.mainThread())这两句代码每次都要写,是的,每次都要,那我们能看得过眼吗?那不得给他装起来完事,怎么去封装了,还是利用ObservableTransformer结合compose一起呀!
public class SchedulerProvider {
@Nullable
private static SchedulerProvider INSTANCE;
private SchedulerProvider() {
}
public static synchronized SchedulerProvider getInstance() {
if (INSTANCE == null) {
INSTANCE = new SchedulerProvider();
}
return INSTANCE;
}
/**
* 重复代码统一处理
* @param
* @return
*/
@NonNull
public ObservableTransformer applySchedulers() {
return new ObservableTransformer() {
@Override
public ObservableSource apply(@io.reactivex.annotations.NonNull Observable observable) {
return observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
这样子,我们在使用网络请求的时候,可以直接.compose(SchedulerProvider.getInstace().applySchedulers())就可以了。当然,项目中还有别的需求的时候,我们还可以在这里封装更多的重复代码,比如别的常常需要用的ObservableTransformer。
前面封装了那么多东西,那实际进行网络请求的时候怎么去用呢?话不多说,上代码
/**
* 加载数据 Retrofit+Rxjava
*/
private void loadData() {
// 加载推荐商品数据
Map map=new HashMap<>();
map.put("current_page",1+"");
map.put("list_rows","10");
RetrofitManager.getIMGoldBird().getGoodsList(map)
.compose(this.>bindToLifecycle())//管理Retrofit的生命周期 当页面被销毁时取消网络请求 防止内存泄露
.compose(ResponseTransformer.handleResult())
.compose(SchedulerProvider.getInstance().applySchedulers())
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
//此方法是在事件发起之前做的准备工作 例:网络请求发起的ProgressBar转圈
}
})
.subscribeOn(AndroidSchedulers.mainThread())//为doOnSubscribe()指定主线程 保证其一直运行在主线程
.observeOn(Schedulers.io())
.doOnNext(new Consumer
这样用起来是不是就舒服很多了,我们只需要获取到的正确的服务器数据进行处理就行了,其他都封装更好了。
这个时候代码中又出现了一些不该出现的东西.compose(ResponseTransformer.
这个哪来的?我前面也没封装呀!嘿嘿,这个不是我封装的,至于这个东西是用来干什么的了,嗯!用来管理Retrofit的生命周期的,因为如果一个Activity被销毁了的时候,网络请求是不是也要同时终止,否则的话可能会导致内存泄露。Rxjava2中是有提供一个disposable.dispose()来取消的。这个的话就需要我们手动判断在activty销毁的时候去调用了。demo中我们用的是封装的框架RxLifecycle, 使用起来的话特别简单,结合compose一句代码就搞定了,自动管理Retrofit的生命周期,妈妈再也不用担心我泄露了。当然,这里只是针对Retrofit+Rxjava2使用可能造成的内存泄露啊,Nothing is all-powerful。
附上github地址:RxLifecycle
基于用户体验,在实际项目中我们可能有这样的需求,在向服务器请求数据之前,我们会先读取本地缓存数据,如果缓存数据没过期(判断条件根据项目需求来,本文的判断条件是简单的是否存在本地缓存),就从缓存中读取数据,防止过度的网络数据请求。
这中需求我们平常项目中也是经常遇到的,一般的我们直接读取缓存,用if else进行判断就可以了。但是,既然我们都用Rxjava了,肯定是为了代码的简洁舒适、逻辑易懂呀!这个时候就不得不提一下Rxjava的concat操作符了。
concat:concat 可以做到不交错的发射两个甚至多个 Observable 的发射事件,并且只有前一个 Observable 终止(onComplete) 后才会订阅下一个 Observable。
是不是很符合我们的期望,只有在缓存数据不合格的时候才去网络获取数据,数据合格的话直接对缓存数据进行处理,不再订阅网络请求事件了。
让我们来看看实现
private void cacheNetworkLoad(){
Observable cacheObservable=Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(@NonNull ObservableEmitter e) throws Exception {
Object goodsBean= cacheDoubleUtils.getSerializable("sp");
if (null!=goodsBean){
//本地缓存数据合格
Log.e("123456", "subscribe: 本地缓存数据合格,不再从网络获取" );
e.onNext(goodsBean);
}else{
//本地缓存数据不合格
Log.e("123456", "subscribe: 本地缓存数据不合格,准备从网络获取" );
e.onComplete();
}
}
});
SchedulerProvider schedulerProvider=SchedulerProvider.getInstance();
Map map=new HashMap<>();
map.put("current_page",1+"");
map.put("list_rows","10");
Observable network=RetrofitManager.getIMGoldBird().getGoodsList(map)
.compose(this.>bindToLifecycle())//管理生命周期 当页面被销毁时取消网络请求 防止内存泄露
.compose(ResponseTransformer.handleResult())
.compose(schedulerProvider.applySchedulers())
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
//此方法是在事件发起之前做的准备工作 例:网络请求发起的ProgressBar转圈
dialogUtils.showProgress(weakReference.get(),"加载中...");
Log.e("123456", "accept: 事件发起之前做的准备工作 例:网络请求发起的ProgressBar转圈" );
}
})
.subscribeOn(AndroidSchedulers.mainThread())//指定主线程
.observeOn(Schedulers.io())
.doOnNext(new Consumer() {
@Override
public void accept(@NonNull Object o) throws Exception {
//doOnNext:数据输出准备 一般用于网络获取数据后缓存到数据库 因为数据库操作是耗时操作所以需要在子线程进行
GoodsBean gb= (GoodsBean) o;
cacheDoubleUtils.put("sp",gb);//缓存
Log.e("123456", "accept: 网络获取到了数据,并进行了缓存" );
}
})
.observeOn(AndroidSchedulers.mainThread());
Observable.concat(cacheObservable,network)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(@NonNull Object o) throws Exception {
Log.e("123456", "accept: 对数据进行处理,更新ui" );
dialogUtils.dismissProgress();//取消loading
GoodsBean goodsBean= (GoodsBean) o;
if (goodsBean!=null){
if (data.size()>0){
data.clear();
}
data.addAll(goodsBean.getRows());
adapter.notifyDataSetChanged();
}
}
}, new Consumer() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
dialogUtils.dismissProgress();//取消loading
ApiException apiException= (ApiException) throwable;
Log.e("123456", "accept: 数据加载失败 异常:"+apiException.getCode()+apiException.getDisplayMessage() );
ToastUtils.showLong("accept: "+"加载失败了"+apiException.getCode()+apiException.getDisplayMessage());
}
});
}
这样子一条链式下来的代码是不是看起来就很舒服,当然,再配上MVP把逻辑层和业务层进一步分离的话,会变的更舒服的。本文就不再涉及了,已经封装了很多东西了,再实现MVP就是很简单的事情了。
上述代码中还加入了加载loading的显示和消失,不过是自己手动在发起网络请求前弹出,数据或去完后消失。其实也可ObservableTransformer+compose联合使用来达到效果。只是本文中,因为使用了concat,会出现不一定发起网络请求的情况,如果将加载loading用compose方式加入的话,会出现loading一直转不消失的问题,所以采用了手动管理。有兴趣的小伙伴可以探究一下能否在用了concat的情况下用compose方式去实现。
想看完整demo的可以去这里下载: https://download.csdn.net/download/wujiahang129/11644073
文末再次感谢本文所用到了的前人的封装思想以及用到了的封装方法,特别感谢。最后附上链接。
Rxjava+Retrofit封装使用链接 对本问的实现帮助特别大,也用到了此文中的大部分封装类,感觉确实封装了特别好了, 所以很多都是在明白了思路后搬过来用的。这篇是结合MVP的例子,有兴趣的可以去看看。
RxJava2.x详细介绍(操作符相关)操作符相关,也举了很多例子。
Retrofit+Rxjava详细封装(加载动画 统一异常处理,生命周期管理,防止内存泄露)
给 Android 开发者的 RxJava 详解(基于Rxjava1.x)
Rxjava2.x 使用详细介绍 有讲到这样的需求相信大家能用到:第一个网络请求获取第二个网络请求所需要的参数;