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