在上一文中,咱们对RxHttp作了一个总体的介绍,文章一经发表后,就收到了广大读者众多不一样的声音,有对个人确定,也有对RxHttp提出改进的建议,更有读者直接指出了个人不足,为此,我收获了不少,让我对不少东西都有了新的认知,我想这就是不少人坚持写做的缘由,由于这里,能够相互学习,相互交流以弥补本身的不足。因此我要感谢大家,是大家给我了动力让我继续写做,我会坚持写一些有养分的文章。php
因为刚开始写做,上一文有不少地方写的不够好,让很多读者走了冤枉路;收到读者给我提出了改进的建议后,我加班加点将RxHttp 版本升级到了1.0.2,主要增长了设置baseUrl的功能,我想这是目前市面上最优雅的设置方法。 为此我对上一文作了不少修改,欢迎新老读者打脸 RxHttp 一条链发送请求,新一代Http请求神器(一)java
数据解析器Parser
在RxHttp担任着一个很重要的角色,它的做用的将Http返回的数据,解析成咱们想要的任意对象,能够用Json、DOM等任意数据解析方式。目前RxHttp提供了三个解析器,分别是SimpleParser
、ListParser
及DownloadParser
,若是这3个解析器不能知足咱们的业务开发,就能够自定义解析器,下面我详细介绍。git
首先咱们先看看Parser的内部结构github
public interface Parser<T> {
/** * 数据解析 * @param response Http执行结果 * @return 解析后的对象类型 * @throws IOException 网络异常、解析异常 */
T onParse(@NonNull Response response) throws IOException;
}
复制代码
能够看到,Parser就是一个接口类,而且里面只有一个方法,输入Http请求返回的Response
对象,输出咱们传入的泛型T
,若是咱们要自定义解析器,就必需要实现此接口。网络
在上一文中,咱们对Parser作了简单的介绍,咱们来回顾一下。数据结构
咱们拿淘宝获取IP的接口做为测试接口http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42
对应的数据结构以下post
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的源码 this
onParser
方法,能够看到。
Response
(注意,此Response类是OkHttp内部的类,并不上咱们上面定义的类)对象,拿到Http的请求结果,为String对象到这,我想你应该知道SimpleParser
解析器的做用类,它就是将Http请求返回的结果直接解析成咱们想要的任意对象。
自问
:你说SimpleParser
能将数据解析成任意对象,而asObject(Class<T> type)
操做符传入的是一个Class<T>
类型,而对于List
对象,只能传入List.class
,List
里面的泛型我怎么传入呢?又该如何实现呢? 自答
:若是想获得一个list<T>
对象,经过asObject
操做符确实没办法实现,可是SimpleParser
却能实现,咱们能够直接new 出一个SimpleParser对象,而且传入一个List<T>
便可,咱们假设要获取学生的集合,以下:
RxHttp.get("/service/...")
.asParser(new SimpleParser<List<Student>>() {})
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(students -> { //这里students 即为List<Student>对象
//成功回调
}, throwable -> {
//失败回调
});
复制代码
能够看到,咱们直接使用asParser
操做符,并传入咱们new出来的SimpleParser对象,最后在观察者就能拿到List<Student>
对象。 到这,有读者会有疑问,咱们new出来的SimpleParser对象,为啥要使用匿名内部类呢?不使用不行吗?能够确定的回答不行。若是new SimpleParser<List<Student>>()
这样书写,编译器会报错,为何呢?眼尖的你可能发现了,SimpleParser无参的构造方法是protected
关键字修饰的,那为啥要用protected
关键字修饰呢?由于不用protected
关键字修饰,SimpleParser内部就拿不到泛型的具体类型,若是你再要问为何,那你就须要了解一些泛型了,这个跟Gson库里面的TypeToken
类是同一个道理,能够查看个人另外一片文章Android、Java泛型扫盲
上面SimpleParser咱们是经过匿名内部类new出来的,而后咱们知道,内部类都会持有外部类的引用,若是外部类是一个Activity,就有可能会有内存泄漏的危险(若是使用了RxLife就不会有这种危险),并且,这种写法本人也不是很喜欢。为此,有没有什么办法来避免此类问题呢?
有,那就是经过ListParser解析器
ListParser的做用是,将Http返回的结果,用Json解析成List<T>
对象,源码以下:
ParameterizedTypeImpl
类来处理泛型,这个类的用法及原理,也查看个人另外一片文章
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对象。
接下来咱们看看RxHttp提供的最后一个解析器DownloadParser
DownloadParser的做用是将Http返回的输入流写到文件中,即文件下载
//断点下载,带进度
public void breakpointDownloadAndProgress() {
String destPath = getExternalCacheDir() + "/" + "Miaobo.apk";
long length = new File(destPath).length();
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.setRangeHeader(length) //设置开始下载位置,结束位置默认为文件末尾
.asDownloadProgress(destPath,length) //若是须要衔接上次的下载进度,则须要传入上次已下载的字节数
.observeOn(AndroidSchedulers.mainThread()) //主线程回调
.doOnNext(progress -> {
//下载进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已下载的字节大小
long totalSize = progress.getTotalSize(); //要下载的总字节大小
})
.filter(Progress::isCompleted)//过滤事件,下载完成,才继续往下走
.map(Progress::getResult) //到这,说明下载完成,拿到Http返回结果并继续往下走
.as(RxLife.asOnMain(this)) //加入感知生命周期的观察者
.subscribe(s -> { //s为String类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
}
复制代码
跟带进度回调的下载代码差很少,上面也有注释,就不在讲解了。
在上面的介绍的3个解析中,SimpleParser能够说是万能的,任何数据结构,只要你建好对应的Bean类,都可以正确解析,就是要咱们去建n个Bean类,甚至这些Bean类,可能不少都是能够抽象化的。例如,大部分Http返回的数据结构均可以抽象成下面的Bean类
public class Data<T> {
private int code;
private String msg;
private T data;
//这里省略get、set方法
}
复制代码
假设,Data里面的T是一个学生对象,咱们要拿到此学生信息,就能够这么作
RxHttp.get(http://www.......) //这里get,表明Get请求
.asParser(new SimpleParser<Data<Student>>() {}) //这里泛型传入Data<Student>
.observeOn(AndroidSchedulers.mainThread()) //主线程回调
.map(Data::getData) //经过map操做符获取Data里面的data字段
.as(RxLife.asOnMain(this))
.subscribe(student -> {
//这里的student,即Data里面的data字段内容
}, throwable -> {
//Http请求出现异常
});
复制代码
以上代码有3个缺点
Data<Student>
对象,随后使用map
操做符从Data<Student>
拿到Student对象传给下游观察者Data
里面的code字段作验证那么有什么优雅的办法解决呢?答案就是自定义解析器。咱们来定一个DataParser解析器,以下:
此时,咱们就能够以下实现:
RxHttp.get("http://www...") //这里get,表明Get请求
.asDataParser(Student.class) //此方法是经过注解生成的
.as(RxLife.asOnMain(this))
.subscribe(student -> {
//这里的student,即Data里面的data字段内容
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Data里面的msg字段或者其余异常信息
String code = throwable.getLocalizedMessage(); //Data里面的code字段,若是有传入的话
});
复制代码
注:
咱们在定义DataParser时,使用了注解@Parser(name = "DataParser")
,故在RxHttp类里有asDataParser
方法,注解使用请查看RxHttp 一条链发送请求之注解处理器 Generated API(四)
而后,若是Data里面T是一个List<T>
又该怎么办呢?咱们也许能够这样:
RxHttp.get("http://www...") //这里get,表明Get请求
.asParser(new DataParser<List<Student>>() {})
.as(RxLife.asOnMain(this))
.subscribe(students -> {
//这里的students,为List<Student>对象
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Data里面的msg字段或者其余异常信息
String code = throwable.getLocalizedMessage(); //Data里面的code字段,若是有传入的话
});
复制代码
又是经过匿名内部类实现的,心累,有没有更优雅的方式?有,仍是自定义解析器,咱们来定义一个DataListParser解析器
RxHttp.get("http://www...") //这里get,表明Get请求
.asDataListParser(Student.class) //此方法是经过注解生成的
.as(RxLife.asOnMain(this))
.subscribe(students -> {
//这里的students,为List<Student>对象
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Data里面的msg字段或者其余异常信息
String code = throwable.getLocalizedMessage(); //Data里面的code字段,若是有传入的话
});
复制代码
注:
asDataListParser
方法也是经过注解生成的 咱们最后来看一个问题
{
"code": 0,
"msg": "",
"data": {
"totalPage": 0,
"list": []
}
}
复制代码
这种数据,咱们又该如何解析呢?首先,咱们再定一个Bean类叫PageList,以下:
public class PageList<T> {
private int totalPage;
private List<T> list;
//省略get/set方法
}
复制代码
此Bean类,对于的是data字段的数据结构,机智的你确定立刻想到了用DataParser如何实现,以下:
RxHttp.get("http://www...") //这里get,表明Get请求
.asParser(new DataParser<PageList<Student>>(){})
.as(RxLife.asOnMain(this))
.subscribe(pageList -> {
//这里的pageList,即为PageList<Student>类型
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Data里面的msg字段或者其余异常信息
String code = throwable.getLocalizedMessage(); //Data里面的code字段,若是有传入的话
});
}
复制代码
好吧,又是匿名内部类,仍是乖乖自定义解析器吧。咱们定义一个DataPageListParser解析器,以下:
RxHttp.get("http://www...") //这里get,表明Get请求
.asDataPageListParser(Student.class) //此方法是经过注解生成的
.as(RxLife.asOnMain(this))
.subscribe(pageList -> {
//这里的pageList,即为PageList<Student>类型
}, throwable -> {
//Http请求出现异常
String msg = throwable.getMessage(); //Data里面的msg字段或者其余异常信息
String code = throwable.getLocalizedMessage(); //Data里面的code字段,若是有传入的话
});
复制代码
注:
asDataPageListParser
方法依然是经过注解生成的。
到这,仔细观察你会发现,咱们定一个三个解析器DataParser、DataListParser及DataPageListParser,代码其实都差很少,用法也差很少,无非就输出的不同。
本篇文章,给你们介绍了RxHttp自定的三个解析器SimpleParser
、ListParser
及DownloadParser
他们的用法及内部实现,后面又对常见的数据结构带领你们自定义了3个解析器,分别是DataParser、DataListParser及DataPageListParser,相信有了这个6个解析器,就能应对大多数场景了。若是还有场景不能实现,看完本篇文章自定义解析对你来讲也是很是容易的事情了。
自问:
为啥不将自定义的三个解析器DataParser、DataListParser及DataPageListParser封装进RxHttp? 自答:
由于这3个解析器都涉及到了具体的业务需求,每一个开发者的业务逻辑均可能不同,故不能封装进RxHttp库里。
最后,本文若是有写的不对的地方,请广大读者指出。 若是以为我写的不错,记得给我点赞RxHttp
更过详情请查看RxHttp系列其它文章
RxHttp 一条链发送请求之注解处理器 Generated API(四)
转载请注明出处,谢谢🙏