前言
本文并非介绍开源库使用方式,如不习惯文档结构,请见谅。另,RxHttp各版本间API可能变动较大,请谨慎使用。
目标
最近在重构公司基础组件。可预见的是,过程会很痛苦。所以,便想梳理一些在做公共库时的思路及注意事项。
每隔一段时间便看到一些网络请求库,那么今天就围绕此话题进行讨论。
首先,我们要明确网络请求库的功能及目标:
- 支持网络请求
- 简洁、易用的API
- 高度可扩展
当然,还有其他比如"包体积"、"依赖关联"等分支功能,但其主要的功能需求仍然是上面三点。
支持网络请求
这是最基础的核心功能,其他所有的需求都是围绕此功能进行扩展。因此,我们必须明确此功能的核心流程:
构建Request -> 发送Request -> 解析Response -> 返回解析值
接下来的工作就是围绕此流程设计API。
简洁、易用的API
我们首先要考虑的一个问题是:
OkHttp能满足我们几乎所有的需求,为何需要多封装一层?
装逼如风,常伴吾身?
哈,开个玩笑。我个人认为的最大原因是"API不好用"。请注意"不好用"和"不好"的区别。
包括但不仅限于:标准但冗长的调用过程、进度监听的缺失、糟糕的异步线程(Android)
我一直认为,好用的库通常是这样定义的:
通过20%的API,实现80%的功能
那么,我们开始设计API吧!
1.构建Request
我们知道,Http请求是由"请求行"、"请求头"、"请求体"构成,大致的API如下:
rxHttp.get(url) // 支持get、post、head、delete...
.addHeader(...) // 增加单个头
.addUrlParam(...) // 增加单个URL参数
.addRequestParam(...) // 增加单个表单参数
.addRequestStringParams(...) // 增加多个字符串表单参数
.addRequestFileParams(...) // 增加多个文件表单参数
.requestBody4JSon(...) // 设置Json请求体
.requestBody(...) // 自定义请求体
我见过很多网络请求库无论是URL参数还是请求体参数,都是使用"addParam(...)"这种方式添加。
先不论是否语义明确,若是URL和请求体都需要设置参数(永远别高估后端的技术水平),这时候可就够呛了。
2.发送Request
在原先的OkHttp中,发送请求是通过异步线程池控制并执行的。在RxJva、协程等出来之前,可能是最优的解决方式。但RxJva、协程让我们看到了更优秀的处理方式,大致的API如下:
.toUploadObservable(...) // 转为带上传进度的Observable
.toDownloadObservable(...) // 转为带下载进度的Observable
.toResultObservable(...) // 转为普通结果的Observable
3.解析Response
OkHttp的Response很强大,但却不太适用于实际业务场景。通常来说,我们希望通过网络请求获取特定格式的Entity,其他的信息并不需要关心。
但为了适用于各种复杂的场景,解析器设计成了接口模式:
public interface Converter<T> {
@NonNull T convertResponse(@NonNull Response response) throws Throwable;
}
通过给Observable传入特定的解析器,去解析数据:
.toResultObservable(StringConverter())
我也曾考虑过内置Gson,并自动解析,但最后还是放弃了。原因主要是:
- 你并不能肯定API的来源,来源不同,可能规范也不同
- 过多的引入额外架包,并不是件好事
4.返回解析值
既然使用了RxJava做为线程管理,那么必然返回Observable,同时也赋予了丰富的后续变换。
但有个问题是:进度值的接收。
我曾试过多种方式去接收处理(可以从RxHttp的历史记录看到):
(1) 设置setOnDownloadListener(...),内部使用Handler发送
放弃的原因是进度值脱离了Rx的数据流,这会导致使用者的思维困扰,可能导致后续隐患。同时,使用了Handler,就和Android平台深度捆绑,适用用户范围将进一步缩小。
(2) 封装返回类型Response,其内部包含普通解析值和进度值
放弃的原因是其需要使用者去判断返回类型,并筛选值,大大增加了使用的繁琐度。
最终设计方案,通过RxJava中的<compose>操作符,过滤进度监听,并返回如期Entity,如下:
.compose(object : ProgressProcessor<T>() {
override fun onProgress(totalSize: Long, currentSize: Long, percent: Int) {
// 进度处理
}
})
高度可扩展
通过以上的API设计,我们已经完成了一个基本网络请求库,但这并不是我们的终点。
在设计之初,我们便定下了"高度可扩展"的目标要求。那么,又有哪些需要扩展?
- 基础配置
- 可扩展请求
- 自由的线程切换
- 灵活的解析器
基本囊括了请求流程的各个环节。
(1) 基础配置
我们知道,通常情况下,网络请求有大量的公共配置,例如:超时、基础请求头、Cookie等等。
因此,在构建请求器时,我们需要一些配置项,如下:
val rxHttp = RxHttp.Builder()
.debug(...) // 开启Debug模式
.baseUrl(...) // 设置基础请求地址
.addBaseHeader(...) // 增加单个基础头
.addBaseHeaders(...) // 增加多个基础头
.addBaseUrlParam(...) // 增加单个基础URL参数
.addBaseUrlParams(...) // 增加多个基础URL参数
.connectTimeout(...) // 设置连接超时(ms)
.readTimeout(...) // 设置读超时(ms)
.writeTimeout(...) // 设置写超时(ms)
.hostnameVerifier(...) // 设置域名校验规则
.sslFactory(...) // 设置SSL验证
.cookieJar(...) // 设置Cookie管理
.addInterceptor(...) // 增加拦截器
.addNetworkInterceptor(...) // 增加网络层拦截器
.build()
细心的会发现,这里的请求器,并没有使用单例模式,而是需要创建一个普通的对象。基于以下考虑:
当我们的APP当中,出现多种请求域集合,并需要配置不同的超时、域名校验、基础参数
此时,如果用单例,会发现仍旧需要创建不同的OkHttpClient,甚至于打乱原本的API构建方式,使内部实现复杂度直线上升。
因此,将请求器交由用户去创建、管理,不失为一种温和、可行的方案。
(2) 可扩展请求
除了使用度较高的请求方式,RxJava还提供了多种定制请求构建方式:
rxHttp.method(...) // 自定义请求方法
.requestBody(...) // 自定义请求体
理论上,只要OkHttp支持的方式,RxHttp都可以扩展。
(3) 自由的线程切换
通过RxJva实现,并将其线程切换交由用户控制。
(4) 灵活的解析器
在介绍解析Response时,我就已经提到过,用户可以实现Converter接口,灵活的实现数据解析。
当然了,RxHttp提供了基础的解析器:DefaultConverter、FileConverter、StringConverter
结语
以上便是RxHttp在设计时的一些思路及考虑,具体文档请参考RxHttp。
如有任何意见或讨论,欢迎提Issue或直接给我发邮件。
Thanks!