乐趣区

RxHttp-一条链发送请求新一代Http请求神器一

简介

RxHttp 是基于 OkHttp 的二次封装,并于 RxJava 做到无缝衔接,一条链就能发送一个完整的请求。主要功能如下:

  • 支持 Get、Post、Put、Delete 等任意请求方式,可自定义请求方式
  • 支持 Json、DOM 等任意数据解析方式,可自定义数据解析器
  • 支持文件下载 / 上传,及进度的监听,并且支持断点下载
  • 支持在 Activity/Fragment 的任意生命周期方法,自动关闭未完成的请求
  • 支持添加公共参数 / 头部信息,且可动态更改 baseUrl
  • 支持请求串行和并行

gradle 依赖

implementation 'com.rxjava.rxhttp:rxhttp:1.0.3'
// 注解处理器,生成 RxHttp 类,即可一条链发送请求
annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.0.3'
// 管理 RxJava 及生命周期,Activity/Fragment 销毁,自动关闭未完成的请求
implementation 'com.rxjava.rxlife:rxlife:1.0.4'

RxHttp 源码
RxLife 源码

初始化

// 设置 debug 模式,此模式下有日志打印
HttpSender.setDebug(boolean debug)
// 非必须, 只能初始化一次,第二次将抛出异常
HttpSender.init(OkHttpClient okHttpClient)
// 或者,调试模式下会有日志输出
HttpSender.init(OkHttpClient okHttpClient, boolean debug)

此步骤是非必须的,不初始化或者传入 null 即代表使用默认 OkHttpClient 对象。

疑问:标题不是说好的是 RxHttp,这么用 HttpSender 做一些初始化呢?这里先卖一个关子,后面会解答

添加公共参数 / 头部及重新设置 url

相信大多数开发者在开发中,都遇到要为 Http 请求添加公共参数 / 请求头,甚至要为不同类型的请求添加不同的公共参数 / 请求头,为此,RxHttp 为大家提供了一个静态接口回调,如下,每发起一次请求,此接口就会被回调一次,并且此回调在子线程进行(在请求执行线程回调)

HttpSender.setOnParamAssembly(new Function() {
    @Override
    public Param apply(Param p) {if (p instanceof GetRequest) {// 根据不同请求添加不同参数} else if (p instanceof PostRequest) {} else if (p instanceof PutRequest) {} else if (p instanceof DeleteRequest) { }
        // 可以通过 p.getSimpleUrl() 拿到 url 更改后,重新设置
        //p.setUrl("");
        return p.add("versionName", "1.0.0")// 添加公共参数
                .addHeader("deviceType", "android"); // 添加公共请求头
    }
});

然后有些请求我们不希望添加公共参数 / 请求头,RxHttp 又改如何实现呢?很简单,发起请求前,设置不添加公共参数,如下:

Param param = Param.get("http://...")
        // 设置是否对 Param 对象修饰,即是否添加公共参数,默认为 true
        .setAssemblyEnabled(false); // 设为 false,就不会回调上面的静态接口

到这,也许你们会有疑问,Param 是什么东东,下面就为大家讲解。

Param

首先,我们来看看如何发送一个请求

Param param = Param.get("http://...")
        .add("key", "value");
Disposable disposable = HttpSender.from(param)
        .subscribe(s -> { // 这里的 s 为 String 类型,即 Http 请求的返回结果
           // 成功回调
        }, throwable -> {// 失败回调});

疑问:说好的一条链发送请求呢?别着急,还没到放大招的时候
到这,我可以告诉大家,Param承担的是一个请求体的一个角色,我们通过 Param 可以确定请求方式 (如:Get、Post、Put、Delete 等请求方式)、添加请求参数、添加请求头、添加 File 对象等;然后通过 HttpSender,传入Param 对象,将请求发送出去。

HttpSender

到这,有人又有疑问,前面初始化、设置公共参数都用到了 HttpSender,这里发送请求又用到了 HttpSender , 那么它又是承担怎么样的一个角色呢?看名字,我们可以理解为它就是一个请求发送者,通过一个 from 操作符,传入一个 Param 对象,然后返回一个 RxJavaObservable对象,此时,我们就可以使用 RxJava 强大的操作符去处理相关的逻辑 (这就是简介说的,做到了与 RxJava 的无缝链接),在这,我们只是使用了subscribe 操作符去订阅观察者。

RxHttp

现在,我们正式放大招,标题说好的一条链发送请求,既然吹牛了,就要去实现它。拿上面的例子,看看我们如何一条链实现,上代码

  RxHttp.get("http://...")
        .add("key", "value")
        .from()  
        .subscribe(s -> { // 这里的 s 为 String 类型,即 Http 请求的返回结果
           // 成功回调
        }, throwable -> {// 失败回调});

我们的主角 RxHttp 终于登场了,可以看到使用 RxHttp 类我们就实现了一条链完成请求的发送,那它又是承担一个什么角色呢?我们暂时可以理解为RxHttp=Param+HttpSender,并且还有自己特殊的使命。至于什么使用,后面会讲解。

我们现在来解疑惑,为什么我们的库叫 RxHttp,但是初始化、设置公共参数等却用 HttpSender?因为RxHttp 这个类不在 RxHttp 库中,它是通过注解处理器生成的类。前面我们看到 gradle 依赖时,使用了

annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.0.2'

该注解处理器的目的就是在项目中生成 RxHttp 类,那为何不直接把它写到库里面去呢?前面讲过,因为它有自己的使命,而这个使命,就是我们可以通过注解,在 RxHttp 中生成自定义的 api,我们来看看如何使用注解。

动态设置 baseUrl

现实开发中,大部人开发者都会将 baseUrl 单独抽取出来,RxHttp 也考虑到了这一点,RxHttp 通过 @DefaultDomain 注解来配置 baseUrl,看代码

public class Url {@DefaultDomain() // 设置为默认域名
    public static String baseUrl = "http://ip.taobao.com/";
}

rebuild 一下项目,此时我们发送请求就可以直接传入 path 路径,如下:

  RxHttp.get("/service/getIpInfo.php")
        .add("key", "value")
        .from()  
        .subscribe(s -> { // 这里的 s 为 String 类型,即 Http 请求的返回结果
           // 成功回调
        }, throwable -> {// 失败回调});

RxHttp 在发送请求前,会对 url 做判断,如果没有域名,就会自定加上默认的域名,也就是 baseUrl。然后,如果我们不想使用默认的域名呢?RxHttp 也考虑到来,提供了一个 @Domain 注解,我们再来看看用法:

public class Url {@Domain(name = "Update9158") // 设置非默认域名,name 可不传,不传默认为变量的名称
    public static String update = "http://update.9158.com";

    @DefaultDomain() // 设置为默认域名
    public static String baseUrl = "http://ip.taobao.com/";
}

此时再 rebuild 一下项目,就会在 RxHttp 类中生成一个 setDomainToUpdate9158IfAbsent() 方法,其中的 Update9158 字符就是 name 指定的名字,然后发请求就可以这样:

  RxHttp.get("/service/getIpInfo.php")
        .setDomainToUpdate9158IfAbsent()
        .add("key", "value")
        .from()  
        .subscribe(s -> { // 这里的 s 为 String 类型,即 Http 请求的返回结果
           // 成功回调
        }, throwable -> {// 失败回调});

此时,RxHttp 检测到 url 已经配置了域名,就不会再去使用默认的域名。同样的,setDomainToUpdate9158IfAbsent也会检测 url 有没有配置域名,如果配置了,也不会使用我们指定的域名。

注意:@Domain 注解可以在多个地方使用,而 @DefaultDomain()只能在一个地方使用,否则编译不通过,很好理解,默认域名只可能有一个。两个注解都要使用在 public static 修饰的 String 类型变量上,对 final 关键字没有要求,可写可不写,这就表明,baseUrl 可以动态更改,RxHttp 始终会拿到的最新的 baseUrl。怎么样,是不是很 nice!!
更多注解使用请查看 RxHttp 一条链发送请求之注解处理器 Generated API(四)

接下来,我们来看看,如何发送 Post 请求、如何在 Activity/Fragment 销毁时,自动关闭为完成的请求、如何上传 / 下载文件及进度的监听、如何把 Http 返回的结果自动解析成我们想要的对象。

注:以下讲解均使用 RxHttp

Post

  RxHttp.postForm("http://...")
        .add("key", "value")
        .from()  
        .subscribe(s -> { // 这里的 s 为 String 类型,即 Http 请求的返回结果
           // 成功回调
        }, throwable -> {// 失败回调});

可以看到,跟上面的 Get 请求只有一点不同,Get 是 RxHttp.get,而 Post 是RxHttp.postForm,除此之外,没有任何区别,我们在看来来,RxHttp 都有哪些静态方法供我们选择请求方式
可以看到,默认提供了 10 个静态方法供我们选择具体的请求方式,有 Get、Post、Put 等,而 Post 等又分为 postForm 和 postJson,这个好理解,前者是发送表单形式的 post 请求,后者是发送 json 字符串的 post 请求。

现实中,这些默认的请求方式显然不能满足我们的需求,如:我要发送加密的 post 请求,这个时候该怎么办呢?此时就需要我们自定义请求方式。自定义请求方式请查看 RxHttp 一条链发送请求之强大的 Param 类(三)

Activity 销毁,自动关闭未完成的请求

上面的案例中,在 Activity/Fragment 销毁时,如果请求还未完成,就会造成 Activity/Fragment 无法回收,导致内存泄漏。这是非常严重的问题,那么 RxHttp 是如何解决的呢?此时,就要引入我自己写的另一个库 RxLife,直接看看如何使用

  RxHttp.postForm("http://...")
        .add("key", "value")
        .from()
        .as(RxLife.as(this)) // 订阅观察者前,加上这句话即可
        .subscribe(s -> {// 成功回调}, throwable -> {// 失败回调});
  // 或者
  RxHttp.postForm("http://...")
        .add("key", "value")
        .from()
        .as(RxLife.asOnMain(this)) //asOnMain 可以在主线程回调观察者
        .subscribe(s -> {// 成功回调}, throwable -> {// 失败回调});

这里的 thisLifecycleOwner对象,它是一个接口,这里我们传入的是 Activity,因为 Activity 实现了 LifecycleOwner 接口。当 Activity/Fragment 销毁时,会将 RxJava 的管道中断,管道中断时,又会将未完成的请求自动关闭。
RxLife 不了解的同学请查看 Android RxLife 一款轻量级别的 RxJava 生命周期管理库 (一),这里不详细讲解。在下面的讲解中,我们均会使用 RxLife

文件上传 / 下载及进度监听

使用RxHttp,可以很优雅的实现文件上传 / 下载及进度的监听,如何优雅?直接上代码

文件上传

  RxHttp.postForm("http://...") // 发送 Form 表单形式的 Post 请求
        .add("key", "value")
        .add("file1", new File("xxx/1.png")) // 添加 file 对象
        .add("file2", new File("xxx/2.png"))
        .from() //from 操作符,是异步操作
        .as(RxLife.asOnMain(this))  // 感知生命周期,并在主线程回调
        .subscribe(s -> {// 成功回调}, throwable -> {// 失败回调});

可以看到,文件上传跟普通的 post 请求其实没啥区别,无非就是在 post 请求的基础上,调用 add 方法添加要上传的文件对象。

文件下载

  // 文件存储路径
  String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
  RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
        .download(destPath) // 注意这里使用 download 操作符,并传入本地路径
        .as(RxLife.asOnMain(this))  // 感知生命周期,并在主线程回调
        .subscribe(s -> {// 下载成功, 回调文件下载路径}, throwable -> {// 下载失败});

下载跟普通请求不同的是,下载使用的是 download 操作符,其它都一样。

文件下载进度监听

// 文件存储路径
  String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
  RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
        .downloadProgress(destPath) // 注: 如果需要监听下载进度,使用 downloadProgress 操作符
        .observeOn(AndroidSchedulers.mainThread())
        .doOnNext(progress -> {
            // 下载进度回调,0-100,仅在进度有更新时才会回调,最多回调 101 次,最后一次回调文件存储路径
            int currentProgress = progress.getProgress(); // 当前进度 0-100
            long currentSize = progress.getCurrentSize(); // 当前已下载的字节大小
            long totalSize = progress.getTotalSize();     // 要下载的总字节大小
            String filePath = progress.getResult(); // 文件存储路径,最后一次回调才有内容})
        .filter(Progress::isCompleted)// 下载完成,才继续往下走
        .map(Progress::getResult) // 到这,说明下载完成,返回下载目标路径
        .as(RxLife.as(this)) // 感知生命周期
        .subscribe(s -> {// s 为 String 类型,这里为文件存储路径
            // 下载完成,处理相关逻辑
        }, throwable -> {// 下载失败,处理相关逻辑});

下载进度的监听我们稍微看一下,首先一点,下载使用 download 操作符,而下载进度监听使用 downloadProgress 操作符,随后,我们使用了 doOnNext 操作符处理进度回调,注意这里是仅当有进度更新时,才会回调,其中的 progress 变量是一个 Progress 类型的对象,我们贴上源码:

public class Progress<T> {
    private int  progress; // 当前进度 0-100
    private long currentSize;// 当前已完成的字节大小
    private long totalSize; // 总字节大小
    private T mResult; //http 返回结果, 上传 / 下载完成时调用
    // 省略 get/set 方法
}

由于进度回调会执行 101 次 (上面注释有解释),而最下面观察者其实是不需要关心这么多事件的,只需要关心最后下载完成的事件,所以使用了filter 操作符过滤事件,只要还未下载完成,就将事件过滤调,不让往下走。最终下载完成后,拿到本地下载路径。

文件上传进度监听

  RxHttp.postForm("http://www.......") // 发送 Form 表单形式的 Post 请求
        .add("file1", new File("xxx/1.png"))
        .add("file2", new File("xxx/2.png"))
        .add("key1", "value1")// 添加参数,非必须
        .add("key2", "value2")// 添加参数,非必须
        .addHeader("versionCode", "100") // 添加请求头, 非必须
        .uploadProgress() // 注: 如果需要监听上传进度,使用 uploadProgress 操作符
        .observeOn(AndroidSchedulers.mainThread()) // 主线程回调
        .doOnNext(progress -> {
            // 上传进度回调,0-100,仅在进度有更新时才会回调, 最多回调 101 次,最后一次回调 Http 执行结果
            int currentProgress = progress.getProgress(); // 当前进度 0-100
            long currentSize = progress.getCurrentSize(); // 当前已上传的字节大小
            long totalSize = progress.getTotalSize();     // 要上传的总字节大小
            String result = progress.getResult(); //Http 执行结果,最后一次回调才有内容})
        .filter(Progress::isCompleted)// 过滤事件,上传完成,才继续往下走
        .map(Progress::getResult) // 到这,说明上传完成,拿到 Http 返回结果并继续往下走
        .as(RxLife.as(this))  // 感知生命周期
        .subscribe(s -> { // s 为 String 类型,由 SimpleParser 类里面的泛型决定的
            // 上传成功,处理相关逻辑
        }, throwable -> {// 上传失败,处理相关逻辑});

上传进度监听使用 downloadProgress 操作符,剩下的操作跟下载进度监听的操作都一样,通过 doOnNext 监听上传进度,然后过滤事件,最终拿到 Http 的返回结果。

数据解析器 Parser

在上面的案例中,观察者拿到数据类型都是 String 类型,然后现实开发中,我们经常需要对数据解析成我们想要的对象,RxHttp考虑到了这一点,现在我们就来看看如何的到我们想要的对象

我们拿淘宝获取 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 -> {// 失败回调});

可以看到,这里我们没有用 from 操作符,而是用了 fromSimpleParser 操作符,并且传入 Response.class,最后观察者拿到的 response 变量就是 Response 类型的对象。怎么样,是不是很简单。RxHttp 为我们提供了一系列的 fromXXX 方法,我们来看一下:

我们可以看到,一些基本类型的封装对象 RxHttp 都为我们封装好了,还有一个 fromListParser 方法,此方法是用来解析集合对象的,一些常见的数据结构,RxHttp 都为我们考虑到了,并封装好了,然后,一些不常见的数据呢?眼尖的你也许发现了,上图中还有一个 <T> Observable<T> from(Parser<T> parser) 方法,它允许我们传入一个自定义的解析器,更多解析器的介绍,请查看 RxHttp 之强大的数据解析功能(二)

最后,附上 RxHttp 一些常用的用法,如下:

  RxHttp.postForm("/service/getIpInfo.php")       // 发送 Form 表单形式的 Post 请求
        .setDomainToUpdate9158IfAbsent()  // 手动设置域名,此方法是通过 @Domain 注解生成的
        .tag("RxHttp.get")          // 为单个请求设置 tag
        .setUrl("http://...")       // 重新设置 url
        .setAssemblyEnabled(false)  // 设置是否添加公共参数,默认为 true
        .cacheControl(CacheControl.FORCE_NETWORK)  // 缓存控制
        .setParam(Param.postForm("http://..."))    // 重新设置一个 Param 对象
        .add(new HashMap<>())   // 通过 Map 添加参数
        .add("int", 1)          // 添加 int 类型参数
        .add("float", 1.28838F) // 添加 float 类型参数
        .add("double", 1.28838) // 添加 double 类型参数
        .add("key1", "value1")  // 添加 String 类型参数
        .add("key2", "value2", false) // 根据最后的 boolean 字段判断是否添加参数 
        .add("file1", new File("xxx/1.png"))            // 添加文件对象
        .addHeader("headerKey1", "headerValue1")        // 添加头部信息
        .addHeader("headerKey2", "headerValue2", false)// 根据最后的 boolean 字段判断是否添加头部信息 
        .fromSimpleParser(String.class)  // 这里返回 Observable<T> 对象  fromXXX 都是异步操作符
        // 感知生命周期,并在主线程回调,当 Activity/Fragment 销毁时,自动关闭未完成的请求
        .as(RxLife.asOnMain(this))  
        .subscribe(s -> {    // 订阅观察者
            // 成功回调
        }, throwable -> {// 失败回调});

小结

到这,RxHttp的基本用法我们就讲解完毕了,可以看到,使用 RxHttp 类一条链就能完成一个完整的 Http 请求,简单一点,就是请求三部曲:

  • 首先,确定请求方式并添加相关参数
  • 然后,确定解析器,指定要解析成的类型
  • 最后,订阅观察者,开始发送请求

以上所有的案例都离不开这 3 个步骤。最后,你会发现,RxHttp除了提供的一系列强大的功能外,在写法上,不管什么请求,都极其的相似,只要通过 RxHttp 类,就能一条链,完成所有的请求,极大的降低了学习成本。

注:要想在项目中生成 RxHttp 类,至少需要使用一次注解类,否则检测不到注解,就无法生成。

如果你觉得 RxHttp+RxLife 好用,请记得给我 star
如果有好的 idea,请留言或者联系我。

更过详情请查看 RxHttp 系列其它文章

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

RxHttp 一条链发送请求之强大的 Param 类(三)

RxHttp 一条链发送请求之注解处理器 Generated API(四)

退出移动版