自定义Retrofit的Convert统一处理返回结果为Observable<T>

转载请附原文链接:自定义Retrofit的Convert统一处理返回结果为Observable<T>

需求

最近公司开了一个新项目,在搭建项目的时候,我们希望将服务端返回列表数据直接与我们的 List 控件绑定,高度封装,方便使用,所以与服务端定义了请求返回数据格式,当一个页面含有列表 List 数据,第一次进入页面数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"code":2000,
"message":"请求成",
"data":{
"lsit1":{
"load_more_url":"xxxxx",
"refresh_url":"xxxx",
"key1":"value1",
"key2":"value2",
"data":[
{
"name":"小米"
},
{
"name":"华为"
},
{
"name":"蓝绿"
}
]
},
"lsit2":{
"load_more_url":"xxxxx",
"refresh_url":"xxxx",
"key1":"value1",
"key2":"value2",
"data":[
{
"name":"小米2"
},
{
"name":"华为2"
},
{
"name":"蓝绿2"
}
]
}
}
}

当我们对 list1 进行下拉刷新或者上拉加载更多的时候服务端返回数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"code":2000,
"message":"请求成",
"data":{
"load_more_url":"xxxxx",
"refresh_url":"xxxx",
"key1":"value1",
"key2":"value2",
"data":[
{
"name":"小米"
},
{
"name":"华为"
},
{
"name":"蓝绿"
}
]
}
}

对比这两次网络请求返回的数据,我们发现两次的数据跟节点的 data 层数据格式不一样!这就很蛋疼了,这样跟节点的 data 层就是对应两个 JavaBean 对象了,本来这个界面的数据我是只想通过一个网络请求搞定,但是由于服务端返回数据格式两次不一样,那么现在就不能通过一个网络请求接口搞定了,就得写出下面这样两个接口:

1
2
3
4
5
6
//第一次网络请求接口
@GET("xx/xxx/xxxx")
Observable<BaseBean<PageBean>> getPageFirstData(@Query(Constant.PERSONAL_KEY_TYPE) String rtk);
//第二次网络请求接口
@GET("xx/xxx/xxxx")
Observable<BaseBean<RfreshBean>> getPageRefreshData(@Query(Constant.PERSONAL_KEY_TYPE) String rtk);

这样写起来是很蛋疼的,只要有 List 列表需要刷新或者加载更多的界面都需要这么写,简直难以接受!

解决方案

天真想法

想一下如果 Retrofit 支持下面这种泛型写法那是不是就可爱了:

1
2
@GET("xx/xxx/xxxx")
Observable<BaseBean<T>> getPageData(@Query(Constant.PERSONAL_KEY_TYPE) String rtk);

即传入一个泛型,当我们调用的时候再传入具体类型,自己试了下,炸了,报错信息是,不能传入泛型 T ,这个问题在 Retrofit 项目下面也有人提过 issue ,JakeWharton 大神给出了否定回答,那么就死了这条心,想想这也是天真的想法,因为 Gson 在进行转换的时候你传入的是泛型 T 不是具体类型,而运行时 泛型 T 是擦除的,怎么能实现转换。

正确思路

现在我们想要调用接口返回的数据格式为 Observable<BaseBean<T>> 格式,但是 Retrofit 不支持,Retrofit 要求这个 T 必须是具体的 JavaBean 类型,但是我们最终想要的结果的是 T 类型,那么就想办法自己中转一层试试,思路就是让 Retrofit 返回统一格式为 Observable<BaseBean<String>> 类型,然后我们自己去把 Observable<BaseBean<String>> 转换为 Observable<BaseBean<T>> 类型,这样就达到我们在最终目的。

Converter.Factory 使用

原理

注意:要想去自定义 Converter.Factory 我们肯定是要先去弄懂他工作的原理了。

怎么让 Retrofit 返回 Observable<BaseBean<String>> 呢,当然是借助 Converter.Factory 转换器,功能很强大。下面代码我们应该很熟悉:

1
2
3
4
5
6
7
8
9
10
11
private void init() {
retrofit = new Retrofit.Builder()
.client(OKHttpFactory.getInstance().getOkHttpClient())
.baseUrl(xxxx)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//String 等类型转换
.addConverterFactory(ScalarsConverterFactory.create())
// gson 转换器
.addConverterFactory(GsonResponseBodyConverter.create())
.build();
}

这里 ScalarsConverterFactoryGsonConverterFactory这两个转换器一般我们都会在项目中添加,而且已经有裤子我们添加个依赖就可以用,ScalarsConverterFactory 是将服务端返回数据转为 String、Boolean、Byte 等类型,GsonConverterFactory 是将服务端返回数据利用 Gson 转换为我们指定对应的 JavaBean 对象,但是上面调用 addConverterFactory 一定要注意先后顺序,比如我们先调用 addConverterFactory(GsonResponseBodyConverter.create()) 再调用 addConverterFactory(ScalarsConverterFactory.create())这时你去调用如下接口:

1
2
@GET("xx/xxx/xxxx")
Observable<String> getPageData(@Query(Constant.PERSONAL_KEY_TYPE) String rtk);

那么程序就会崩溃!为什么呢?一起来看源码!

当我们调用 addConverterFactory 看看发生了什么。

1
2
3
4
5
6
private final List<Converter.Factory> converterFactories = new ArrayList<>();
/** Add converter factory for serialization and deserialization of objects. */
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}

其实就是将转换器 factory 对象加入到 retrofit 内部自己维护的一个 list 集合中。

那么是如何调用的呢?看下面代码就是处理网络请求返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
checkNotNull(type, "type == null");
checkNotNull(annotations, "annotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
//遍历存储转换器 factory 的 list 对象
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
//关键代码,这里处理返回结果 ResponseBody
converterFactories.get(i).responseBodyConverter(type, annotations, this);
//如果返 converter 不为空,那么就使用这个 concer ,否则继续遍历
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
}

上面代码就是在处理网络请求返回结果的时候去遍历存储转换器 factory 的 list 对象集合,只要返回的 converter 对象不为 null ,那么就使用这个 conver ,否则继续遍历。这个模式有点像 OkHttpInterceptor一样, 内部通过一层层的拦截器逐步完成, 使用的是责任链模式:它包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,不同的是,如果前面的 Converter 转换器不能处理那么就交给后面的转换器去处理,直到找到一个能处理的转换器来处理返回数据 ResponseBody

看一下 ScalarsConverterFactory 内部是如何处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public final class ScalarsConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
if (type == String.class) {
return StringResponseBodyConverter.INSTANCE;
}
if (type == Boolean.class || type == boolean.class) {
return BooleanResponseBodyConverter.INSTANCE;
}
if (type == Byte.class || type == byte.class) {
return ByteResponseBodyConverter.INSTANCE;
}
if (type == Character.class || type == char.class) {
return CharacterResponseBodyConverter.INSTANCE;
}
if (type == Double.class || type == double.class) {
return DoubleResponseBodyConverter.INSTANCE;
}
if (type == Float.class || type == float.class) {
return FloatResponseBodyConverter.INSTANCE;
}
if (type == Integer.class || type == int.class) {
return IntegerResponseBodyConverter.INSTANCE;
}
if (type == Long.class || type == long.class) {
return LongResponseBodyConverter.INSTANCE;
}
if (type == Short.class || type == short.class) {
return ShortResponseBodyConverter.INSTANCE;
}
return null;
}
}

代码很简单,就是根据 Type 类型来返回不同类型的 ResponseBodyConverter ,如果没有自己想要处理的类型就直接返回 null,看一下 StringResponseBodyConverter 代码:

1
2
3
4
5
6
7
static final class StringResponseBodyConverter implements Converter<ResponseBody, String> {
static final StringResponseBodyConverter INSTANCE = new StringResponseBodyConverter();
@Override public String convert(ResponseBody value) throws IOException {
return value.string();
}
}

代码很简单直接将 ResponseBody 转换为 String 。

自定义想要的 Converter

既然这个是我们特殊处理的返回结果,那么我们先定义一个特殊的 JavaBean 作为特殊的 Type:

1
2
public class ObjectStringBean {
}

实现 Factory 逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BeanStringConverterFactory extends Converter.Factory {
public static BeanStringConverterFactory create(){
return new BeanStringConverterFactory();
}
//我们用的 get 请求,请求体不需要处理,
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return super.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}
//这里是关键代码,在判断 type== ObjectStringBean 时候,调用我们自定义的 BeanStringResponseBodyConverter
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if(type== ObjectStringBean.class){
return new BeanStringResponseBodyConverter();
}
//其他 Type 类型交给其他 Convert 处理
return null;
}
}

自定义 Converter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class BeanStringResponseBodyConverter implements Converter<ResponseBody, BaseBean<String>> {
@Override
public BaseBean<String> convert(ResponseBody value) throws IOException {
//第一步将 ResponseBody
String json = value.string();
if(TextUtils.isEmpty(json))return null;
//第二步将 String 转换为 BaseBean<String>
try {
JSONObject jsonObject = new JSONObject(json);
int code= GsonUtils.getJsonInt(jsonObject,"code");
String message= GsonUtils.getJsonString(jsonObject,"message");
String action= GsonUtils.getJsonString(jsonObject,"action");
JSONObject jsonData = GsonUtils.getJsonObject(jsonObject,"data");
String data=null;
if(jsonData!=null){
data= jsonData.toString();
}
BaseBean<String> baseBean=new BaseBean<>();
baseBean.setCode(code);
baseBean.setMsg(message);
baseBean.setData(data);
baseBean.setAction(action);
return baseBean;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
}

上面代码也很简单,没什么好说的,下面是使用我们自定义的 Convert :

使用自定义 Convert

第一步:添加自定义 Converter

1
2
3
4
5
6
7
8
9
10
11
private void init() {
retrofit = new Retrofit.Builder()
.client(OKHttpFactory.getInstance().getOkHttpClient())
.baseUrl(getBaseUrl())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//添加我们的 Converter
.addConverterFactory(BeanStringConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(CustomGsonConverterFactory.create())
.build();
}

第二步:编写请求接口

1
2
3
4
//注意,这里 ObjectStringBean 只是定义了我们想要的 Type 类型,不是请求结果的返回类型,
//请求结果的返回类型是我们在自定义的 Convert中返回的 BaseBean<String> 类型
@GET
Observable<ObjectStringBean> getListRefreshOrLoadMoreData(@Url String url);

第三步:处理接口返回的 BaseBean<String>Observable<BaseBean<T>> 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//调用接口返回 Observable<BaseBean<String>>
private static Observable<BaseBean<String>> getListRefreshOrLoadMoreData(String url){
return ApiManager.doHttpRequest(ApiManager.getPersonalApiService().getListRefreshOrLoadMoreData(url));
}
//将 Observable<BaseBean<String>> 根据传入进来的 Class<T> tClass 转换为 Observable<BaseBean<T>> 类型
public static <T> Observable<BaseBean<T>> getListRefreshOrLoadMoreData(String refreshUrl, final Class<T> tClass) {
Observable<BaseBean<T>> observable = getListRefreshOrLoadMoreData(refreshUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Function<BaseBean<String>, BaseBean<T>>() {
//转换实现
@Override
public BaseBean<T> apply(BaseBean<String> stringBaseBean) throws Exception {
BaseBean<T> baseBean = new BaseBean<>();
baseBean.setAction(stringBaseBean.getAction());
baseBean.setMsg(stringBaseBean.getMsg());
baseBean.setCode(stringBaseBean.getCode());
String data = stringBaseBean.getData();
T t = null;
if (!TextUtils.isEmpty(data)) {
t = GsonUtils.fromJson(data, tClass);
}
baseBean.setData(t);
return baseBean;
}
});
return observable;
}

第四步:调用处理业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void getRefreshableData(String refreshUrl) {
//传入我们想要的 JavaBean 类型
RefreshListViewModel.getListRefreshOrLoadMoreData(refreshUrl,TestListBean.List1Bean.class)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<BaseBean<TestListBean.List1Bean>>() {
@Override
public void onNext(BaseBean<TestListBean.List1Bean> list1BeanBaseBean) {
//处理业务逻辑
if (list1BeanBaseBean != null&&list1BeanBaseBean.getData()!=null) {
BaseListBean baseListBean= (BaseListBean) list1BeanBaseBean.getData();
mRefreshListView.setBaseListBean(baseListBean);
mRefreshListView.updateData(baseListBean.getData());
}
}
});
}

这样我们就不需要一个需要 List 列表的界面定义两个接口,写两套业务逻辑,我们共用一套即可。

总结: Retrofit 和 OKHttp 的设计模式真牛逼!