RxHttp Android HTTP 请求框架使用指南
RxHttp 是基于 OkHttp 的二次封装,与 RxJava 无缝衔接的 Android HTTP 请求框架。支持多域名、动态域名切换、文件上传下载及进度监听、自动生命周期管理等功能。通过注解处理器(APT)在编译期生成代码,降低耦合,提供极简 API 和零上手成本。内置多种数据解析器,支持自定义 Parser、Param 和 Converter,满足加密、解密等复杂业务需求。

RxHttp 是基于 OkHttp 的二次封装,与 RxJava 无缝衔接的 Android HTTP 请求框架。支持多域名、动态域名切换、文件上传下载及进度监听、自动生命周期管理等功能。通过注解处理器(APT)在编译期生成代码,降低耦合,提供极简 API 和零上手成本。内置多种数据解析器,支持自定义 Parser、Param 和 Converter,满足加密、解密等复杂业务需求。

RxHttp 是一款受到广大 Android 开发者喜爱的 Http 请求框架。相比 Retrofit,RxHttp 在功能上两者均能实现,并无多大差异,更多的差异体现在易用性上。例如对文件上传/下载/进度监听的操作上,RxHttp 用极简的 API 实现;另外在 baseUrl、公共参数/请求头、请求加解密等功能上的易用性也要优于 Retrofit。个人觉得 RxHttp 最大的优势在于它近乎为 0 的上手成本、极简的 API 以及高扩展性。
当然,RxHttp 也有缺点,那就是它的稳定性目前还不如 Retrofit,毕竟 RxHttp 刚出道不久,且全部由个人维护。但 RxHttp 未开源前,在实际项目已经使用了近 2 年,接着将其开源,目前大大小小已迭代多个版本,用的人也不在少数,可以说很稳定了。
RxHttp 是基于 OkHttp 的二次封装,并与 RxJava 做到无缝衔接,一条链就能发送任意请求。主要优势如下:
Gradle 依赖
OkHttp 3.14.x 以上版本,最低要求为 API 21,如你想要兼容 21 以下,请依赖 OkHttp 3.12.x,该版本最低要求 API 9。
asXxx 方法内部是通过 RxJava 实现的,而 RxHttp 2.2.0 版本起,内部已剔除 RxJava,如需使用,请自行依赖 RxJava 并告知 RxHttp 依赖的 Rxjava 版本。
将 jitpack 添加到项目的 build.gradle 文件中:
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
注:RxHttp 2.6.0 版本起,已全面从 JCenter 迁移至 jitpack。
//使用 kapt 依赖 rxhttp-compiler 时必须
apply plugin: 'kotlin-kapt'
android {
//必须,java 8 或更高
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.github.liujingxing.rxhttp:rxhttp:2.7.3'
kapt 'com.github.liujingxing.rxhttp:rxhttp-compiler:2.7.3' //生成 RxHttp 类,纯 Java 项目,请使用 annotationProcessor 代替 kapt
}
android {
kapt {
arguments {
//依赖了 RxJava 时,rxhttp_rxjava 参数为必须,传入 RxJava 版本号
arg("rxhttp_rxjava", "3.1.1")
arg("rxhttp_package", "rxhttp") //指定 RxHttp 类包名,非必须
}
}
//如果项目未集成 kotlin,通过 javaCompileOptions 方法传参,在 defaultConfig 标签下
annotationProcessorOptions {
arguments = [
rxhttp_rxjava: '3.1.1',
rxhttp_package: 'rxhttp'
]
}
}
dependencies {
//rxjava2 (RxJava2/Rxjava3 二选一,使用 asXxx 方法时必须)
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.github.liujingxing.rxlife:rxlife-rxjava2:2.2.1' //管理 RxJava2 生命周期,页面销毁,关闭请求
//rxjava3
implementation 'io.reactivex.rxjava3:rxjava:3.1.1'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.1' //管理 RxJava3 生命周期,页面销毁,关闭请求
//非必须,根据自己需求选择 RxHttp 默认内置了 GsonConverter
implementation 'com.github.liujingxing.rxhttp:converter-fastjson:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-jackson:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-moshi:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-protobuf:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-simplexml:2.7.3'
}
注:添加依赖后,需要 rebuild 一下项目,注解处理器才会生成 RxHttp 类;另外 kotlin 用户,请使用 kapt 替代 annotationProcessor。
最后,Rebuild 一下项目(必须的),就会自动生成 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 -> {
//请求失败
});
是的,不用怀疑,就是这么简单。
任意请求,任意返回数据类型,皆遵循请求三部曲
到这,你已经掌握了精髓,我们只需牢记请求三部曲,使用 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")) //添加单个文件
.addFiles("fileList", new ArrayList<>()) //添加多个文件
...
以上只列出了几个常用的 addXxx 方法,更多方法请下载源码体验。
添加好参数/请求头后,正式进入第二部曲,确定返回数据类型,我们通过 asXxx 方法确定返回类型,比如,我们要返回一个 Student 对象,就可以通过 asClass(Class<T>) 方法,如下:
RxHttp.postForm("/service/...") //发送 post 表单请求
.add("key", "value") //添加参数,可调用多次
.asClass(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类型数据
然而,现实开发中,大多数人的接口,返回的数据结构都类似下面的这个样子:
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 方法,如,asInteger、asBoolean、asLong、asBitmap、asList、asMap等等,它们最终都是通过 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(s -> {
//成功回调,当前在主线程回调
}, (OnError) error -> {
//失败回调,当前在主线程回调
});
到这,请求三部曲介绍完毕,接着,将介绍其它常用的功能。
RxHttpPlugins.init(OkHttpClient) //自定义 OkHttpClient 对象
.setDebug(boolean) //是否开启调试模式,开启后,logcat 过滤 RxHttp,即可看到整个请求流程日志
.setCache(File, long, CacheMode) //配置缓存目录,最大 size 及缓存模式
.setExcludeCacheKeys(String...) //设置一些 key,不参与 cacheKey 的组拼
.setResultDecoder(Function) //设置数据解密/解码器,非必须
.setConverter(IConverter) //设置全局的转换器,非必须
.setOnParamAssembly(Function); //设置公共参数/请求头回调
此步骤是非必须的,如需要添加拦截器等其他业务需求,则可调用 init 方法进行初始化,不初始化或者传入 null 即代表使用默认 OkHttpClient 对象,建议在 Application 中初始化,默认的 OkHttpClient 对象在 RxHttpPlugins 类中可以找到,如下:
//Default OkHttpClient object in RxHttp
private static OkHttpClient getDefaultOkHttpClient() {
SSLParams sslParams = HttpsUtils.getSslSocketFactory();
return new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.hostnameVerifier((hostname, session) -> true)
.build();
}
虽然初始化是非必须的,但是建议大家传入自定义的 OkHttpClient 对象,一来,自定义的 OkHttpClient 能最大化满足自身的业务;二来,随着 RxHttp 版本的升级,默认的 OkHttpClient 可能会发生变化 (虽然可能性很小),故建议自定义 OkHttpClient 对象传入 RxHttp。
RxHttp 支持为所有的请求添加公共参数/请求头,如下:
RxHttpPlugins
.init(okHttpClient)
.setOnParamAssembly(new Function<Param, Param>() {
@Override
public Param apply(Param p) { //此方法在 UI 线程执行,请勿执行耗时操作
Method method = p.getMethod();
if (method.isGet()) { //可根据请求类型添加不同的参数
} else if (method.isPost()) {
}
return p.add("versionName", "1.0.0")//添加公共参数
.addHeader("deviceType", "android"); //添加公共请求头
}
});
我们需要在 RxHttp初始化的地方,通过 setOnParamAssembly 方法设置公共参数接口回调,此时每次发起请求,都会回调该接口。
当然,如果希望某个请求不回调该接口,即不添加公共参数/请求头,则可以调用 setAssemblyEnabled(boolean) 方法,并传入 false 即可,如下:
RxHttp.get("/service/...") //get 请求
.setAssemblyEnabled(false) //设置是否添加公共参数/头部,默认为 true
.asString() //返回字符串数据
.subscribe(s -> { //这里的 s 为 String 类型
//请求成功
}, throwable -> {
//请求失败
});
3.6.1、多域名
现实开发中,我们经常会遇到多个域名的情况,其中 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.2、动态域名
现实开发中,也会有动态域名切换的需求,如域名被封、或者需要根据服务端下发的域名去配置,这对于 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.1、自动关闭请求
自动关闭请求,需要引入本人开源的另一个库 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。
3.7.2、手动关闭请求
手动关闭请求,我们只需要在订阅回调的时候拿到 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 -> {
//上传失败
});
通过 upload 系列方法监听上传进度,如下:
RxHttp.postForm("/service/...") //发送 Form 表单形式的 Post 请求
.addFile("file1", new File("xxx/1.png"))
.addFiles("file2", new File("xxx/2.png"))
.upload(AndroidSchedulers.mainThread(), progress -> {
//上传进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已上传的字节大小
long totalSize = progress.getTotalSize(); //要上传的总字节大小
})
.asString()
.subscribe(s -> {
//上传成功
}, throwable -> {
//上传失败
});
可以看到,跟上传的代码相比,我们仅仅增加了 upload 方法,第一个参数是指定回调的线程,这里我们指定了在 UI 线程中回调,第二个参数是进度监听回调,每当进度有更新时,都会回调该接口。
3.8.2、下载
下载使用 asDownload(String) 方法,传入本地路径即可
//文件存储路径
String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.asDownload(destPath) //注意这里使用 asDownload 操作符,并传入本地路径
.subscribe(s -> {
//下载成功,回调文件下载路径
}, throwable -> {
//下载失败
});
如果你想监听下载进度,调用 asDownload(String, Scheduler, Consumer<Progress>) 方法,传入回调线程及回调接口即可,如下:
//文件存储路径
String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.asDownload(destPath, AndroidSchedulers.mainThread(), progress -> { //第二个参数指定主线程回调
//下载进度回调,0-100,仅在进度有更新时才会回调,最多回调 101 次,最后一次回调文件存储路径
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已下载的字节大小
long totalSize = progress.getTotalSize(); //要下载的总字节大小
})
.subscribe(s -> {//s 为 String 类型,这里为文件存储路径
//下载完成,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
3.8.3、断点下载
断点下载相较于下载,仅需要调用 asAppendDownload 方法即可,其它没有任何差别
String destPath = getExternalCacheDir() + "/" + "test.apk";
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.asAppendDownload(destPath)
.subscribe(s -> { //s 为 String 类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
当然,此时你想监听下载进度,也是可以的,调用 asAppendDownload(String, Scheduler, Consumer<Progress>) 方法,传入回调线程及回调接口即可,如下:
String destPath = getExternalCacheDir() + "/" + "test.apk";
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.asAppendDownload(destPath, AndroidSchedulers.mainThread(), progress -> {
//下载进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已下载的字节大小
long totalSize = progress.getTotalSize(); //要下载的总字节大小
}) //指定主线程回调
.subscribe(s -> { //s 为 String 类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});
3.9.1、设置全局超时
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.2、为单个请求设置超时
为单个请求设置超时,使用的是 RxJava 的 timeout(long timeout, TimeUnit timeUnit) 方法,如下:
RxHttp.get("/service/...")
.asString()
.timeout(5, TimeUnit.SECONDS)//设置总超时时间为 5s
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(s -> {
//成功回调
}, (OnError) error -> {
//失败回调
});
注:这里设置的总超时时间要小于全局读、写、连接超时时间之和,否则无效。
3.10.1、设置全局 Converter
IConverter converter = FastJsonConverter.create();
RxHttpPlugins.init(OkHttpClient)
.setConverter(converter)
3.10.2、为请求设置单独的 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
.asClass(NewsDataXml.class)
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(dataXml -> {
//成功回调
}, (OnError) error -> {
//失败回调
});
3.11.1、加密
请求加密,需要自定义 Param,非常简单,详情请查看本文 5.2 章节----自定义 Param。
3.11.2、解密
有些时候,请求会返回一大串的密文,此时就需要将密文转化为明文,直接来看代码,如下:
//设置数据解密/解码器
RxHttpPlugins.init(OkHttpClient)
.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(s -> {
//成功回调
}, (OnError) error -> {
//失败回调
});
RxHttp 默认在 Io 线程执行请求,也默认在 Io 线程回调,即默认在同一 Io 线程执行请求并回调,当然,我们也可以指定请求/回调所在线程。
3.12.1、同步请求
RxHttp 默认在 IO 线程发起请求,即异步请求,如果需要同步请求,调用 setSync 方法即可,如下:
//指定请求所在线程,需要在第二部曲前任意位置调用,第二部曲后调用无效
RxHttp.get("/service/...")
.setSync() //同步执行,
.asString()
.subscribe();
以上使用的皆是 RxJava 的线程调度器,不熟悉的请自行查阅相关资料,这里不做详细介绍。
3.12.2、指定回调所在线程
指定回调所在线程,依然使用 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) //使用标准的占位符协议
.asClass(Student.class);
}
//多个占位符
public static Observable<Student> getStudent(int page, int count) {
return RxHttp.get("/service/%1$d/%2$d/...", page, count) //使用标准的占位符协议
.asClass(Student.class);
}
}
这一点跟 Retrofit 不同,Retrofit 是通过注解指定占位符的,而 RxHttp 是使用标准的占位符,我们只需要在 url 中声明占位符,随后在传入 url 的后面,带上对应的参数即可。
RxHttp 使用到当下流行的注解处理器工具 (Annotation Processing Tool,以下简称 APT),像知名的 Eventbus、ButterKnife、Dagger2、Glide 以及 Jetpack 库里非常好用 Room 数据库框架,都使用到了 APT,它能够在编译时检索注解信息,通过 Javapoet 框架生成 Java 类、方法等相关代码 (想生成 Kotlin 相关代码,使用 kotlinpoet),并因此在运行时做到零性能损耗。
那么,APT 给 RxHttp 带来了哪些优势?RxHttp 又是如何使用 APT 的?继续往下看。
说起 APT,大家脑海里第一个想到的可能是解耦,没错,解耦是它的一大优势,其实它还有一个更大有优势,那就是根据配置,生成不同的代码逻辑;比如在 RxHttp 中,默认是不依赖 RxJava 的,但是如果你需要使用 RxHttp + RxJava 方式发送请求,就可以在 annotationProcessorOptions 标签中的 rxhttp_rxjava 参数来配置 RxJava 大版本,可传入 RxJava2 或 RxJava3,内部根据传入的 RxJava 版本,生成不同的代码,这样就可做到一套代码同时兼通 RxJava2 和 RxJava3,如果后续出了 RxJava4、RxJava5 等新版本,一样可以兼容,而且非常简单。
在 RxHttp v2.4.2 以下版本中,对 OkHttp 的兼容,也使用了该方式去适配 okhttp 各个版本,为此,RxHttp 适配了 OkHttp v3.12.0 到 v4.9.0(截止 2020/12/27 最新版本) 中的任一版本 (v4.3.0 除外,该版本有一个 bug,导致无法适配),因此,使用 RxHttp,完全不用担心 okhttp 版本冲突问题。
同时兼容 RxJava、OkHttp 不同版本,这就是 APT 带给 RxHttp 的第一大优势。
RxHttp 是如何使用 APT?在 RxHttp 中,一共定义了 6 个注解,如下:
@DefaultDomain:用它来指定默认的 baseUrl,只能使用一次
@Domain:指定非默认的 baseUrl,可使用多次
@Parser:指定自定义的解析器,可使用多次,这个非常强大,可在解析器里写自己数据解析逻辑,并返回任意类型的数据,完美解决服务端返回的数据不规范问题
@Param:指定自定义的 Param,可使用多次,发送统一加密请求时用到
@OkClient:为不同请求配置不同的 OkHttpClient 对象,可多次使用
@Converter:为不同请求配置不同的 Converter 对象,可多次使用
注:以上 6 个注解的具体使用方式,请查看官方文档。
RxHttp 的注解处理器是 rxhttp-compiler,它首要任务就是生成 RxHttp 类,其次就是检索以上 6 个注解,生成对应的类及方法,这就使得,无论我们如何去自定义,写请求代码时,始终遵循请求三部曲,如我们要发送统一加密的请求,就可以直接使用 @Param 注解生成的方法,如下:
//发送加密的 post 表单请求,方法名可通过@Param 注解随意指定
val student = RxHttp.postEncryptForm("/service/...")
.add("key", "value")
.toClass<Student>()
.await()
其它 5 个注解带来的优势就不一一介绍了,总之就是另一大优势,解耦,使得任意请求,皆遵循请求三部曲。
RxHttp 工作流程
接下来,讲讲 RxHttp 的工作流程,有 5 个重要的角色,分别是:
RxHttp:这是最重要的一个角色,所以请求的唯一入口,内部持有一个 Param 对象,它的职责是,请求参数/请求头/BaseUrl 的处理,请求线程的调度,提供注解生成的方法等等,最终的使命就是通过 Param 构建一个 okhttp3.Request 对象,随后在构建一个 okhttp3.Call 对象,并把 Call 对象丢给 Observable 或 IAwait,然后由 Observable 或 IAwait真正的去执行请求
Param:它的职责是处理请求参数/请求头/url 等一切用来构建 okhttp3.Request 需要的东西,最终使命就是构建 okhttp3.Request 对象,它被 RxHttp 类所持有,RxHttp 把构建 okhttp3.Request 对象所需要的东西,都交给 Param 去实现的
IAwiat:结合协程发请求时,真正执行网络请求的对象,具体实现类为 AwaitImpl,它内部持有 Parser 对象,请求返回后,将 okhttp3.Response 丢给 Parser 去解析,并返回解析后的对象
Observable:结合 RxJava 发送请求时,真正执行网络请求的对象,具体实现类有 ObservableCallExecute、ObservableCallEnqueue、ObservableParser,分别用于同步请求、异步请求及请求返回后对 okhttp3.Response 对象的数据解析,ObservableParser 内部持有 Parser 对象,具体的解析工作都交给 Parser
Parser:负责数据解析工作,将数据解析成我们想要的数据类型,这是一个接口对象,内部只有 onParse(response: Response): T这一个方法,具体实现类有 4 个:SimpleParser、StreamParser、SuspendStreamParser、BitmapParser,第一个为万能的解析器,内部的 asClass/toClss 方法,就是通过它去实现的;第二第三是下载文件时用的的解析器,区别前者是结合 RxJava 下载的,后者是结合协程下载的;最后一个是解析 Bitmap 对象用的,asBitmap/toBitmap 就是通过它去实现的
工作流程图略。
前面第二部曲中,我们介绍了一系列 asXxx 方法,通过该系列方法可以很方便的指定数据返回类型,特别是自定义的 asResponse(Class<T>)、asResponseList(Class<T>)、asResponsePageList(Class<T>)这 3 个方法,将 Reponse<T>类型数据,处理的简直不要太完美,下面我们就来看看如何自定义 Parser。
源码永远是最好的学习方式,在学习自定义 Parser 前,我们不妨先看看内置的 Parser 是如何实现的。
SimPleParser
open class SimpleParser<T> : TypeParser<T> {
protected constructor() : super()
constructor(type: Type) : super(type)
@Throws(IOException::class)
override fun onParse(response: Response): T {
return response.convert(types[0])
}
}
其中,convert(Type) 是 okhttp3.Response 的扩展方法,定义如下:
@Throws(IOException::class)
fun <R> Response.convert(type: Type): R {
val body = ExceptionHelper.throwIfFatal(this)
val needDecodeResult = OkHttpCompat.needDecodeResult(this)
LogUtil.log(this, null)
val converter = OkHttpCompat.getConverter(this)
return converter!!.convert(body, type, needDecodeResult)
}
该方法的目的就是把服务器返回的数据,转换为我们期望的实体类对象。
到这,我想大家应该就多少有点明白了,自定义 Parser,无非就是继承 TypeParser,然后实现 onParser 方法即可,现在,我们来自定义 ResponseParser,用来处理 Response<T>数据类型,先看看数据结构:
public class Response<T> {
private int code;
private String msg;
private T data;
//这里省略 get、set 方法
}
自定义 ResponseParser 代码如下:
@Parser(name = "Response", wrappers = [PageList::class])
open class ResponseParser<T> : TypeParser<T> {
//注意,以下两个构造方法是必须的
protected constructor() : super()
constructor(type: Type) : super(type)
@Throws(IOException::class)
override fun onParse(response: okhttp3.Response): T {
val data: Response<T> = response.convertTo(Response::class, *types)
val t = data.data //获取 data 字段
if (data.code != 200 || t == null) {
throw ParseException(data.code.toString(), data.msg, response)
}
return t
}
}
以上代码,需要注意两个地方
第一,我们在类开头使用了 @Parser 注解,该注解有两个参数,如下:
name 代表解析器的名称,这里取名为 Response,于是在 RxHttp 类就会有 asResponse(Class<T>) 方法(命名方式为:as + name属性的值);
wrappers 可以把他理解为泛型 T 的包装类,需要传入 Class[] 数组对象,这里我们传入了 {PageList.class} 这两个类,于是就会生成 asResponsePageList(Class<T>) 方法。(命名方式为:as +name属性的值+Class类名)
注:PageList 类需要自己定义,用于加载分页数据,Demo 里有这个类。
第二,我们在 if 语句里,对 code 做了判断,非 200 或者 data 为空时,就抛出异常,并带上了 code 及 msg 字段,所以我们在异常回调的地方就能拿到这两个字段。
此时,我们就可以通过这 3 个方法,直接拿到 T、List<T>、PageList<T>类型数据,并且对 code 做了统一的判断,直接来看看如何使用这个解析器。
//第一种方式,使用@parser 注解生成的 asResponse 方法
RxHttp.postForm("/service/...") //发送 post 表单请求
.add("key", "value") //添加参数,可调用多次
.asResponse(Student.class) //返回 Student 类型
//.asResponseList(Student.class) //返回 List<Student>类型
//.asResponsePageList(Student.class) //返回 PageList<Student>类型
.subscribe(student -> {
//请求成功,这里能拿到 Student 对象
}, throwable -> {
//请求失败
});
//第二种方式,直接使用 asParser(Parser<T>) 方法
RxHttp.postForm("/service/...") //发送 post 表单请求
.add("key", "value") //添加参数,可调用多次
.asParser(new ResponseParser<Student>(){}) //返回 Student 类型
.subscribe(student -> {
//请求成功,这里能拿到 Student 对象
}, throwable -> {
//请求失败
});
以上两种方式,除了写法上的区别,其它都一样,相信大家都会选择第一种方式,不仅写法简单,还降低了耦合。
自定义 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 PostEncryptJsonParam(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 方法 extbackslash`,如下:
@Param(methodName = "postTestForm")
public class PostTestFormParam extends FormParam {
public PostTestFormParam(String url) {
super(url, Method.POST);
}
public PostTestFormParam test(long a, float b) {
//这里的业务逻辑自行实现
return this;
}
public PostTestFormParam test1(String s, double b) {
//这里的业务逻辑自行实现
return this;
}
}
同样的问题,我们怎么用这 3 个自定义的 Param 呢?我想大多数人在类名前发现类 @Param 注解,并为 Param 取了别名。那这个又有什么作用呢?答案揭晓,只要在自定的 Param 上使用了 @Param 注解,并取了别名,就会在 RxHttp 类自动生成一个跟别名一样的方法,在上面我们自定义了 3 个 Param,并分别取别名为 postEncryptForm、postEncryptJson、postTestForm,此时就会在 RxHttp 类中生成 postEncryptForm(String)、postEncryptJson(String)、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,并且额外提供了 5 个 Converter,如下:
//非必须 根据自己需求选择 Converter RxHttp 默认内置了 GsonConverter
implementation 'com.github.liujingxing.rxhttp:converter-fastjson:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-jackson:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-moshi:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-protobuf:2.7.3'
implementation 'com.github.liujingxing.rxhttp:converter-simplexml:2.7.3'
即使这样,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。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online