在上一文中,我们对RxHttp做了一个整体的介绍,文章一经发表后,就收到了广大读者众多不同的声音,有对我的肯定,也有对RxHttp提出改进的建议,更有读者直接指出了我的不足,为此,我收获了很多,让我对很多东西都有了新的认知,我想这就是很多人坚持写作的原因,因为这里,可以相互学习,相互交流以弥补自己的不足。所以我要感谢你们,是你们给我了动力让我继续写作,我会坚持写一些有营养的文章。
由于刚开始写作,上一文有很多地方写的不够好,让不少读者走了冤枉路;收到读者给我提出了改进的建议后,我加班加点将 RxHttp 版本升级到了1.0.2,主要增加了设置baseUrl的功能,我想这是目前市面上最优雅的设置方法。 为此我对上一文做了很多修改,欢迎新老读者打脸 RxHttp 一条链发送请求,新一代Http请求神器(一)
数据解析器 Parser
在RxHttp担任着一个很重要的角色,它的作用的将Http返回的数据,解析成我们想要的任意对象,可以用Json、DOM等任意数据解析方式。目前RxHttp提供了三个解析器,分别是 SimpleParser
、 ListParser
及 DownloadParser
,如果这3个解析器不能满足我们的业务开发,就可以自定义解析器,下面我详细介绍。
首先我们先看看Parser的内部结构
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
对应的数据结构如下
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)") .fromSimpleParser(Response.class) //这里返回Observable<Response> 对象 .as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调 .subscribe(response -> { //成功回调 }, throwable -> { //失败回调 }); 复制代码
上面代码中使用了 fromSimpleParser
操作符,并传入 Response.class
,此是在观察者就能只能拿到Response对象,那么它是如何实现的呢?看名字应该也能猜到,它内部就是用了SimpleParser解析器,我们点进去看看
public <T> Observable<T> fromSimpleParser(Class<T> type) { return from(SimpleParser.get(type)); } 复制代码
果然它使用了SimpleParser解析器,那我们就来看看SimpleParser的源码
我们具体看onParser
方法,可以看到。
Response
到这,我想你应该知道 SimpleParser
解析器的作用类,它就是将Http请求返回的结果直接解析成我们想要的任意对象。
自问
:你说 SimpleParser
能将数据解析成任意对象,而 fromSimpleParser(Class<T> type)
操作符传入的是一个 Class<T>
类型,而对于 List
对象,只能传入 List.class
, List
里面的泛型我怎么传入呢?又该如何实现呢? 自答
:如果想得到一个 list<T>
对象,通过 fromSimpleParser
操作符确实没办法实现,但是同时 SimpleParser
却能实现,我们可以直接new 出一个SimpleParser对象,并且传入一个 List<T>
即可,我们假设要获取学生的集合,如下:
RxHttp.get("/service/...") .from(new SimpleParser<List<Student>>() {}) .as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调 .subscribe(students -> { //这里students 即为List<Student>对象 //成功回调 }, throwable -> { //失败回调 }); 复制代码
可以看到,我们直接使用 from
操作符,并传入我们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/...") .fromListParser(Student.class) .as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调 .subscribe(students -> { //这里students 即为List<Student>对象 //成功回调 }, throwable -> { //失败回调 }); 复制代码
可以看到,直接使用 fromListParser
操作符,传入 Student.class
即可,它内部就是通过 ListParser.get(Student.class)
获取的ListParser对象。
接下来我们看看RxHttp提供的最后一个解析器DownloadParser
DownloadParser的作用是将Http返回的输入流写到文件中,即文件下载
这个好理解,就不仔细讲解了,有一点要的说的,此解析器是支持断点下载,我们来看看如何实现断点下载,并且带进度回调
//断点下载,带进度 public void breakpointDownloadAndProgress() { String destPath = getExternalCacheDir() + "/" + "Miaobo.apk"; File file = new File(destPath); long length = file.length(); RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk") //如果文件存在,则添加 RANGE 头信息 ,以支持断点下载 .addHeader("RANGE", "bytes=" + length + "-", length > 0) .downloadProgress(destPath) .map(progress -> { if (length > 0) {//增加上次已经下载好的字节数 progress.addCurrentSize(length); progress.addTotalSize(length); progress.updateProgress(); } return progress; }) .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请求 .from(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解析器,如下:
代码跟SimpleParser类差不多,好处如下
此时,我们就可以如下实现:
RxHttp.get("http://www...") //这里get,代表Get请求 .fromDataParser(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类里有 fromDataParser
方法,注解使用请查看 RxHttp 扩展篇之注解处理器 Generated API(七)
然后,如果Data里面T是一个 List<T>
又该怎么办呢?我们也许可以这样:
RxHttp.get("http://www...") //这里get,代表Get请求 .from(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请求 .fromDataListParser(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字段,如果有传入的话 }); 复制代码
注:
fromDataListParser
方法也是通过注解生成的 我们最后来看一个问题
{ "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请求 .from(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请求 .fromDataPageListParser(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字段,如果有传入的话 }); 复制代码
注:
fromDataPageListParser
方法依然是通过注解生成的。
到这,仔细观察你会发现,我们定一个三个解析器DataParser、DataListParser及DataPageListParser,代码其实都差不多,用法也差不多,无非就输出的不一样。
本篇文章,给大家介绍了RxHttp自定的三个解析器 SimpleParser
、 ListParser
及 DownloadParser
他们的用法及内部实现,后面又对常见的数据结构带领大家自定义了3个解析器,分别是DataParser、DataListParser及DataPageListParser,相信有了这个6个解析器,就能应对大多数场景了。如果还有场景不能实现,看完本篇文章自定义解析对你来说也是非常容易的事情了。
自问:
为啥不将自定义的三个解析器DataParser、DataListParser及DataPageListParser封装进RxHttp? 自答:
因为这3个解析器都涉及到了具体的业务需求,每个开发者的业务逻辑都可能不一样,故不能封装进RxHttp库里。
最后,本文如果有写的不对的地方,请广大读者指出。 如果觉得我写的不错,记得给我点赞 RxHttp
转载请注明出处,谢谢:pray:,下篇文章,将详细讲解另一重要角色Param。