RxHttp在今年4月份一经推出,就受到了广大Android 开发者的喜好,截止本文发表在github上已有1100+star,为此,我本身也建个RxHttp&RxLife 的群(群号:378530627)目前群里也有将近120号人,里面有很多小伙伴提了不少有价值的创意,才使得RxHttp一直坚持走到了如今,在此,感谢你们的喜好。java
这期间,一直有人问我,retrofit不香吗?以前不知道该如何回答这个问题,如今我想说,香!!retrofit无疑是目前综合得分最高的选手,但它也有它的不足。android
RxHttp相较于retrofit,功能上,二者均能实现,并没有多大差别,更多的差别体现功能的使用上,也就是易用性,如对文件上传/下载/进度监听的操做上,RxHttp用及简的API,能够说碾压retrofit;另外在baseUrl、公共参数/请求头、请求加解密等功能上的易用性都要优于retrofit;然而这些,我的以为都不算什么,我的以为RxHttp最大的优点在于它近乎为0的上手成本、极简的API以及高扩展性,看完这篇文章,相信你会有同感。git
那RxHttp就没有缺点吗?有,那就是它的稳定性目前还不如retrofit,毕竟RxHttp刚出道8个月,且所有是我一我的在维护,固然,并非说RxHttp不稳定,RxHttp未开源前,在我司的项目已经使用了近2年,接着今年4月份将其开源,至今大大小小已迭代20多个版本,目前用的人也不在少数,能够说很稳定了。github
RxHttp是基于OkHttp的二次封装,并与RxJava作到无缝衔接,一条链就能发送任意请求。主要优点以下:json
1. 支持Gson、Xml、ProtoBuf、FastJson等第三方数据解析工具数组
2. 支持Get、Post、Put、Delete等任意请求方式,可自定义请求方式缓存
3. 支持在Activity/Fragment/View/ViewModel/任意类中,自动关闭请求网络
4. 支持统一加解密,且可对单个请求设置是否加解密session
5. 支持添加公共参数/头部,且可对单个请求设置是否添加公共参数/头部数据结构
6. 史上最优雅的实现文件上传/下载及进度的监听,且支持断点下载
7. 史上最优雅的对错误统一处理,且不打破Lambda表达式
8. 史上最优雅的处理多个BaseUrl及动态BaseUrl
9. 史上最优雅的处理网络缓存
10. 30秒便可上手,学习成本极低
gradle依赖
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缓存最优解
RxHttp 要求项目使用Java 8,请在 app 的 build.gradle 文件中添加如下代码
compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }
此时,再Rebuild一下项目(经过Rebuild生成RxHttp类),就能够开始RxHttp的入坑之旅
经过@DefaultDomain
注解配置默认域名,以下:
public class Url { @DefaultDomain //设置为默认域名 public static String baseUrl = "https://www.wanandroid.com/"; }
此步骤是非必须的,这里先介绍@DefaultDomain
注解的用法,更多有关域名的介绍,请查看本文3.6章节----多域名/动态域名
先来看看如何发送一个最简单的请求,以下
RxHttp.get("http://...") //第一步, 经过get、postXxx、putXxx等方法,肯定请求类型 .asString() //第二步, 经过asXxx系列方法,肯定返回数据类型 .subscribe(s -> { //第三步, 订阅回调(此步骤同RxJava订阅观察者) //请求成功 }, throwable -> { //请求失败 });
是的,不用怀疑,就是这么简单,重要的事情说3遍
任意请求,任意返回数据类型,皆遵循请求三部曲
任意请求,任意返回数据类型,皆遵循请求三部曲
任意请求,任意返回数据类型,皆遵循请求三部曲
到这,你已经掌握了RxHttp的精髓,咱们只需牢记请求三部曲,使用RxHttp就会驾轻就熟。
RxHttp内部共提供了14个请求方法,以下:
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()
方法添加参数/请求头,以下:
RxHttp.get("/service/...") //发送get请求 .add("key", "value") //添加参数 .addAll(new HashMap<>()) //经过Map添加多个参数 .addHeader("deviceType", "android") //添加请求头 ...
任意请求,均可调用以上3个方法添加参数/请求头,固然,在不一样的请求方式下,也会有不一样的addXxx方法供开发者调用。以下:
//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方法,更多方法请下载源码体验。
添加好参数/请求头后,正式进入第二部曲,肯定返回数据类型,咱们经过asXxx
方法肯定返回类型,好比,咱们要返回一个Student对象,就能够经过asObject(Class<T>)
方法,以下:
RxHttp.postForm("/service/...") //发送post表单请求 .add("key", "value") //添加参数,可调用屡次 .asObject(Student.class) //返回Student类型 .subscribe(student -> { //请求成功,这里就能拿到 Student对象 }, throwable -> { //请求失败 });
若是要返回Student对象列表,则能够经过asList(Class<T>)
方法,以下:
RxHttp.postForm("/service/...") //发送post表单请求 .add("key", "value") //添加参数,可调用屡次 .asList(Student.class) //返回List<Student>类型 .subscribe(students -> { //请求成功,这里就能拿到 Student对象列表 }, throwable -> { //请求失败 });
解析Response<T>
类型数据
然而,现实开发中,大多数人的接口,返回的数据结构都相似下面的这个样子
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>)
方法获取,以下:
RxHttp.postForm("/service/...") //发送post表单请求 .add("key", "value") //添加参数,可调用屡次 .asResponse(Student.class) //返回Student类型 .subscribe(student -> { //请求成功,这里能拿到 Student对象 }, throwable -> { //请求失败 });
若是Response<T>
里面的T表明一个List<Student>
列表对象,则能够经过asResponseList(Class<T>)
方法获取,以下
RxHttp.postForm("/service/...") //发送post表单请求 .add("key", "value") //添加参数,可调用屡次 .asResponseList(Student.class) //返回List<Student>类型 .subscribe(students -> { //请求成功,这里能拿到List<Student>列表对象 }, throwable -> { //请求失败 });
更多时候,咱们的列表数据是分页的,相似下面的数据结构
{ "code": 0, "msg": "", "data": { "totalPage": 0, "list": [] } }
此时,调用RxHttp的asResponsePageList(Class<T>)
方法依然能够完美解决,以下:
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 -> { //请求失败 });
到这,估计不少人会问我:
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
接口处理错误回调便可,以下:
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方法,以下:
RxHttp内部共提供了23
个asXXX
方法,其中:
asObject
方法;asParser(Parser<T>)
、 asUpload
系列方法及asDownload
系列方法。duang、duang、duang !!! 划重点,这里我能够告诉你们,其实前面的14个方法,最终都是经过asParser(Parser<T>)
方法实现的,具体实现过程,这里先跳过,后续会详细讲解。
这一步就很简单了,在第二部曲中,asXxx方法会返回Observable<T>
对象,没错,就是RxJava内部的Observable<T>
对象,此时咱们即可经过subscribe
系列方法订阅回调,以下:
//不处理任何回调 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 -> { //请求失败 }); //等等,省略
另外,咱们还能够订阅请求开始/结束的回调,以下:
RxHttp.get("/service/...") .asString() .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe(disposable -> { //请求开始,当前在主线程回调 }) .doFinally(() -> { //请求结束,当前在主线程回调 }) .as(RxLife.as(this)) //感知生命周期 .subscribe(pageList -> { //成功回调,当前在主线程回调 }, (OnError) error -> { //失败回调,当前在主线程回调 });
到这,请求三部曲介绍完毕,接着,将介绍其它经常使用的功能
//设置debug模式,默认为false,设置为true后,发请求,过滤"RxHttp"能看到请求日志 RxHttp.setDebug(boolean debug) //非必须,只能初始化一次,第二次将抛出异常 RxHttp.init(OkHttpClient okHttpClient) //或者,调试模式下会有日志输出 RxHttp.init(OkHttpClient okHttpClient, boolean debug)
此步骤是非必须的,如须要添加拦截器等其余业务需求,则可调用init
方法进行初始化,不初始化或者传入null
即表明使用默认OkHttpClient对象,建议在Application中初始化,默认的OkHttpClient对象在HttpSender类中能够找到,以下:
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。
RxHttp支持为全部的请求添加公共参数/请求头,固然,若是你但愿某个请求不添加公共参数/请求头,也是支持的,并且很是简单。以下:
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便可,以下:
RxHttp.get("/service/...") //get请求 .setAssemblyEnabled(false) //设置是否添加公共参数/头部,默认为true .asString() //返回字符串数据 .subscribe(s -> { //这里的s为String类型 //请求成功 }, throwable -> { //请求失败 });
3.6.一、多域名
现实开发中,咱们常常会遇到多个域名的状况,其中1个为默认域名,其它为非默认域名,对于这种状况,RxHttp提供了@DefaultDomain()
、@Domain()
这两个注解来标明默认域名和非默认域名,以下:
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()
这两方法,此时发请求,咱们就可使用指定的域名,以下:
//使用默认域名,则无需添加任何额外代码 //此时 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.二、动态域名
现实开发中,也会有动态域名切换的需求,如域名被封、或者须要根据服务端下发的域名去配置,这对于RxHttp来讲简直就是 so easy !!! 咱们只须要对BaseUrl从新赋值,此时发请求便会当即生效,以下:
//此时 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();
咱们知道,在Activity/Fragment中发起请求,若是页面销毁时,请求还未结束,就会有内存泄漏的危险,所以,咱们须要在页面销毁时,关闭一些还未完成的请求,RxHttp提供了两种关闭请求的方式,分别是自动+手动。
3.7.一、自动关闭请求
自动关闭请求,须要引入本人开源的另外一个库RxLife,先来看看如何用:
//如下代码均在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生命周期,这里不详细讲解。
3.7.二、手动关闭请求
手动关闭请求,咱们只须要在订阅回调的时候拿到Disposable对象,经过该对象能够判断请求是否结束,若是没有,就能够关闭请求,以下:
//订阅回调,能够拿到Disposable对象 Disposable disposable = RxHttp.get("/service/...") .asString() .subscribe(s -> { //成功回调 }, throwable -> { //失败回调 }); if (!disposable.isDisposed()) { //判断请求有没有结束 disposable.dispose(); //没有结束,则关闭请求 }
RxHttp能够很是优雅的实现上传/下载及进度的监听,是骡子是马,拉出来溜溜
3.8.1上传
经过addFile系列方法添加文件,以下:
RxHttp.postForm("/service/...") //发送Form表单形式的Post请求 .addFile("file1", new File("xxx/1.png")) //添加单个文件 .addFile("fileList", new ArrayList<>()) //经过List对象,添加多个文件 .asString() .subscribe(s -> { //上传成功 }, throwable -> { //上传失败 });
经过asUpload系列方法监听上传进度,以下:
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.二、下载
下载使用asDownload(String)
方法,传入本地路径便可
//文件存储路径 String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk"; RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk") .asDownload(destPath) //注意这里使用asDownload操做符,并传入本地路径 .subscribe(s -> { //下载成功,回调文件下载路径 }, throwable -> { //下载失败 });
3.8.三、带进度下载
带进度下载使用asDownload(String,Consumer,Scheduler)
方法
//文件存储路径 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.四、断点下载
断点下载
相较于下载
,仅须要调用setRangeHeader(long startIndex, long endIndex)
方法传入开始及结束位置便可(结束位置不传默认为文件末尾),其它没有任何差异
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.五、带进度断点下载
带进度断点下载
相较于带进度下载
仅须要调用setRangeHeader
方法传入开始及结束位置便可(结束位置不传默认为文件末尾),其它没有任何差异
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)
方法传入上次已经下载好的长度(第二个参数),以下:
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.一、设置全局超时
RxHttp内部默认的读、写、链接超时时间均为10s,如需修改,请自定义OkHttpClient对象,以下:
//设置读、写、链接超时时间为15s OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .build(); RxHttp.init(client);
3.9.二、为单个请求设置超时
为单个请求设置超时,使用的是RxJava的timeout(long timeout, TimeUnit timeUnit)
方法,以下:
RxHttp.get("/service/...") .asString() .timeout(5, TimeUnit.SECONDS)//设置总超时时间为5s .as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调 .subscribe(pageList -> { //成功回调 }, (OnError) error -> { //失败回调 });
注:这里设置的总超时时间要小于全局读、写、链接超时时间之和,不然无效
3.10.一、设置全局Converter
IConverter converter = FastJsonConverter.create(); RxHttp.setConverter(converter)
3.10.二、为请求设置单独的Converter
首先须要在任意public类中经过@Converter注解声明Converter,以下:
public class RxHttpManager { @Converter(name = "XmlConverter") //指定Converter名称 public static IConverter xmlConverter = XmlConverter.create(); }
而后,rebuild 一下项目,就在自动在RxHttp类中生成setXmlConverter()
方法,随后就能够调用此方法为单个请求指定Converter,以下:
RxHttp.get("/service/...") .setXmlConverter() //指定使用XmlConverter,不指定,则使用全局的Converter .asObject(NewsDataXml.class) .as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调 .subscribe(dataXml -> { //成功回调 }, (OnError) error -> { //失败回调 });
3.11.一、加密
请求加密,须要自定义Param,很是简单,详情请查看本文5.2章节----自定义Param
3.11.二、解密
有些时候,请求会返回一大串的密文,此时就须要将密文转化为明文,直接来看代码,以下:
//设置数据解密/解码器 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便可,以下:
RxHttp.get("/service/...") .setDecoderEnabled(false) //设置本次请求不须要解密,默认为true .asString() .subscribe(pageList -> { //成功回调 }, (OnError) error -> { //失败回调 });
RxHttp默认在Io线程执行请求,也默认在Io线程回调,即默认在同一Io线程执行请求并回调,固然,咱们也能够指定请求/回调所在线程。
3.12.一、指定请求所在线程
咱们能够调用一些列subscribeXxx方法指定请求所在线程,以下:
//指定请求所在线程,须要在第二部曲前任意位置调用,第二部曲后调用无效 RxHttp.get("/service/...") .subscribeOnCurrent() //指定在当前线程执行请求,即同步执行, .asString() .subscribe(); //其它subscribeXxx方法 subscribeOnIo() //RxHttp默认的请求线程 subscribeOnSingle() subscribeOnNewThread() subscribeOnComputation() subscribeOnTrampoline() subscribeOn(Scheduler) //自定义请求线程
以上使用的皆是RxJava的线程调度器,不熟悉的请自行查阅相关资料,这里不作详细介绍。
3.12.二、指定回调所在线程
指定回调所在线程,依然使用RxJava的线程调度器,以下:
//指定回调所在线程,须要在第二部曲后调用 RxHttp.get("/service/...") .asString() .observeOn(AndroidSchedulers.mainThread()) //指定在主线程回调 .subscribe(s -> { //s为String类型,主线程回调 //成功回调 }, throwable -> { //失败回调 });
时常会有童鞋问我,我是Retrofit用户,喜欢把接口写在一个类里,而后能够直接调用,RxHttp如何实现?其实,这个问题压根就不是问题,在介绍第二部曲的时候,咱们知道,使用asXxx方法后,就会返回Observable<T>
对象,所以,咱们就能够这样实现:
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又如何实现?简单,以下:
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的后面,带上对应的参数便可。
在RxHttp有4个重要的角色,分别是:
为此,我画了一个流程图,能够直观的了解到RxHttp的大体工做流程
我想应该很好理解,RxHttp要作的事情,就是把添加的参数/请求头等所有丢给Param处理,本身啥事也不敢;随后将Param交给HttpSender,让它去执行请求,执行完毕,返回Response对象;接着又将Response对象丢给Parser去作数据解析工做,并返回实体类对象T;最后,将T经过回调传给开发者,到此,一个请求就处理完成。
首先,附上一张Param类的继承关系图
下面将从上往下对上图中的类作个简单的介绍:
HttpSender能够把它理解为请求发送者,里面声明OkHttpClient对象和一系列静态方法,咱们来简单看下:
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对象,就能够发送一个请求。
先看下Parser继承结构图
这里对上图中的类作个简单的介绍
T onParse(Response)
方法,输入Response对象,输出实体类对象TasList(Class<T>)
方法,就是经过该解析器实现的前面第二部曲中,咱们介绍了一系列asXxx方法,经过该系列方法能够很方便的指定数据返回类型,特别是自定义的asResponse(Class<T>)
、asResponseList(Class<T>)
、asResponsePageList(Class<T>)
这3个方法,将Reponse<T>
类型数据,处理的简直不要太完美,下面咱们就来看看如何自定义Parser。
源码永远是最好的学习方式,在学习自定义Parser前,咱们不妨先看看内置的Parser是如何实现的
SimPleParser
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接口中定义的,而且有默认的实现,以下:
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是否是这样实现的,以下:
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>
数据类型,先看看数据结构:
public class Response<T> { private int code; private String msg; private T data; //这里省略get、set方法 }
自定义ResponseParser代码以下:
//经过@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>)
方法,以下:
public <T> Observable<T> asResponse(Class<T> type) { return asParser(new ResponseParser(type)); }
能够看到,该方法内部又调用了asParser(Parser<T>)
方法,并传入了ResponseParser,所以,咱们有两种方式使用自定义的ResponseParser,以下:
//第一种方式,使用@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
@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
@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; } }
自定义Param,想较于自定义Parser,要更加的简单,咱们只需根据本身的需求,继承NoBodyParam、FormParam、JsonParam等,增长或者重写方法便可,好比咱们有如下3种状况,须要自定义Param,以下:
这种状况,咱们须要继承FormParam,并重写getRequestBody()方法,以下:
@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(); } }
这种状况,咱们须要继承JsonParam,也重写getRequestBody()方法,以下:
@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); //发送加密后的字符串 } }
咱们继承FormParam,并新增两个test方法`,以下:
@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; } }
一样的问题,咱们怎么用这3个自定义的Param呢?我想大多数人在类名前发现类@Param
注解,并为Param取了别名。那这个又有什么做用呢?
答案揭晓,只要在自定的Param上使用了@Param
注解,并取了别名,就会在RxHttp类自动生成一个跟别名同样的方法,在上面咱们自定义了3个Param,并分别取别名为postEncryptForm、postEncryptJson、postTestForm,此时就会在RxHttp类中生成postEncryptForm(String)
、postEncryptJsonString)
、postTestForm(String)
这3个方法,咱们在RxHttp这个类中来看下:
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)); }
发请求时,只须要调用对应的方法就好,如:
//发送加密的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!!!!,选择对应的请求方法后,就能够直接调用,以下:
//发送加密的postJson请求 RxHttp.postTestJson("/service/...") .test(100L, 99.99F) //调用自定义的API .test1("testKey", 88.88D) //调用自定义的API .add("key", "value") //添加参数,可调用屡次 .asString() //返回String类型 .subscribe(s-> { //请求成功 }, throwable -> { //请求失败 });
RxHttp内部默认使用来GsonConverter,而且额外提供了4个Converter,以下:
//非必须 根据本身需求选择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'
即便这样,RxHttp也没法保证知足全部的业务需求,为此,咱们能够选择自定义Converter,自定义Converter须要继承IConverter接口,以下:
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
请查看本文3.10章节----设置Converter
在这教你们一个小技巧,因为使用RxHttp发送请求都遵循请求三部曲,故咱们能够在android studio 设置代码模版,以下
如图设置好后,写代码时,输入rp,就会自动生成模版,以下:
到这,RxHttp经常使用功能介绍完毕,你会发现,一切都是那么的美好,不管你是get、post、加密请求、自定义解析器,仍是文件上传/下载/进度监听等等,皆遵循请求三部曲。特别是对Response<T>
类型数据处理,能够说是完美无缺,咱们无需每次都判断code,直接就能够拿到T,简直了。。。
最后,喜欢的,请给本文点个赞,若是能够,还请给个star,创做不易,感激涕零。🙏🙏🙏