前言
在之前, 我们对 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 做了简单的介绍, 我们来回顾一下.
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)")
- .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 方法, 可以看到.
首先通过将 Http 请求返回的 Response(注意, 此 Response 类是 OkHttp 内部的类, 并不上我们上面定义的类)对象, 拿到 Http 的请求结果, 为 String 对象
然后就拿到我们传入的泛型类型判断是否是 String 类型, 如果是, 则直接将结果返回, 否则就通过 JSON 将结果解析成我们传入的泛型对象
最后对泛型对象做判断, 如果为空, 就代表解析失败, 我们抛出异常(这里的异常会被 RxJava 的 onError 观察者接收), 否则返回泛型对象
到这, 我想你应该知道 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 类是同一个道理.
上面 SimpleParser 我们是通过匿名内部类 new 出来的, 然后我们知道, 内部类都会持有外部类的引用, 如果外部类是一个 Activity, 就有可能会有内存泄漏的危险(如果使用了 RxLife 就不会有这种危险), 而且, 这种写法本人也不是很喜欢. 为此, 有没有什么办法来避免此类问题呢?
有, 那就是通过 ListParser 解析器
ListParser
ListParser 的作用是, 将 Http 返回的结果, 用 JSON 解析成 List<T > 对象, 源码如下:
代码跟 SimpleParser 差不多, 不在详细讲解. 不同的是这里使用了 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
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) // 设置开始下载位置, 结束位置默认为文件末尾
- .downloadProgress(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 请求
- .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 个缺点
还是通过 SimpleParse 匿名内部类实现的, 前面说过, 这种方式有可能造成内存泄漏, 而且写法上不是很优雅
下游首先拿到的是一个 Data<Student > 对象, 随后使用 map 操作符从 Data<Student > 拿到 Student 对象传给下游观察者
没法统一对 Data 里面的 code 字段做验证
DataParser
那么有什么优雅的办法解决呢? 答案就是自定义解析器. 我们来定一个 DataParser 解析器, 如下:
代码跟 SimpleParser 类差不多, 好处如下
DataParser 自动为我们做了一层过滤, 我们可以直接拿到 T 对象, 而不再使用 map 操作符了
内部可以对 code 字段做统一判断, 根据不同的 code, 抛出不同的异常, 做到统一的错误处理机制(这里抛出的异常会被下游的 onError 观察者接收)
当 codo 正确时, 就代表了数据正确, 下游的 onNext 观察者就能收到事件
避免了使用匿名内部类
此时, 我们就可以如下实现:
- 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 方法.
然后, 如果 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 解析器
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 解析器, 如下:
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
来源: http://www.jianshu.com/p/0602b177639e