目前RxJava和Retrofit结合使用已经是非常普遍了,网上关于这方面的文章也是层出不穷,其实大致的思想都是差不多的,今天我也来写一篇关于RxJava与Retrofit的文章,聊一聊关于RxJava与Retrofit的封装,尽可能的能让其适用于大部分项目,以供大家在学习这方面的时候多一份参考。
关于RxJava的基础使用可以参考我的另一篇文章:是时候学习RxJava了,至于Retrofit的基本使用这里我就不做介绍了,这里可以给大家提供一个学习Retrofit比较全面的网址retrofit-getting-started-and-android-client,对Retrofit还不太熟悉的同学可以先去看看上面的系列文章。
闲话不多说了,直接上今天的主题。我们先来看看关于RxJava和Retrofit最基本的使用是怎么样的
首先我们需要去定义一个对应接口的Service和返回结果的实体类
public class GankResultBean {
private boolean error;
private List<ResultsBean> results;
...省略部分代码...
}
public interface RxGankService {
@GET("data/all/20/{page}")
Observable<GankResultBean> getAndroidData(@Path("page") int page);
}
接着再去初始化Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://gank.io/api/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
最后我们就可以去使用了
RxGankService rxGankService = retrofit.create(RxGankService.class);
Observable<GankResultBean> observable = rxGankService.getAndroidData(1);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GankResultBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(GankResultBean gankResultBean) {
}
});
逻辑还是挺清晰的,但是呢,如果每次使用都让你去写这么多的代码肯定会觉得很乏味,并且这里我还没有对返回结果做错误处理,于是我们就应该考虑一下对代码封装一下了。
但是应该从哪里入手呢,这里简单分析下:
- 看上面的代码我们会发现Retrofit初始化的那段代码,一般就一个baseUrl会有不同,其他的基本是一致的,如果我们每次去创建一个Service都要去写那么多重复的代码也大大增加了冗余度
- 对于返回的结果一般情况下数据格式是这样的:
{
code:1,
msg:"your message",
data:[]
}
code是服务器端和客户端约定好的一种规则,比如1表示数据请求成功,-1表示请求失败,-2表示权限不足等等,msg代表提示消息,其中data可能是数组对象也可能是普通的对象,我们可以考虑对返回的结果做一个统一的处理。
经过上面的分析我们大致有了一个方向,对于Service的创建应该有一个类去单独处理。所以这里我创建了一个ServiceFactory的类。
/**
* Created by _SOLID
* Date:2016/7/27
* Time:15:23
*/
public class ServiceFactory {
private final Gson mGsonDateFormat;
private ServiceFactory() {
mGsonDateFormat = new GsonBuilder()
.setDateFormat("yyyy-MM-dd hh:mm:ss")
.create();
}
private static class SingletonHolder {
private static final ServiceFactory INSTANCE = new ServiceFactory();
}
public static ServiceFactory getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* create a service
*
* @param serviceClass
* @param <S>
* @return
*/
public <S> S createService(Class<S> serviceClass) {
String baseUrl = "";
try {
Field field1 = serviceClass.getField("BASE_URL");
baseUrl = (String) field1.get(serviceClass);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.getMessage();
e.printStackTrace();
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create(mGsonDateFormat))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit.create(serviceClass);
}
private final static long DEFAULT_TIMEOUT = 10;
private OkHttpClient getOkHttpClient() {
//定制OkHttp
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
//设置超时时间
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
httpClientBuilder.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
httpClientBuilder.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
//设置缓存
File httpCacheDirectory = new File(FileUtils.getCacheDir(SolidApplication.getInstance()), "OkHttpCache");
httpClientBuilder.cache(new Cache(httpCacheDirectory, 10 * 1024 * 1024));
return httpClientBuilder.build();
}
}
其他的代码我就不做说明了,这里我只对createService方法做一个简单的说明:对于baseUrl是用反射去获取我们自定义Service中的BASE_URL字段,所以在使用的时候就有了一个约定,当我们新建一个Service的时候一定要有BASE_URL字段并赋值。也就是说我们在新建的Service大致是这样的
public interface GankService {
String BASE_URL = "http://www.gank.io/api/";
@GET("data/all/20/{page}") Observable<GankResultBean> getAndroidData(@Path("page") int page);
}
现在我们应该怎样去使用呢
GankService gankService = ServiceFactory.getInstance().createService(GankService.class);
是不是一下感觉创建一个Service的代码一下简洁了很多。这里还没完,我们只是解决了Service的创建,还没有对结果去做处理。
我以 http://gank.io/api/data/Android/10/1 这个接口为例:
他返回的结果的格式是这样的:
{
"error": false,
"results": []
}
所以这里我定义了这样的一个泛型类(T 是返回结果results的类型)
public class HttpResult<T> {
public boolean error;
public T results;
}
在处理结果的时候,其实用户只关心的是T,对其他数据可以统一处理下就比如这里的error字段,有了这个我们就可以再封装一下Subscriber了。
public abstract class HttpResultSubscriber<T> extends Subscriber<HttpResult<T>> {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Logger.e(this,e.getMessage());
e.printStackTrace();
//在这里做全局的错误处理
if (e instanceof HttpException) {
// ToastUtils.getInstance().showToast(e.getMessage());
}
_onError(e);
}
@Override
public void onNext(HttpResult<T> t) {
if (!t.error)
onSuccess(t.results);
else
_onError(new Throwable("error=" + t.error));
}
public abstract void onSuccess(T t);
public abstract void _onError(Throwable e);
}
我们来看看现在怎么使用吧:
ServiceFactory.getInstance()
.createService(GankService.class)
.getAndroidData(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new HttpResultSubscriber<List<GanHuoDataBean>>() {
@Override
public void onSuccess(List<GanHuoDataBean> list) {
}
@Override
public void _onError(Throwable e) {
}
});
把这段代码与文章开始那段代码比较一下,是不是神清气爽了很多,并且这里还做了错误处理的。
细心的同学肯定注意到了这段代码,这段代码每次都是在重复的使用
observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
这里我们可以创建一个TransformUtils类去处理一下
public class TransformUtils {
public static <T> Observable.Transformer<T, T> defaultSchedulers() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> tObservable) {
return tObservable.observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io());
}
};
}
public static <T> Observable.Transformer<T, T> all_io() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> tObservable) {
return tObservable.observeOn(Schedulers.io()).subscribeOn(Schedulers.io());
}
};
}
}
然后在使用的时候使用compose操作符就可以了
.compose(TransformUtils.<HttpResult<List<GanHuoDataBean>>>defaultSchedulers())
我们都知道对于网络请求肯定会有不成功的情况,有没有一种方案能够处理一下?其实RxJava已经为我们提供了这样的一个操作符<code>RetryWhen</code>可以用来实现重试机制,我这里有一个实现好的一个机制,默认情况下,最多重试3次,第一次会等3s,第二次会等6s,第三次会等9s。
public class RetryWhenNetworkException implements Func1<Observable<? extends Throwable>, Observable<?>> {
private int count = 3;//retry count
private long delay = 3000;//delay time
public RetryWhenNetworkException() {
}
public RetryWhenNetworkException(int count) {
this.count = count;
}
public RetryWhenNetworkException(int count, long delay) {
this.count = count;
this.delay = delay;
}
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable
.zipWith(Observable.range(1, count + 1), new Func2<Throwable, Integer, Wrapper>() {
@Override
public Wrapper call(Throwable throwable, Integer integer) {
return new Wrapper(throwable, integer);
}
}).flatMap(new Func1<Wrapper, Observable<?>>() {
@Override
public Observable<?> call(Wrapper wrapper) {
if ((wrapper.throwable instanceof ConnectException
|| wrapper.throwable instanceof SocketTimeoutException
|| wrapper.throwable instanceof TimeoutException)
&& wrapper.index < count + 1) {
return Observable.timer(delay + (wrapper.index - 1) * delay, TimeUnit.MILLISECONDS);
}
return Observable.error(wrapper.throwable);
}
});
}
private class Wrapper {
private int index;
private Throwable throwable;
public Wrapper(Throwable throwable, int index) {
this.index = index;
this.throwable = throwable;
}
}
}
到这里对于RxJava与Retrofit的封装的基本封装就差不多了,也能适用于大部分的项目中去了,一般情况下改改HttpResult和HttpResultSubscriber这两个类就可以了。
但是实际开发中有可能会遇到这样的一种情况:直接去访问一个完整的Url,还有用Retrofit去做下载该怎么做呢?
其实解决方案是有的,这里我们可以去定义一个CommonService
public interface CommonService {
String BASE_URL = "http://www.example.com/";//这个不重要,可以随便写,但是必须有
@GET
Observable<ResponseBody> loadString(@Url String url);
@GET
@Streaming
Observable<ResponseBody> download(@Url String url);
}
其实就是把参数的注解换成@Url就可以了,关于实现的细节可以参考文末给出的源码。
本文源码地址:源码
参考资料可以去我管理的专题查看:
RxJava系列专题(Android方向)
网友评论
compile 'com.squareup.retrofit2:converter-gson
或者retrofit的版本换了。就不行了。楼主有没有试过rxjava2
public String code;
public String message;
public T content;
其他什么都没改然后我请求接口成功,但是断网之后读不到缓存了
HTTP 504 Unsatisfiable Request (only-if-cached)
ObservableProvider.getDefault().<List<ResultsBean>>loadResult("http://gank.io/api/data/Android/10/1)
.subscribe(new HttpResultSubscriber<List<ResultsBean>>() {
@Override
public void onSuccess(List<ResultsBean> resultsBeen) {
for (int i = 0; i < resultsBeen.size(); i++) {
System.out.println(resultsBeen.get(i).getDesc());
}
}
@Override
public void _onError(Throwable e) {
//System.out.println(e);
}
});
泛型擦除原因,导致Gson解析不到指定的实体类,所以回调回来的数据并不是泛型类型。
"data": {
"name": "Unauthorized",
"message": "access-token是一个无效的凭证.",
"code": 0,
"status": 401
}
请问 如果我要得到服务器返回的错误message 但这个message 是来自服务器返给我的数据
我该怎么得到他 (类似于 点赞 这类服务器返回的message )
observeOn 跟 subscribeOn 难道不是 observeOn之后再调用subscribeOn 并不会有实质性的效果的么?
public class ResultsEntity {
private String token;
private String name;
private String schoolName;
private String imagePath;
private DefaultRoleEntity defaultRole;
private List<RolesEntity> roles;
results是这样的,改怎么去封装
public int code;
public String msg;
public T data;
},主要需要对code的值进行处理,然后判断服务器返回的状态,是调用成功,还是提交数据无效,我主要就是对这个code处理进行封装