阅读 230

网络请求库开发杂谈

前言

本文并非介绍开源库使用方式,如不习惯文档结构,请见谅。另,RxHttp各版本间API可能变动较大,请谨慎使用。

目标

最近在重构公司基础组件。可预见的是,过程会很痛苦。所以,便想梳理一些在做公共库时的思路及注意事项。

每隔一段时间便看到一些网络请求库,那么今天就围绕此话题进行讨论。

首先,我们要明确网络请求库的功能及目标:

  1. 支持网络请求
  2. 简洁、易用的API
  3. 高度可扩展

当然,还有其他比如"包体积"、"依赖关联"等分支功能,但其主要的功能需求仍然是上面三点。

支持网络请求

这是最基础的核心功能,其他所有的需求都是围绕此功能进行扩展。因此,我们必须明确此功能的核心流程:

构建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

OkHttpResponse很强大,但却不太适用于实际业务场景。通常来说,我们希望通过网络请求获取特定格式的Entity,其他的信息并不需要关心。

但为了适用于各种复杂的场景,解析器设计成了接口模式:

public interface Converter<T> {
    @NonNull T convertResponse(@NonNull Response response) throws Throwable;
}
复制代码

通过给Observable传入特定的解析器,去解析数据:

.toResultObservable(StringConverter())
复制代码

我也曾考虑过内置Gson,并自动解析,但最后还是放弃了。原因主要是:

  1. 你并不能肯定API的来源,来源不同,可能规范也不同
  2. 过多的引入额外架包,并不是件好事

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. 基础配置
  2. 可扩展请求
  3. 自由的线程切换
  4. 灵活的解析器

基本囊括了请求流程的各个环节。

(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提供了基础的解析器:DefaultConverterFileConverterStringConverter

结语

以上便是RxHttp在设计时的一些思路及考虑,具体文档请参考RxHttp

如有任何意见或讨论,欢迎提Issue或直接给我发邮件。

Thanks!

关注下面的标签,发现更多相似文章
评论