RxHttp 让你眼前一亮的Http请求框架
# 1、前言
rxhttp在今年4月份一经推出,就受到了广大android 开发者的喜爱,截止本文发表在github上已有[800+star](https://github.com/liujingxing/rxhttp),为此,我自己也建个rxhttp&rxlife 的群(群号:378530627)目前群里也有将近120号人,里面有不少小伙伴提了很多有价值的创意,才使得rxhttp一直坚持走到了现在,在此,感谢大家的喜爱。
这期间,一直有人问我,retrofit不香吗?之前不知道该如何回答这个问题,现在我想说,香!!retrofit无疑是目前综合得分最高的选手,但它也有它的不足。
rxhttp相较于retrofit,功能上,两者均能实现,并无多大差异,更多的差异体现功能的使用上,也就是易用性,如对文件上传/下载/进度监听的操作上,rxhttp用及简的api,可以说碾压retrofit;另外在baseurl、公共参数/请求头、请求加解密等功能上的易用性都要优于retrofit;然而这些,个人觉得都不算什么,个人觉得rxhttp最大的优势在于它近乎为0的上手成本、及简的api以及高扩展性,看完这篇文章,相信你会有同感。
那rxhttp就没有缺点吗?有,那就是它的稳定性目前还不如retrofit,毕竟rxhttp刚出道8个月,且全部是我一个人在维护,当然,并不是说rxhttp不稳定,rxhttp未开源前,在我司的项目已经使用了近2年,接着今年4月份将其开源,至今大大小小已迭代20多个版本,目前用的人也不在少数,可以说很稳定了。
# 2、简介
rxhttp是基于okhttp的二次封装,并与rxjava做到无缝衔接,一条链就能发送任意请求。主要优势如下:
**1. 支持gson、xml、protobuf、fastjson等第三方数据解析工具**
**2. 支持get、post、put、delete等任意请求方式,可自定义请求方式**
**3. 支持在activity/fragment/view/viewmodel/任意类中,自动关闭请求**
**4. 支持统一加解密,且可对单个请求设置是否加解密**
**5. 支持添加公共参数/头部,且可对单个请求设置是否添加公共参数/头部**
**6. 史上最优雅的实现文件上传/下载及进度的监听,且支持断点下载**
**7. 史上最优雅的对错误统一处理,且不打破lambda表达式**
**8. 史上最优雅的处理多个baseurl及动态baseurl**
**9. 史上最优雅的处理网络缓存**
**10. 30秒即可上手,学习成本极低**
**gradle依赖**
```java
implementation 'com.rxjava.rxhttp:rxhttp:1.3.6'
//注解处理器,生成rxhttp类,即可一条链发送请求
annotationprocessor 'com.rxjava.rxhttp:rxhttp-compiler:1.3.6'
//管理rxjava及生命周期,activity/fragment 销毁,自动关闭未完成的请求
implementation 'com.rxjava.rxlife:rxlife:1.1.0'
//非必须 根据自己需求选择converter rxhttp默认内置了gsonconverter
implementation 'com.rxjava.rxhttp:converter-jackson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-fastjson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-protobuf:1.3.6'
implementation 'com.rxjava.rxhttp:converter-simplexml:1.3.6'
```
`注:kotlin用户,请使用kapt替代annotationprocessor`
缓存功能,请查看:[rxhttp 全网http缓存最优解](https://juejin.im/post/5dff3c2de51d45582c27cea6)
# 3、使用
## 3.1、准备工作
rxhttp 要求项目使用java 8,请在 app 的 build.gradle 文件中添加以下代码
```java
compileoptions {
sourcecompatibility javaversion.version_1_8
targetcompatibility javaversion.version_1_8
}
```
此时,再rebuild一下项目(通过rebuild生成rxhttp类),就可以开始rxhttp的入坑之旅
## 3.2、配置默认的baseurl
通过`@defaultdomain`注解配置默认域名,如下:
```java
public class url {
@defaultdomain //设置为默认域名
public static string baseurl = "https://www.wanandroid.com/";
}
```
此步骤是非必须的,这里先介绍`@defaultdomain`注解的用法,更多有关域名的介绍,请查看本文3.6章节----多域名/动态域名
## 3.3、请求三部曲
先来看看如何发送一个最简单的请求,如下
```java
rxhttp.get("http://...") //第一步, 通过get、postxxx、putxxx等方法,确定请求类型
.asstring() //第二步, 通过asxxx系列方法,确定返回数据类型
.subscribe(s -> { //第三步, 订阅回调(此步骤同rxjava订阅观察者)
//请求成功
}, throwable -> {
//请求失败
});
```
是的,不用怀疑,就是这么简单,重要的事情说3遍
**任意请求,任意返回数据类型,皆遵循请求三部曲**
**任意请求,任意返回数据类型,皆遵循请求三部曲**
**任意请求,任意返回数据类型,皆遵循请求三部曲**
到这,你已经掌握了[rxhttp](https://github.com/liujingxing/rxhttp)的精髓,我们只需牢记请求三部曲,使用rxhttp就会得心应手。
### 3.3.1、第一部曲:确定请求类型
rxhttp内部共提供了14个请求方法,如下:
```java
rxhttp.get(string) //get请求 参数拼接在url后面
rxhttp.head(string) //head请求 参数拼接在url后面
rxhttp.postform(string) //post请求 参数以{application/x-www-form-urlencoded}形式提交
rxhttp.postjson(string) //post请求 参数以{application/json; charset=utf-8}形式提交,发送json对象
rxhttp.postjsonarray(string) //post请求 参数以{application/json; charset=utf-8}形式提交,发送json数组
rxhttp.putform(string) //put请求 参数以{application/x-www-form-urlencoded}形式提交
rxhttp.putjson(string) //put请求 参数以{application/json; charset=utf-8}形式提交,发送json对象
rxhttp.putjsonarray(string) //put请求 参数以{application/json; charset=utf-8}形式提交,发送json数组
rxhttp.patchform(string) //patch请求 参数以{application/x-www-form-urlencoded}形式提交
rxhttp.patchjson(string) //patch请求 参数以{application/json; charset=utf-8}形式提交,发送json对象
rxhttp.patchjsonarray(string) //patch请求 参数以{application/json; charset=utf-8}形式提交,发送json数组
rxhttp.deleteform(string) //delete请求 参数以{application/x-www-form-urlencoded}形式提交
rxhttp.deletejson(string) //delete请求 参数以{application/json; charset=utf-8}形式提交,发送json对象
rxhttp.deletejsonarray(string) //delete请求 参数以{application/json; charset=utf-8}形式提交,发送json数组
```
以上14个请求方法你会发现,其实就6个类型,分别对应是get、head、post、put、patch、delete方法,只是其中post、put、patch、delete各有3个方法有不同形式的提交方式,只需要根据自己的需求选择就好。
如以上方法还不能满足你的需求,我们还可以通过`@param`注解自定义请求方法,有关注解的使用,本文后续会详细介绍。
`注:当调用xxxform方法发送请求时,通过setmultiform()方法或者调用addfile(string, file)添加文件时,内部会自动将参数以{multipart/form-data}方式提交`
**添加参数/请求头**
确定请求方法后,我们就可以调用一系列`addxxx()`方法添加参数/请求头,如下:
```java
rxhttp.get("/service/...") //发送get请求
.add("key", "value") //添加参数
.addall(new hashmap<>()) //通过map添加多个参数
.addheader("devicetype", "android") //添加请求头
...
```
任意请求,都可调用以上3个方法添加参数/请求头,当然,在不同的请求方式下,也会有不同的addxxx方法供开发者调用。如下:
```java
//postjson请求方法下会有更多addall等方法可供调用
rxhttp.postjson("/service/...") //发送post json请求
.addall(new jsonobject()) //通过json对象添加多个参数
.addall("{\"height\":180,\"weight\":70}") //通过json字符串添加多个参数
...
//postform请求方法下会有一系列addfile方法可供调用
rxhttp.postform("/service/...") //发送post表单请求
.addfile("file", new file("xxx/1.png")) //添加单个文件
.addfile("filelist", new arraylist<>()) //添加多个文件
...
```
以上只列出了几个常用的addxxx方法,更多方法请下载源码体验。
### 3.3.2、第二部曲:确定返回数据类型
添加好参数/请求头后,正式进入第二部曲,确定返回数据类型,我们通过`asxxx`方法确定返回类型,比如,我们要返回一个student对象,就可以通过`asobject(class<t>)`方法,如下:
```java
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asobject(student.class) //返回student类型
.subscribe(student -> {
//请求成功,这里就能拿到 student对象
}, throwable -> {
//请求失败
});
```
如果要返回student对象列表,则可以通过`aslist(class<t>)`方法,如下:
```java
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.aslist(student.class) //返回list<student>类型
.subscribe(students -> {
//请求成功,这里就能拿到 student对象列表
}, throwable -> {
//请求失败
});
```
**解析`response<t>`类型数据**
然而,现实开发中,大多数人的接口,返回的数据结构都类似下面的这个样子
```java
public class response<t> {
private int code;
private string msg;
private t data;
//这里省略get、set方法
}
```
对于这种数据结构,按传统的写法,每次都要对code做判断,如果有100个请求,就要判断100次,真的会逼死强迫症患者。
rxhttp对于这种情况,给出完美的答案,比如`response<t>`里面的t代表一个student对象,则可以通过`asresponse(class<t>)`方法获取,如下:
```java
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponse(student.class) //返回student类型
.subscribe(student -> {
//请求成功,这里能拿到 student对象
}, throwable -> {
//请求失败
});
```
如果`response<t>`里面的t代表一个`list<student>`列表对象,则可以通过`asresponselist(class<t>)`方法获取,如下
```java
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponselist(student.class) //返回list<student>类型
.subscribe(students -> {
//请求成功,这里能拿到list<student>列表对象
}, throwable -> {
//请求失败
});
```
更多时候,我们的列表数据是分页的,类似下面的数据结构
```java
{
"code": 0,
"msg": "",
"data": {
"totalpage": 0,
"list": []
}
}
```
此时,调用rxhttp的`asresponsepagelist(class<t>)`方法依然可以完美解决,如下:
```java
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponsepagelist(student.class) //返回pagelist<student>类型
.subscribe(pagelist -> {
//请求成功,这里能拿到pagelist<student>列表对象
int totalpage = pagelist.gettotalpage(); //总页数
list<student> students = pagelist.getdata(); //单页列表数据
}, throwable -> {
//请求失败
});
```
到这,估计很多人会问我:
- 你的code在哪里判断的?
- 我的code是100或者其它值才代表正确,怎么改?
- 我的`response<t>`类里面的字段名,跟你的都不一样,怎么该?
- 你这成功的时候直接返回`response<t>`里面的t,那我还要拿到code做其他的判断,执行不同业务逻辑,怎么办?
这里可以先告诉大家,`asresponse(class<t>)`、`asresponselist(class<t>)`、`asresponsepagelist(class<t>)`这3个方法并不是rxhttp内部提供的,而是通过自定义解析器生成,里面的code判断、`response<t>`类都是开发者自定义的,如何自定义解析器,请查看本文5.1章节----自定义parser。
接着回答第4个问题,如何拿到code做其他的业务逻辑判断,很简单,我们只需用`onerror`接口处理错误回调即可,如下:
```java
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponse(student.class) //返回student类型
.subscribe(student -> {
//请求成功,这里能拿到 student对象
}, (onerror) error -> { //注意,这里要用onerror接口,其中error是一个errorinfo对象
//失败回调
//拿到code字段,此时就可以对code做判断,执行不同的业务逻辑
int code = error.geterrorcode();
string errormsg = error.geterrormsg() //拿到msg字段
});
```
`注:上面的onerror接口并非是rxhttp内部提供的,而是自定义的,在demo里可以找到`
以上介绍的5个asxxx方法,可以说基本涵盖80%以上的业务场景,接下来我们看看rxhttp都提供了哪些asxxx方法,如下:![](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d31d028d95?w=1348&h=832&f=png&s=238055)
rxhttp内部共提供了`23`个`asxxx`方法,其中:
- 有7个是返回基本类型的包装类型,如:asinteger、asboolean、aslong等等;
- 还有7个是返回对象类型,如:asstring、asbitmap、aslist、asmap(3个)以及最常用`asobject`方法;
- 剩下9个是`asparser(parser<t>)`、 `asupload`系列方法及`asdownload`系列方法。
duang、duang、duang !!! 划重点,这里我可以告诉大家,其实前面的14个方法,最终都是通过`asparser(parser<t>)`方法实现的,具体实现过程,这里先跳过,后续会详细讲解。
### 3.3.3、第三部曲:订阅回调
这一步就很简单了,在第二部曲中,asxxx方法会返回`observable<t>`对象,没错,就是rxjava内部的`observable<t>`对象,此时我们便可通过`subscribe`系列方法订阅回调,如下:
```java
//不处理任何回调
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponselist(student.class) //返回list<student>类型
.subscribe(); //不订阅任何回调
//仅订阅成功回调
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponselist(student.class) //返回list<student>类型
.subscribe(students -> {
//请求成功,这里能拿到list<student>列表对象
});
//订阅成功与失败回调
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponselist(student.class) //返回list<student>类型
.subscribe(students -> {
//请求成功,这里能拿到list<student>列表对象
}, throwable -> {
//请求失败
});
//等等,省略
```
另外,我们还可以订阅请求开始/结束的回调,如下:
```java
rxhttp.get("/service/...")
.asstring()
.observeon(androidschedulers.mainthread())
.doonsubscribe(disposable -> {
//请求开始,当前在主线程回调
})
.dofinally(() -> {
//请求结束,当前在主线程回调
})
.as(rxlife.as(this)) //感知生命周期
.subscribe(pagelist -> {
//成功回调,当前在主线程回调
}, (onerror) error -> {
//失败回调,当前在主线程回调
});
```
到这,请求三部曲介绍完毕,接着,将介绍其它常用的功能
## 3.4、初始化
```java
//设置debug模式,默认为false,设置为true后,发请求,过滤"rxhttp"能看到请求日志
rxhttp.setdebug(boolean debug)
//非必须,只能初始化一次,第二次将抛出异常
rxhttp.init(okhttpclient okhttpclient)
//或者,调试模式下会有日志输出
rxhttp.init(okhttpclient okhttpclient, boolean debug)
```
此步骤是非必须的,如需要添加拦截器等其他业务需求,则可调用`init`方法进行初始化,不初始化或者传入`null`即代表使用默认okhttpclient对象,建议在application中初始化,默认的okhttpclient对象在httpsender类中可以找到,如下:
```java
private static okhttpclient getdefaultokhttpclient() {
x509trustmanager trustallcert = new x509trustmanagerimpl();
sslsocketfactory sslsocketfactory = new sslsocketfactoryimpl(trustallcert);
return new okhttpclient.builder()
.connecttimeout(10, timeunit.seconds)
.readtimeout(10, timeunit.seconds)
.writetimeout(10, timeunit.seconds)
.sslsocketfactory(sslsocketfactory, trustallcert) //添加信任证书
.hostnameverifier((hostname, session) -> true) //忽略host验证
.build();
}
```
虽然初始化是非必须的,但是建议大家传入自定义的okhttpclient对象,一来,自定义的okhttpclient能最大化满足自身的业务;二来,随着rxhttp版本的升级,默认的okhttpclient可能会发生变化(虽然可能性很小),故建议自定义okhttpclient对象传入rxhttp。
## 3.5、公共参数/请求头
rxhttp支持为所有的请求添加公共参数/请求头,当然,如果你希望某个请求不添加公共参数/请求头,也是支持的,而且非常简单。如下:
```java
rxhttp.setonparamassembly(new function() {
@override
public param apply(param p) { //此方法在子线程中执行,即请求发起线程
method method = p.getmethod();
if (method.isget()) { //可根据请求类型添加不同的参数
} else if (method.ispost()) {
}
return p.add("versionname", "1.0.0")//添加公共参数
.addheader("devicetype", "android"); //添加公共请求头
}
});
```
我们需要调用`rxhttp.setonparamassembly(function)`方法,并传入一个function接口对象,每次发起请求,都会回调该接口。
当然,如果希望某个请求不回调该接口,即不添加公共参数/请求头,则可以调用`setassemblyenabled(boolean)`方法,并传入false即可,如下:
```java
rxhttp.get("/service/...") //get请求
.setassemblyenabled(false) //设置是否添加公共参数/头部,默认为true
.asstring() //返回字符串数据
.subscribe(s -> { //这里的s为string类型
//请求成功
}, throwable -> {
//请求失败
});
```
## 3.6、多域名/动态域名
**3.6.1、多域名**
现实开发中,我们经常会遇到多个域名的情况,其中1个为默认域名,其它为非默认域名,对于这种情况,rxhttp提供了`@defaultdomain()`、`@domain()`这两个注解来标明默认域名和非默认域名,如下:
```java
public class url {
@defaultdomain() //设置为默认域名
public static string baseurl = "https://www.wanandroid.com/"
@domain(name = "baseurlbaidu") //非默认域名,并取别名为baseurlbaidu
public static string baidu = "https://www.baidu.com/";
@domain(name = "baseurlgoogle") //非默认域名,并取别名为baseurlgoogle
public static string google = "https://www.google.com/";
}
```
通过`@domain()`注解标注非默认域名,就会在rxhttp类中生成`setdomaintoxxxifabsent()`方法,其中xxx就是注解中取的别名。
上面我们使用了两个`@domain()`注解,此时(需要rebuild一下项目)就会在rxhttp类中生成`setdomaintobaseurlbaiduifabsent()`、`setdomaintobaseurlgoogleifabsent()`这两方法,此时发请求,我们就可以使用指定的域名,如下:
```java
//使用默认域名,则无需添加任何额外代码
//此时 url = "https://www.wanandroid.com/service/..."
rxhttp.get("/service/...")
.asstring()
.subscribe();
//手动输入域名,此时 url = "https://www.mi.com/service/..."
rxhttp.get("https://www.mi.com/service/...")
.asstring()
.subscribe();
//手动输入域名时,若再次指定域名,则无效
//此时 url = "https://www.mi.com/service/..."
rxhttp.get("https://www.mi.com/service/...")
.setdomaintobaseurlbaiduifabsent() //此时指定baidu域名无效
.asstring()
.subscribe();
//使用谷歌域名,此时 url = "https://www.google.com/service/..."
rxhttp.get("/service/...")
.setdomaintobaseurlgoogleifabsent() //指定使用google域名
.asstring()
.subscribe();
```
通过以上案例,可以知道,rxhttp共有3种指定域名的方式,按优先级排名分别是:手动输入域名 > 指定非默认域名 > 使用默认域名。
**3.6.2、动态域名**
现实开发中,也会有动态域名切换的需求,如域名被封、或者需要根据服务端下发的域名去配置,这对于rxhttp来说简直就是 so easy !!! 我们只需要对baseurl重新赋值,此时发请求便会立即生效,如下:
```java
//此时 url = "https://www.wanandroid.com/service/..."
rxhttp.get("/service/...")
.asstring()
.subscribe();
url.baseurl = "https://www.qq.com"; //动态更改默认域名,改完立即生效,非默认域名同理
//此时 url = "https://www.qq.com/service/..."
rxhttp.get("/service/...")
.asstring()
.subscribe();
```
## 3.7、关闭请求
我们知道,在activity/fragment中发起请求,如果页面销毁时,请求还未结束,就会有内存泄漏的危险,因此,我们需要在页面销毁时,关闭一些还未完成的请求,rxhttp提供了两种关闭请求的方式,分别是自动+手动。
**3.7.1、自动关闭请求**
自动关闭请求,需要引入本人开源的另一个库[rxlife](https://github.com/liujingxing/rxlife),先来看看如何用:
```java
//以下代码均在fragmentactivty/fragment中调用
rxhttp.postform("/service/...")
.asstring()
.as(rxlife.as(this)) //页面销毁、自动关闭请求
.subscribe();
//或者
rxhttp.postform("/service/...")
.asstring()
.as(rxlife.asonmain(this)) //页面销毁、自动关闭请求 并且在主线程回调观察者
.subscribe();
//kotlin用户,请使用life或lifeonmain方法,如下:
rxhttp.postform("/service/...")
.asstring()
.life(this) //页面销毁、自动关闭请求
.subscribe();
//或者
rxhttp.postform("/service/...")
.asstring()
.lifeonmain(this) //页面销毁、自动关闭请求 并且在主线程回调观察者
.subscribe();
```
上面的`this`为`lifecycleowner`接口对象,我们的fragmentactivity/fragment均实现了这个接口,所有我们在fragmentactivity/fragment中可以直接传`this`。
对`rxlife`不了解的同学请查看[rxlife 史上最优雅的管理rxjava生命周期](https://juejin.im/post/5cf3e1235188251c064815f1),这里不详细讲解。
**3.7.2、手动关闭请求**
手动关闭请求,我们只需要在订阅回调的时候拿到disposable对象,通过该对象可以判断请求是否结束,如果没有,就可以关闭请求,如下:
```java
//订阅回调,可以拿到disposable对象
disposable disposable = rxhttp.get("/service/...")
.asstring()
.subscribe(s -> {
//成功回调
}, throwable -> {
//失败回调
});
if (!disposable.isdisposed()) { //判断请求有没有结束
disposable.dispose(); //没有结束,则关闭请求
}
```
## 3.8、文件上传/下载/进度监听
rxhttp可以非常优雅的实现上传/下载及进度的监听,是骡子是马,拉出来溜溜
**3.8.1上传**
通过addfile系列方法添加文件,如下:
```java
rxhttp.postform("/service/...") //发送form表单形式的post请求
.addfile("file1", new file("xxx/1.png")) //添加单个文件
.addfile("filelist", new arraylist<>()) //通过list对象,添加多个文件
.asstring()
.subscribe(s -> {
//上传成功
}, throwable -> {
//上传失败
});
```
通过asupload系列方法监听上传进度,如下:
```java
rxhttp.postform("/service/...") //发送form表单形式的post请求
.addfile("file1", new file("xxx/1.png"))
.addfile("file2", new file("xxx/2.png"))
.asupload(progress -> {
//上传进度回调,0-100,仅在进度有更新时才会回调
int currentprogress = progress.getprogress(); //当前进度 0-100
long currentsize = progress.getcurrentsize(); //当前已上传的字节大小
long totalsize = progress.gettotalsize(); //要上传的总字节大小
}, androidschedulers.mainthread()) //指定回调(进度/成功/失败)线程,不指定,默认在请求所在线程回调
.subscribe(s -> {
//上传成功
}, throwable -> {
//上传失败
});
```
可以看到,跟上传的代码相比,我们仅仅是使用了`asupload(consumer, scheduler)`方法替换`asstring()`方法,第一个参数是进度监听接口,每当进度有更新时,都会回调该接口,第二个参数是指定回调的线程,这里我们指定了在ui线程中回调。
**3.8.2、下载**
下载使用`asdownload(string)`方法,传入本地路径即可
```java
//文件存储路径
string destpath = getexternalcachedir() + "/" + system.currenttimemillis() + ".apk";
rxhttp.get("http://update.9158.com/miaolive/miaolive.apk")
.asdownload(destpath) //注意这里使用asdownload操作符,并传入本地路径
.subscribe(s -> {
//下载成功,回调文件下载路径
}, throwable -> {
//下载失败
});
```
**3.8.3、带进度下载**
带进度下载使用`asdownload(string,consumer,scheduler)`方法
```java
//文件存储路径
string destpath = getexternalcachedir() + "/" + system.currenttimemillis() + ".apk";
rxhttp.get("http://update.9158.com/miaolive/miaolive.apk")
.asdownload(destpath, progress -> {
//下载进度回调,0-100,仅在进度有更新时才会回调,最多回调101次,最后一次回调文件存储路径
int currentprogress = progress.getprogress(); //当前进度 0-100
long currentsize = progress.getcurrentsize(); //当前已下载的字节大小
long totalsize = progress.gettotalsize(); //要下载的总字节大小
}, androidschedulers.mainthread()) //指定主线程回调
.subscribe(s -> {//s为string类型,这里为文件存储路径
//下载完成,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
```
**3.8.4、断点下载**
`断点下载`相较于`下载`,仅需要调用`setrangeheader(long startindex, long endindex)`方法传入开始及结束位置即可(结束位置不传默认为文件末尾),其它没有任何差别
```java
string destpath = getexternalcachedir() + "/" + "miaobo.apk";
long length = new file(destpath).length(); //已下载的文件长度
rxhttp.get("http://update.9158.com/miaolive/miaolive.apk")
.setrangeheader(length) //设置开始下载位置,结束位置默认为文件末尾
.asdownload(destpath)
.subscribe(s -> { //s为string类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
```
**3.8.5、带进度断点下载**
`带进度断点下载`相较于`带进度下载`仅需要调用`setrangeheader`方法传入开始及结束位置即可(结束位置不传默认为文件末尾),其它没有任何差别
```java
string destpath = getexternalcachedir() + "/" + "miaobo.apk";
long length = new file(destpath).length(); //已下载的文件长度
rxhttp.get("http://update.9158.com/miaolive/miaolive.apk")
.setrangeheader(length) //设置开始下载位置,结束位置默认为文件末尾
.asdownload(destpath, progress -> {
//下载进度回调,0-100,仅在进度有更新时才会回调
int currentprogress = progress.getprogress(); //当前进度 0-100
long currentsize = progress.getcurrentsize(); //当前已下载的字节大小
long totalsize = progress.gettotalsize(); //要下载的总字节大小
}, androidschedulers.mainthread()) //指定主线程回调
.subscribe(s -> { //s为string类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
```
`注:`上面带进度断点下载中,返回的进度会从0开始,如果需要衔接上次下载的进度,则调用`asdownload(string,long,consumer,scheduler)`方法传入上次已经下载好的长度(第二个参数),如下:
```java
string destpath = getexternalcachedir() + "/" + "miaobo.apk";
long length = new file(destpath).length(); //已下载的文件长度
rxhttp.get("http://update.9158.com/miaolive/miaolive.apk")
.setrangeheader(length) //设置开始下载位置,结束位置默认为文件末尾
.asdownload(destpath, length, progress -> {
//下载进度回调,0-100,仅在进度有更新时才会回调
int currentprogress = progress.getprogress(); //当前进度 0-100
long currentsize = progress.getcurrentsize(); //当前已下载的字节大小
long totalsize = progress.gettotalsize(); //要下载的总字节大小
}, androidschedulers.mainthread()) //指定主线程回调
.subscribe(s -> { //s为string类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
```
## 3.9、超时设置
**3.9.1、设置全局超时**
rxhttp内部默认的读、写、连接超时时间均为10s,如需修改,请自定义okhttpclient对象,如下:
```java
//设置读、写、连接超时时间为15s
okhttpclient client = new okhttpclient.builder()
.connecttimeout(15, timeunit.seconds)
.readtimeout(15, timeunit.seconds)
.writetimeout(15, timeunit.seconds)
.build();
rxhttp.init(client);
```
**3.9.2、为单个请求设置超时**
为单个请求设置超时,使用的是rxjava的`timeout(long timeout, timeunit timeunit)`方法,如下:
```java
rxhttp.get("/service/...")
.asstring()
.timeout(5, timeunit.seconds)//设置总超时时间为5s
.as(rxlife.asonmain(this)) //感知生命周期,并在主线程回调
.subscribe(pagelist -> {
//成功回调
}, (onerror) error -> {
//失败回调
});
```
**注:这里设置的总超时时间要小于全局读、写、连接超时时间之和,否则无效**
## 3.10、设置converter
**3.10.1、设置全局converter**
```java
iconverter converter = fastjsonconverter.create();
rxhttp.setconverter(converter)
```
**3.10.2、为请求设置单独的converter**
首先需要在任意public类中通过@converter注解声明converter,如下:
```java
public class rxhttpmanager {
@converter(name = "xmlconverter") //指定converter名称
public static iconverter xmlconverter = xmlconverter.create();
}
```
然后,rebuild 一下项目,就在自动在rxhttp类中生成`setxmlconverter()`方法,随后就可以调用此方法为单个请求指定converter,如下:
```java
rxhttp.get("/service/...")
.setxmlconverter() //指定使用xmlconverter,不指定,则使用全局的converter
.asobject(newsdataxml.class)
.as(rxlife.asonmain(this)) //感知生命周期,并在主线程回调
.subscribe(dataxml -> {
//成功回调
}, (onerror) error -> {
//失败回调
});
```
## 3.11、请求加解密
**3.11.1、加密**
请求加密,需要自定义param,非常简单,详情请查看本文5.2章节----自定义param
**3.11.2、解密**
有些时候,请求会返回一大串的密文,此时就需要将密文转化为明文,直接来看代码,如下:
```java
//设置数据解密/解码器
rxhttp.setresultdecoder(new function<string, string>() {
//每次请求成功,都会回调这里,并传入请求返回的密文
@override
public string apply(string s) throws exception {
string plaintext = decode(s); //将密文解密成明文,解密逻辑自己实现
return plaintext; //返回明文
}
});
```
很简单,通过`rxhttp.setresultdecoder(function<string, string>)`静态方法,传入一个接口对象,此接口会在每次请求成功的时候被回调,并传入请求返回的密文,只需要将密文解密后返回即可。
然而,有些请求是不需求解密的,此时就可以调用`setdecoderenabled(boolean)`方法,并传入false即可,如下:
```java
rxhttp.get("/service/...")
.setdecoderenabled(false) //设置本次请求不需要解密,默认为true
.asstring()
.subscribe(pagelist -> {
//成功回调
}, (onerror) error -> {
//失败回调
});
```
## 3.12、指定请求/回调线程
rxhttp默认在io线程执行请求,也默认在io线程回调,即默认在同一io线程执行请求并回调,当然,我们也可以指定请求/回调所在线程。
**3.12.1、指定请求所在线程**
我们可以调用一些列subscribexxx方法指定请求所在线程,如下:
```java
//指定请求所在线程,需要在第二部曲前任意位置调用,第二部曲后调用无效
rxhttp.get("/service/...")
.subscribeoncurrent() //指定在当前线程执行请求,即同步执行,
.asstring()
.subscribe();
//其它subscribexxx方法
subscribeonio() //rxhttp默认的请求线程
subscribeonsingle()
subscribeonnewthread()
subscribeoncomputation()
subscribeontrampoline()
subscribeon(scheduler) //自定义请求线程
```
以上使用的皆是rxjava的线程调度器,不熟悉的请自行查阅相关资料,这里不做详细介绍。
**3.12.2、指定回调所在线程**
指定回调所在线程,依然使用rxjava的线程调度器,如下:
```java
//指定回调所在线程,需要在第二部曲后调用
rxhttp.get("/service/...")
.asstring()
.observeon(androidschedulers.mainthread()) //指定在主线程回调
.subscribe(s -> { //s为string类型,主线程回调
//成功回调
}, throwable -> {
//失败回调
});
```
## 3.13、 retrofit用户
时常会有童鞋问我,我是retrofit用户,喜欢把接口写在一个类里,然后可以直接调用,rxhttp如何实现?其实,这个问题压根就不是问题,在介绍第二部曲的时候,我们知道,使用asxxx方法后,就会返回`observable<t>`对象,因此,我们就可以这样实现:
```java
public class httpwrapper {
public static observable<list<student>> getstudent(int page) {
return rxhttp.get("/service/...")
.add("page", page)
.aslist(student.class);
}
}
//随后在其它地方就可以直接调用
httpwrapper.getstudent(1)
.as(rxlife.asonmain(this)) //主线程回调,并在页面销毁自动关闭请求(如果还未关闭的话)
.subscribe(students -> { //学生列表
//成功回调
}, throwable -> {
//失败回调
});
```
很简单,封装的时候返回`observable<t>`对象即可。
还有的同学问,我们获取列表的接口,页码是和url拼接在一起的,retrofit可以通过占位符,那rxhttp又如何实现?简单,如下:
```java
public class httpwrapper {
//单个占位符
public static observable<student> getstudent(int page) {
return rxhttp.get("/service/%d/...", page) //使用标准的占位符协议
.asobject(student.class);
}
//多个占位符
public static observable<student> getstudent(int page, int count) {
return rxhttp.get("/service/%1$d/%2$d/...", page, count) //使用标准的占位符协议
.asobject(student.class);
}
}
```
这一点跟retrofit不同,retrofit是通过注解指定占位符的,而rxhttp是使用标准的占位符,我们只需要在url中声明占位符,随后在传入url的后面,带上对应的参数即可。
# 4、原理剖析
## 4.1、工作流程
在rxhttp有4个重要的角色,分别是:
- param:rxhttp类中所有添加的参数/请求头/文件都交由它处理,它最终目的就是为了构建一个request对象
- httpsender :它负责从param对象中拿到request对象,从而执行请求,最终返回response对象
- parser:它负责将httpsender返回的response对象,解析成我们期望的实体类对象,也就是泛型t
- rxhttp:它像一个管家,指挥前面3个角色做事情,当然,它也有自己的事情要做,比如:请求线程的调度,baseurl的处理、允许开发者自定义api等等
为此,我画了一个流程图,可以直观的了解到rxhttp的大致工作流程
![在这里插入图片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d31d469969?w=1308&h=712&f=png&s=143004)
我想应该很好理解,rxhttp要做的事情,就是把添加的参数/请求头等全部丢给param处理,自己啥事也不敢;随后将param交给httpsender,让它去执行请求,执行完毕,返回response对象;接着又将response对象丢给parser去做数据解析工作,并返回实体类对象t;最后,将t通过回调传给开发者,到此,一个请求就处理完成。
## 4.2、param
首先,附上一张param类的继承关系图
![](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d31ed76c6f?w=1180&h=896&f=png&s=78084)
下面将从上往下对上图中的类做个简单的介绍:
- iheaders:接口类,里面定义了一系列addheader方法
- iparam:接口类,里面定义了add(string,object)、addall(map)等方法,
- irequest:接口类,里面定义了一系列getxxx方法,通过这些方法最终构建一个request对象
- param:接口类,是一个空接口,继承了前面3个接口,里面有一系列静态方法可以获取到param的具体实现类
- abstractparam:param接口的抽象实现类,实现了param接口的所有方法
- ifile:接口类,里面定义了一系列addfile方法
- iuploadlengthlimit:接口类,里面就定义了一个checklength()方法,用于限制文件上传大小
- nobodyparam:param的具体实现类,get、head请求就是通过该类去实现的
- jsonparam:param的具体实现类,调用rxhttp.xxxjson(string)请求方法时,内部就是通过该类去实现的
- jsonarrayparam:param的具体实现类,调用rxhttp.xxxjsonarray(string)请求方法时,内部就是通过该类去实现的
- formparam:param的具体实现类,同时又实现了ifile、iuploadlengthlimit两个接口,调用rxhttp.xxxform(string)请求方法时,内部就是通过该类去实现的
## 4.3、httpsender
httpsender可以把它理解为请求发送者,里面声明okhttpclient对象和一系列静态方法,我们来简单看下:
```java
public final class httpsender {
private static okhttpclient mokhttpclient; //只能初始化一次,第二次将抛出异常
//处理化okhttpclient对象
public static void init(okhttpclient okhttpclient) {
if (mokhttpclient != null)
throw new illegalargumentexception("okhttpclient can only be initialized once");
mokhttpclient = okhttpclient;
}
//通过param对象同步执行一个请求
public static response execute(@nonnull param param) throws ioexception {
return newcall(param).execute();
}
static call newcall(param param) throws ioexception {
return newcall(getokhttpclient(), param);
}
//所有的请求,最终都会调此方法拿到call对象,然后执行请求
static call newcall(okhttpclient client, param param) throws ioexception {
param = rxhttpplugins.onparamassembly(param);
if (param instanceof iuploadlengthlimit) {
((iuploadlengthlimit) param).checklength();
}
request request = param.buildrequest(); //通过param拿到request对象
logutil.log(request);
return client.newcall(request);
}
//省略了部分方法
}
```
这里我们重点看下`newcall(okhttpclient, param)`方法,该方法第一行就是为param添加公共参数;然后判断param有没有实现iuploadlengthlimit接口,有的话,检查文件上传大小,超出大小,则抛出io异常;接着就是通过param拿到request对象;最后拿到call对象,就可以发送一个请求。
## 4.4、parser
先看下parser继承结构图
![在这里插入图片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d320d64d39?w=1728&h=816&f=png&s=76503)
这里对上图中的类做个简单的介绍
- parser:接口类,里面定义了一个`t onparse(response)`方法,输入response对象,输出实体类对象t
- abstractparser:抽象类,里面没有任何具体实现,主要作用是在构造方法内获取泛型类型
- simpleparser:是一个万能的解析器,可以解析任意数据结构,rxhttp内置的大部分asxxx方法,内部就是通过该解析器实现的
- listparser:是一个列表解析器,可以解析任意列表数据,内置`aslist(class<t>)`方法,就是通过该解析器实现的
- mapparser:是一个map解析器,可以解析任意map数据类型,内置的asmap系列方法,就是通过该解析器实现的
- bitmapparser:是一个bitmap解析器,通过该解析器可以获得一个bitmap对象,asbitmap()方法内部就是通过该解析器实现的
- downloadparser:文件下载解析器,用于文件下载,内置的一系列asdownload方法就是通过该解析器实现的
# 5、扩展
## 5.1、自定义parser
前面第二部曲中,我们介绍了一系列asxxx方法,通过该系列方法可以很方便的指定数据返回类型,特别是自定义的`asresponse(class<t>)`、`asresponselist(class<t>)`、`asresponsepagelist(class<t>)`这3个方法,将`reponse<t>`类型数据,处理的简直不要太完美,下面我们就来看看如何自定义parser。
源码永远是最好的学习方式,在学习自定义parser前,我们不妨先看看内置的parser是如何实现的
**simpleparser**
```java
public class simpleparser<t> extends abstractparser<t> {
//省略构造方法
@override
public t onparse(response response) throws ioexception {
return convert(response, mtype);
}
}
```
可以看到,simpleparser除了构造方法,就剩一个onparser方法,该方法是在parser接口中定义的,再来看看具体的实现`convert(response, type)`,这个方法也是在parser接口中定义的,并且有默认的实现,如下:
```java
public interface parser<t> {
//输入response 输出t
t onparse(@nonnull response response) throws ioexception;
//对http返回的结果,转换成我们期望的实体类对象
default <r> r convert(response response, type type) throws ioexception {
responsebody body = exceptionhelper.throwiffatal(response); //这里内部会判断code<200||code>=300 时,抛出异常
boolean onresultdecoder = isonresultdecoder(response); //是否需要对返回的数据进行解密
logutil.log(response, onresultdecoder, null);
iconverter converter = getconverter(response); //取出转换器
return converter.convert(body, type, onresultdecoder); //对数据进场转换
}
//省略若干方法
}
```
可以看到,非常的简单,输入response对象和泛型类型type,内部就通过iconverter接口转换为我们期望的实体类对象并返回。
到这,我想大家应该就多少有点明白了,自定义parser,无非就是继承abstractparser,然后实现onparser方法即可,那我们来验证一下,我们来看看内置listparser是不是这样实现的,如下:
```java
public class listparser<t> extends abstractparser<list<t>> {
//省略构造方法
@override
public list<t> onparse(response response) throws ioexception {
final type type = parameterizedtypeimpl.get(list.class, mtype); //拿到泛型类型
return convert(response, type);
}
}
```
可以看到,跟simpleparser解析器几乎是一样的实现,不同是的,这里将我们输入的泛型t与list组拼为一个新的泛型类型,最终返回`list<t>`对象。
现在,我们就可以来自定义parser了,先来自定义responseparser,用来处理`response<t>`数据类型,先看看数据结构:
```java
public class response<t> {
private int code;
private string msg;
private t data;
//这里省略get、set方法
}
```
自定义responseparser代码如下:
```java
//通过@parser注解,为解析器取别名为response,此时就会在rxhttp类生成asresponse(class<t>)方法
@parser(name = "response")
public class responseparser<t> extends abstractparser<t> {
//省略构造方法
@override
public t onparse(okhttp3.response response) throws ioexception {
final type type = parameterizedtypeimpl.get(response.class, mtype); //获取泛型类型
response<t> data = convert(response, type);
t t = data.getdata(); //获取data字段
if (data.getcode() != 0 || t == null) {//这里假设code不等于0,代表数据不正确,抛出异常
throw new parseexception(string.valueof(data.getcode()), data.getmsg(), response);
}
return t;
}
}
```
可以看到,非常的简单,首先将我们输入泛型和自定义的`response<t>`类组拼成新的泛型类型,随后通过`convert(response, type)`方法得到`response<t>`对象,接着又对code及t做了判断,如果不正确就抛出异常,最后返回t。
估计这里有人会问,我怎么用这个解析器呢?相信不少小伙伴以及发现了,我们在responseparser类名上面用了`@parser`注解,只要用了该注解,就会在rxhttp自动生成`asxxx(class<t>)`方法,其中xxx就是我们在`@parser`注解中为解析器取的别名,这里取别名为response,所以便会在rxhttp类中自动生成`asresponse(class<t>)`方法,如下:
```java
public <t> observable<t> asresponse(class<t> type) {
return asparser(new responseparser(type));
}
```
可以看到,该方法内部又调用了`asparser(parser<t>)`方法,并传入了responseparser,因此,我们有两种方式使用自定义的responseparser,如下:
```java
//第一种方式,使用@parser注解生成的asresponse方法
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asresponse(student.class) //返回student类型
.subscribe(student -> {
//请求成功,这里能拿到 student对象
}, throwable -> {
//请求失败
});
//第二种方式,直接使用asparser(parser<t>)方法
rxhttp.postform("/service/...") //发送post表单请求
.add("key", "value") //添加参数,可调用多次
.asparser(new responseparser<student>(){}) //返回student类型
.subscribe(student -> {
//请求成功,这里能拿到 student对象
}, throwable -> {
//请求失败
});
```
以上两种方式,除了写法上的区别,其它都一样,用哪种,看个人喜好,但还是建议使用第一种方式,不仅写法简单,也降低了耦合。
这里最后再贴上responselistparser、responsepagelistparser的源码,原理都是一样的,代码实现也差不多,就不再详解
**responselistparser**
```java
@parser(name = "responselist")
public class responselistparser<t> extends abstractparser<list<t>> {
//省略构造方法
@override
public list<t> onparse(okhttp3.response response) throws ioexception {
final type type = parameterizedtypeimpl.get(response.class, list.class, mtype); //获取泛型类型
response<list<t>> data = convert(response, type);
list<t> list = data.getdata(); //获取data字段
if (data.getcode() != 0 || list == null) { //code不等于0,说明数据不正确,抛出异常
throw new parseexception(string.valueof(data.getcode()), data.getmsg(), response);
}
return list;
}
}
```
**responsepagelistparser**
```java
@parser(name = "responsepagelist")
public class responsepagelistparser<t> extends abstractparser<pagelist<t>> {
//省略构造方法
@override
public pagelist<t> onparse(okhttp3.response response) throws ioexception {
final type type = parameterizedtypeimpl.get(response.class, pagelist.class, mtype); //获取泛型类型
response<pagelist<t>> data = convert(response, type);
pagelist<t> pagelist = data.getdata(); //获取data字段
if (data.getcode() != 0 || pagelist == null) { //code不等于0,说明数据不正确,抛出异常
throw new parseexception(string.valueof(data.getcode()), data.getmsg(), response);
}
return pagelist;
}
}
```
## 5.2、自定义param
自定义param,想较于自定义parser,要更加的简单,我们只需根据自己的需求,继承nobodyparam、formparam、jsonparam等,增加或者重写方法即可,比如我们有以下3种情况,需要自定义param,如下:
- postform请求,需要将所有添加的参数,拼接在一起,随后加密,最后将加密的字符串添加到请求头中
- postjson请求,需要将所有的参数,也就是json字符串加密后再发送出去
- formparam里面的api不够用,我要自定义api
#### 5.2.1、postform请求加密
这种情况,我们需要继承formparam,并重写getrequestbody()方法,如下:
```java
@param(methodname = "postencryptform")
public class postencryptformparam extends formparam {
public postencryptformparam(string url) {
super(url, method.post); //method.post代表post请求
}
@override
public requestbody getrequestbody() {
//这里拿到你添加的所有参数
list<keyvaluepair> keyvaluepairs = getkeyvaluepairs();
string encryptstr = "加密后的字符串"; //根据上面拿到的参数,自行实现加密逻辑
addheader("encryptstr", encryptstr);
return super.getrequestbody();
}
}
```
#### 5.2.2、postjson请求加密
这种情况,我们需要继承jsonparam,也重写getrequestbody()方法,如下:
```java
@param(methodname = "postencryptjson")
public class postencryptjsonparam extends jsonparam {
public postencryptformparam(string url) {
super(url, method.post);
}
@override
public requestbody getrequestbody() {
//这里拿到你添加的所有参数
map<string, object> params = getparams();
string encryptstr = "加密后的字符串"; //根据上面拿到的参数,自行实现解密逻辑
return requestbody.create(media_type_json, encryptstr); //发送加密后的字符串
}
}
```
#### 5.2.3、自定义api
我们继承formparam,并新增两个test方法`,如下:
```java
@param(methodname = "posttestform")
public class posttestformparam extends formparam {
public postencryptformparam(string url) {
super(url, method.post);
}
public postencryptformparam test(long a, float b) {
//这里的业务逻辑自行实现
return this;
}
public postencryptformparam test1(string s, double b) {
//这里的业务逻辑自行实现
return this;
}
}
```
#### 5.2.4、使用自定义的param
同样的问题,我们怎么用这3个自定义的param呢?我想大多数人在类名前发现类`@param`注解,并为param取了别名。那这个又有什么作用呢?
答案揭晓,只要在自定的param上使用了`@param`注解,并取了别名,就会在rxhttp类自动生成一个跟别名一样的方法,在上面我们自定义了3个param,并分别取别名为postencryptform、postencryptjson、posttestform,此时就会在rxhttp类中生成`postencryptform(string)`、`postencryptjsonstring)`、`posttestform(string)`这3个方法,我们在rxhttp这个类中来看下:
```java
public static rxhttp$postencryptformparam postencryptform(string url) {
return new rxhttp$postencryptformparam(new postencryptformparam(url));
}
public static rxhttp$postencryptjsonparam postencryptjson(string url) {
return new rxhttp$postencryptjsonparam(new postencryptjsonparam(url));
}
public static rxhttp$posttestformparam posttestform(string url) {
return new rxhttp$posttestformparam(new posttestformparam(url));
}
```
发请求时,只需要调用对应的方法就好,如:
```java
//发送加密的postform请求
rxhttp.postencryptform("/service/...")
.add("key", "value") //添加参数,可调用多次
.asstring() //返回string类型
.subscribe(s-> {
//请求成功
}, throwable -> {
//请求失败
});
//发送加密的postjson请求
rxhttp.postencryptjson("/service/...")
.add("key", "value") //添加参数,可调用多次
.asstring() //返回string类型
.subscribe(s-> {
//请求成功
}, throwable -> {
//请求失败
});
```
那我自定义的api如何调用呢,so easy!!!!,选择对应的请求方法后,就可以直接调用,如下:
```java
//发送加密的postjson请求
rxhttp.posttestjson("/service/...")
.test(100l, 99.99f) //调用自定义的api
.test1("testkey", 88.88d) //调用自定义的api
.add("key", "value") //添加参数,可调用多次
.asstring() //返回string类型
.subscribe(s-> {
//请求成功
}, throwable -> {
//请求失败
});
```
## 5.3、自定义converter
rxhttp内部默认使用来gsonconverter,并且额外提供了4个converter,如下:
```java
//非必须 根据自己需求选择converter rxhttp默认内置了gsonconverter
implementation 'com.rxjava.rxhttp:converter-jackson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-fastjson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-protobuf:1.3.6'
implementation 'com.rxjava.rxhttp:converter-simplexml:1.3.6'
```
#### 5.3.1、自定义testconverter
即使这样,rxhttp也无法保证满足所有的业务需求,为此,我们可以选择自定义converter,自定义converter需要继承iconverter接口,如下:
```java
public class testconverter implements iconverter {
/**
* 请求成功后会被回调
* @param body responsebody
* @param type 泛型类型
* @param onresultdecoder 是否需要对结果进行解码/解密
*/
@override
public <t> t convert(responsebody body, type type, boolean onresultdecoder) throws ioexception {
//自行实现相关逻辑
return null;
}
/**
* json请求前会被回调,需要自行根据泛型t创建requestbody对象,并返回
*/
@override
public <t> requestbody convert(t value) throws ioexception {
//自行实现相关逻辑
return null;
}
}
```
以上两个convert方法根据自身业务需求自行实现,可以参考rxhttp提供fastjsonconverter、simplexmlconverter等converter
#### 5.3.2、怎么用converter
请查看本文3.10章节----设置converter
# 6、小技巧
在这教大家一个小技巧,由于使用rxhttp发送请求都遵循请求三部曲,故我们可以在android studio 设置代码模版,如下![在这里插入图片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d320e10621?w=2420&h=1106&f=jpeg&s=317956)
如图设置好后,写代码时,输入rp,就会自动生成模版,如下:
![在这里插入图片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d321027db0?w=380&h=361&f=gif&s=54893)
# 7、小结
到这,rxhttp常用功能介绍完毕,你会发现,一切都是那么的美好,无论你是get、post、加密请求、自定义解析器,还是文件上传/下载/进度监听等等,皆遵循请求三部曲。特别是对`response<t>`类型数据处理,可以说是天衣无缝,我们无需每次都判断code,直接就可以拿到t,简直了。。。
最后,喜欢的,请给本文点个赞,如果可以,还请给个[star](https://github.com/liujingxing/rxhttp),创作不易,感激不尽。