前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RxHttp 一条链发送请求之强大的数据解析功能(二)

RxHttp 一条链发送请求之强大的数据解析功能(二)

作者头像
Android技术干货分享
发布2019-06-01 10:36:27
9440
发布2019-06-01 10:36:27
举报
文章被收录于专栏:Android技术分享Android技术分享

简介

数据解析器Parser在RxHttp担任着一个很重要的角色,它的作用的将Http返回的数据,解析成我们想要的任意对象,可以用Json、DOM等任意数据解析方式。目前RxHttp提供了三个解析器,分别是SimpleParserListParserDownloadParser,如果这3个解析器不能满足我们的业务开发,就可以自定义解析器,下面我详细介绍。

首先我们先看看Parser的内部结构

代码语言:javascript
复制
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 对应的数据结构如下

代码语言:javascript
复制
public class Response {
    private int     code;
    private Address data;
    //省略set、get方法

    class Address {
        //为简单起见,省略了部分字段
        private String country; //国家
        private String region; //地区
        private String city; //城市
        //省略set、get方法
    }
}

开始发送请求

代码语言:javascript
复制
  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解析器,我们点进去看看

代码语言:javascript
复制
  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.classList里面的泛型我怎么传入呢?又该如何实现呢? 自答:如果想得到一个list<T>对象,通过fromSimpleParser操作符确实没办法实现,但是同时SimpleParser却能实现,我们可以直接new 出一个SimpleParser对象,并且传入一个List<T>即可,我们假设要获取学生的集合,如下:

代码语言:javascript
复制
  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>对象,如下

代码语言:javascript
复制
  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返回的输入流写到文件中,即文件下载

这个好理解,就不仔细讲解了,有一点要的说的,此解析器是支持断点下载,我们来看看如何实现断点下载,并且带进度回调

代码语言:javascript
复制
//断点下载,带进度
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类

代码语言:javascript
复制
public class Data<T> {
    private int    code;
    private String msg;
    private T      data;
    //这里省略get、set方法
}

假设,Data里面的T是一个学生对象,我们要拿到此学生信息,就可以这么做

代码语言:javascript
复制
  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观察者就能收到事件
  • 避免了使用匿名内部类

此时,我们就可以如下实现:

代码语言:javascript
复制
  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>又该怎么办呢?我们也许可以这样:

代码语言:javascript
复制
  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

代码都差不多,就不在讲解了,直接看怎么用:

代码语言:javascript
复制
  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方法也是通过注解生成的 我们最后来看一个问题

代码语言:javascript
复制
{
    "code": 0,
    "msg": "",
    "data": {
        "totalPage": 0,
        "list": []
    }
}

这种数据,我们又该如何解析呢?首先,我们再定一个Bean类叫PageList,如下:

代码语言:javascript
复制
public class PageList<T> {
    private int     totalPage;
    private List<T> list;
    //省略get/set方法
}

此Bean类,对于的是data字段的数据结构,机智的你肯定马上想到了用DataParser如何实现,如下:

代码语言:javascript
复制
  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

继续看看怎么用

代码语言:javascript
复制
  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自定的三个解析器SimpleParserListParserDownloadParser他们的用法及内部实现,后面又对常见的数据结构带领大家自定义了3个解析器,分别是DataParser、DataListParser及DataPageListParser,相信有了这个6个解析器,就能应对大多数场景了。

自问: 为啥不将自定义的三个解析器DataParser、DataListParser及DataPageListParser封装进RxHttp? 自答: 因为这3个解析器都涉及到了具体的业务需求,每个开发者的业务逻辑都可能不一样,故不能封装进RxHttp库里。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.05.31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • SimpleParser
  • ListParser
  • DownloadParser
  • 自定义解析器
    • DataParser
      • DataListParser
        • DataPageListParser
        • 小结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档