前言
在上一文中,我们对RxHttp做了一个整体的介绍,文章一经发表后,就收到了广大读者众多不同的声音,有对我的肯定,也有对RxHttp提出改进的建议,更有读者直接指出了我的不足,为此,我收获了很多,让我对很多东西都有了新的认知,我想这就是很多人坚持写作的原因,因为这里,可以相互学习,相互交流以弥补自己的不足。所以我要感谢你们,是你们给我了动力让我继续写作,我会坚持写一些有营养的文章。
简介
数据解析器Parser
在RxHttp担任着一个很重要的角色,它的作用的将Http返回的数据,解析成我们想要的任意对象,可以用Gson、SampleXml、ProtoBuf、FastJson等第三方数据解析工具。目前RxHttp提供了5个解析器,分别是SimpleParser
、ListParser
、MapParser
、BitmapParser
及DownloadParser
,如果这5个解析器不能满足我们的业务开发,还可以自定义解析器,下面我详细介绍。
首先我们先看看Parser的内部结构
public interface Parser<T> {
/**
* 数据解析
* @param response Http执行结果
* @return 解析后的对象类型
* @throws IOException 网络异常、解析异常
*/
T onParse(@NonNull Response response) throws IOException;
}
可以看到,Parser就是一个接口类,并且里面只有一个方法,输入Http请求返回的Response
对象,输出我们传入的泛型T
,如果我们要自定义解析器,就必须要实现此接口。
在上一文中,我们对Parser做了简单的介绍,我们来回顾一下。
内置解析器
SimpleParser
我们拿淘宝获取IP的接口作为测试接口http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42
对应的数据结构如下
public class Response {
private int code;
private Address data;
//省略set、get方法
class Address {
//为简单起见,省略了部分字段
private String country; //国家
private String region; //地区
private String city; //城市
//省略set、get方法
}
}
开始发送请求
RxHttp.get("http://ip.taobao.com/service/getIpInfo.php") //Get请求
.add("ip", "63.223.108.42")//添加参数
.addHeader("accept", "*/*") //添加请求头
.addHeader("connection", "Keep-Alive")
.addHeader("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)")
.asObject(Response.class) //这里返回Observable<Response> 对象
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(response -> {
//成功回调
}, throwable -> {
//失败回调
});
上面代码中使用了asObject
操作符,并传入Response.class
,此时观察者就能能拿到Response对象,那么它是如何实现的呢?它内部就是用了SimpleParser解析器,我们点进去看看
public <T> Observable<T> asObject(Class<T> type) {
return asParser(SimpleParser.get(type));
}
果然它使用了SimpleParser解析器,那我们就来看看SimpleParser的源码
public class SimpleParser<T> extends AbstractParser<T> {
//省略构造方法
@Override
public T onParse(Response response) throws IOException {
return convert(response, mType);
}
}
额。。onParser
方法只调用了父类的convert方法,继续看源码:
public interface Parser<T> {
default <R> R convert(Response response, Type type) throws IOException {
ResponseBody body = ExceptionHelper.throwIfFatal(response);
boolean onResultDecoder = isOnResultDecoder(response);
LogUtil.log(response, onResultDecoder, null);
IConverter converter = getConverter(response);
return converter.convert(body, type, onResultDecoder);
}
}
我们具体看onParser
方法,可以看到。
首先通过Response对象拿到ResponseBody,随后拿到一个IConverter接口对象,然后将ResponseBody对象转换为我们期望的泛型对象。
到这,我想你应该知道SimpleParser
解析器的作用类,它就是将Http请求返回的结果直接解析成我们想要的任意对象。
如果我们要获取List<Student>
对象呢,则可以直接new出一个SimpleParser对象,如下:
RxHttp.get("/service/...")
.asParser(new SimpleParser<List<Student>>() {})
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(students -> { //这里students 即为List<Student>对象
//成功回调
}, throwable -> {
//失败回调
});
可以看到,我们直接使用asParser
操作符,并传入我们new出来的SimpleParser对象,最后在观察者就能拿到List<Student>
对象。
到这,也许有人会说,这种new出来的写法一点都不友好,代码看起来不简洁,有没有更简单的写法。
有,那就是通过ListParser解析器
ListParser
ListParser的作用是,将Http返回的结果,解析成List<T>
对象,源码如下:
public class ListParser<T> extends AbstractParser<List<T>> {
//省略构造方法
@Override
public List<T> onParse(Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(List.class, mType); //拿到泛型类型
return convert(response, type);
}
}
代码跟SimpleParser差不多,不在详细讲解。不同的是这里使用了ParameterizedTypeImpl
类来包装了一个List<Student>
类型,这个类的用法及原理,也查看我的另一片文章Android、Java泛型扫盲
我们直接看看通过ListParser
如何拿到List<T>
对象,如下
RxHttp.get("/service/...")
.asList(Student.class)
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(students -> { //这里students 即为List<Student>对象
//成功回调
}, throwable -> {
//失败回调
});
可以看到,直接使用asList
操作符,传入Student.class
即可,它内部就是通过ListParser.get(Student.class)
获取的ListParser对象。
MapParser
除了能直接获取List对象外,我们还可以通过MapParser直接获取一个Map对象,如下:
RxHttp.get("/service/...")
.asMap(String.class, Student.class)
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(map -> { //这里map 即为Map<String, Student>对象
//成功回调
}, throwable -> {
//失败回调
});
可以看到,通过asMap(Class<K> kType, Class<V> vType)
方法,不仅能得一个map对象,还能指定map里面得泛型类型。
到这,相信你也能猜到BitmapParser、DownloadParser这两个解析器如何使用了。是的,通过asBitmap
、asDownload
操作符,就能获取到一个Bitmap对象和执行下载操作,这里就不再讲解了。
自定义解析器
在上面的介绍的3个解析中,SimpleParser可以说是万能的,任何数据结构,只要你建好对应的Bean类,都能够正确解析,就是要我们去建n个Bean类,甚至这些Bean类,可能很多都是可以抽象化的。例如,大部分Http返回的数据结构都可以抽象成下面的Bean类
public class Response<T> {
private int code;
private String msg;
private T data;
//这里省略get、set方法
}
假设,Response里面的T是一个学生对象,我们要拿到此学生信息,就可以这么做
RxHttp.get(http://www.......) //这里get,代表Get请求
.asParser(new SimpleParser<Response<Student>>() {}) //这里泛型传入Response<Student>
.observeOn(AndroidSchedulers.mainThread()) //主线程回调
.map(Response::getData) //通过map操作符获取Response里面的data字段
.as(RxLife.asOnMain(this))
.subscribe(student -> {
//这里的student,即Response里面的data字段内容
}, throwable -> {
//Http请求出现异常
});
以上代码有3个缺点
- 还是通过SimpleParse匿名内部类实现的,前面说过,这种方式有可能造成内存泄漏,而且写法上不是很优雅
- 下游首先拿到的是一个
Response<Student>
对象,随后使用map
操作符从Response<Student>
拿到Student对象传给下游观察者 - 没法统一对
Response
里面的code字段做验证
ResponseParser
那么有什么优雅的办法解决呢?答案就是自定义解析器。我们来定一个ResponseParser解析器,如下:
@Parser(name = "Response")
public class ResponseParser<T> extends AbstractParser<T> {
//省略构造方法
@Override
public T onParse(okhttp3.Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(Response.class, mType); //获取泛型类型
Response<T> data = convert(response, type);
T t = data.getData(); //获取data字段
if (t == null && mType == String.class) {
/*
* 考虑到有些时候服务端会返回:{"errorCode":0,"errorMsg":"关注成功"} 类似没有data的数据
* 此时code正确,但是data字段为空,直接返回data的话,会报空指针错误,
* 所以,判断泛型为String类型时,重新赋值,并确保赋值不为null
*/
t = (T) data.getMsg();
}
if (data.getCode() != 0 || t == null) {//code不等于0,说明数据不正确,抛出异常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return t;
}
}
代码跟SimpleParser类差不多,好处如下
- ResponseParser自动为我们做了一层过滤,我们可以直接拿到T对象,而不再使用map操作符了
- 内部可以对code字段做统一判断,根据不同的code,抛出不同的异常,做到统一的错误处理机制(这里抛出的异常会被下游的onError观察者接收)
- 当codo正确时,就代表了数据正确,下游的onNext观察者就能收到事件
- 避免了使用匿名内部类
此时,我们就可以如下实现:
RxHttp.get("http://www...") //这里get,代表Get请求
.asResponse(Student.class) //此方法是通过注解生成的
.as(RxLife.asOnMain(this))
.subscribe(student -> {
//这里的student,即Response里面的data字段内容
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Response里面的msg字段或者其他异常信息
String code = throwable.getLocalizedMessage(); //Response里面的code字段,如果有传入的话
});
注:
我们在定义ResponseParser时,使用了注解@Parser(name = "Response")
,故在RxHttp类里有asResponse
方法,注解使用请查看RxHttp 一条链发送请求之注解处理器 Generated API(四)
然后,如果Data里面T是一个List<T>
又该怎么办呢?我们也许可以这样:
RxHttp.get("http://www...") //这里get,代表Get请求
.asParser(new ResponseParser<List<Student>>() {})
.as(RxLife.asOnMain(this))
.subscribe(students -> {
//这里的students,为List<Student>对象
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Response里面的msg字段或者其他异常信息
String code = throwable.getLocalizedMessage(); //Response里面的code字段,如果有传入的话
});
又是通过匿名内部类实现的,心累,有没有更优雅的方式?有,还是自定义解析器,我们来定义一个ResponseListParser解析器
ResponseListParser
@Parser(name = "ResponseList")
public class ResponseListParser<T> extends AbstractParser<List<T>> {
//省略构造方法
@Override
public List<T> onParse(okhttp3.Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(Response.class, List.class, mType); //获取泛型类型
Response<List<T>> data = convert(response, type);
List<T> list = data.getData(); //获取data字段
if (data.getCode() != 0 || list == null) { //code不等于0,说明数据不正确,抛出异常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return list;
}
}
代码都差不多,就不在讲解了,直接看怎么用:
RxHttp.get("http://www...") //这里get,代表Get请求
.asResponseList(Student.class) //此方法是通过注解生成的
.as(RxLife.asOnMain(this))
.subscribe(students -> {
//这里的students,为List<Student>对象
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Response里面的msg字段或者其他异常信息
String code = throwable.getLocalizedMessage(); //Response里面的code字段,如果有传入的话
});
注:
asResponseList
方法也是通过注解生成的
我们最后来看一个问题
{
"code": 0,
"msg": "",
"data": {
"totalPage": 0,
"list": []
}
}
这种数据,我们又该如何解析呢?首先,我们再定一个Bean类叫PageList,如下:
public class PageList<T> {
private int totalPage;
private List<T> list;
//省略get/set方法
}
此Bean类,对于的是data字段的数据结构,机智的你肯定马上想到了用ResponseParser如何实现,如下:
RxHttp.get("http://www...") //这里get,代表Get请求
.asParser(new ResponseParser<PageList<Student>>(){})
.as(RxLife.asOnMain(this))
.subscribe(pageList -> {
//这里的pageList,即为PageList<Student>类型
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Response里面的msg字段或者其他异常信息
String code = throwable.getLocalizedMessage(); //Response里面的code字段,如果有传入的话
});
}
好吧,又是匿名内部类,还是乖乖自定义解析器吧。我们定义一个ResponsePageListParser解析器,如下:
ResponsePageListParser
@Parser(name = "ResponsePageList")
public class ResponsePageListParser<T> extends AbstractParser<PageList<T>> {
//省略构造方法
@Override
public PageList<T> onParse(okhttp3.Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(Response.class, PageList.class, mType); //获取泛型类型
Response<PageList<T>> data = convert(response, type);
PageList<T> pageList = data.getData(); //获取data字段
if (data.getCode() != 0 || pageList == null) { //code不等于0,说明数据不正确,抛出异常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return pageList;
}
}
继续看看怎么用
RxHttp.get("http://www...") //这里get,代表Get请求
.asResponsePageList(Student.class) //此方法是通过注解生成的
.as(RxLife.asOnMain(this))
.subscribe(pageList -> {
//这里的pageList,即为PageList<Student>类型
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Response里面的msg字段或者其他异常信息
String code = throwable.getLocalizedMessage(); //Response里面的code字段,如果有传入的话
});
注:
asResponsePageList
方法依然是通过注解生成的。
到这,仔细观察你会发现,我们定一个三个解析器ResponseParser、ResponseListParser及ResponsePageListParser,代码其实都差不多,用法也差不多,无非就输出的不一样。
小结
本篇文章,给大家介绍了RxHttp自定的三个解析器SimpleParser
、ListParser
及DownloadParser
他们的用法及内部实现,后面又对常见的数据结构带领大家自定义了3个解析器,分别是ResponseParser、ResponseListParser及ResponsePageListParser,相信有了这个6个解析器,就能应对大多数场景了。如果还有场景不能实现,看完本篇文章自定义解析对你来说也是非常容易的事情了。
自问:
为啥不将自定义的三个解析器ResponseParser、ResponseListParser及ResponsePageListParser封装进RxHttp?
自答:
因为这3个解析器都涉及到了具体的业务需求,每个开发者的业务逻辑都可能不一样,故不能封装进RxHttp库里。
最后,本文如果有写的不对的地方,请广大读者指出。 如果觉得我写的不错,记得给我点赞RxHttp
更过详情请查看RxHttp系列其它文章
RxHttp 一条链发送请求之注解处理器 Generated API(四)
转载请注明出处,谢谢🙏