解决 Retrofit 多 BaseUrl 及运行时动态改变 BaseUrl ?

原文地址: https://juejin.im/post/5978567d51882517921cdcfdgit

前言

Hello,我是 JessYan,做为一个喜欢探索新颖解决方案的我,在 上篇文章 中,向你们介绍了怎样经过一行代码便可实现上传下载以及 Glide 进度监听,如今又给你们带来了另外一项你们都很期待的问题的解决方案,这个问题起源于 MVPArms 的一个 Issues ,固然使用 Retrofit 时,多个 BaseUrl 以及动态切换 BaseUrl 这两个需求,在其余地方也常常被讨论,那么下面就来说讲个人思路和解决方案github

Github : 你的 Star 是我坚持的动力 ✊安全

gif

需求出现的场景

也许在平常开发中有些人已经遇到了这两个需求的场景,但为了让一些以前没遇到这些场景的朋友,也能看懂这篇文章,因此先在前面提一提服务器

多个 BaseUrl 的需求场景

若是项目是聚合型 App ,好比像一些新闻资讯类客户端,可能数据源来自于多个平台,好比说知乎啊,豆瓣啊,今日头条啊,因此这样就会涉及到多个 BaseUrl框架

若是项目使用到多个三方服务提供商,好比图片的读取使用到一个服务商,文件的存储又使用到另外一个服务商,这个也会存在一个 App 出现多个 BaseUrlide

动态改变 BaseUrl 的需求场景

若是项目的 BaseUrl 会在 App 启动时,请求服务器,根据服务器的返回结果,来肯定项目最终的 BaseUrl,就会涉及到运行时动态切换 BaseUrlpost

若是项目的某个三方服务提供商,并非固定的,也许会出现变动的状况,好比存储服务从七牛迁移至其余云存储,那咱们为了不更改代码致使从新打包以及发版,就会从服务器获取三方服务提供商的 BaseUrl ,而后在运行时动态改变这个 BaseUrl学习

解决方案

其实官方 Api 早已经提供了解决方案来支持多个 BaseUrl 以及运行时动态改变 BaseUrl ,民间也一样有不少解决方案优化

官方静态解决方案

熟悉 Retrofit 的开发者应该知道 @Get , @Post 这些标注到每一个接口方法上的注解不只能够传相对路径,还能够传全路径,这样咱们就能够作到不一样的接口使用不一样的 BaseUrl ,从而达到使用多个 BaseUrl 的需求,可是注解上的值只能是 Final 的常量,不能动态改变,因此我称这个解决方案为静态解决方案ui

官方动态解决方案

熟悉 Retrofit 的开发者也一样知道 @Url 这个标注到每一个接口方法参数上的注解,它能够将全路径做为参数传进接口做为每次请求的 Url 地址,每次请求接口均可以将不一样的全路径做为参数,从而达到支持多个 BaseUrl 以及在运行时动态改变 BaseUrl ,因此不少请求图片等资源的接口都是使用这个方案(咦,看样子这个官方解决方案不是同时解决我提到的这两个问题吗,别急,先日后面看!)

民间经常使用解决方案

以前也看过不少开源的聚合类 App 源码,像一些整合 知乎 , 豆瓣 , Gank 等多个平台数据的 App ,由于各自平台的域名不一样,因此大多数这类 App 会给每一个平台都各自建立一个 Retrofit 对象,即不一样的 BaseUrl 使用不一样的 Retrofit 对象来建立 ApiService 进行请求,这样只要新增一个不一样的 BaseUrl ,那就须要从新建立一个新的 Retrofit 对象

这样也能够同时实现,支持多个 BaseUrl 以及运行时动态改变 BaseUrl 这两个需求,可是以我的的观点,建立多个其余配置属性如出一辙,只是 BaseUrl 不同的 Retrofit 对象,太过于浪费资源

民间大牛解决方案

以前偶然看到了一个 Retrofit 维护者, Square 公司的大牛的 解决方案,用来解决运行时动态改变 BaseUrl ,其实也算半官方的解决方案

提到这个解决方案时,不得不讲一个趣事,其实以前 Retrofit 默认是支持运行时动态改变 BaseUrl 的,之前是有一个名为 BaseUrl 的接口,而 Retrofit.Builder#baseUrl(BaseUrl) 方法当时传的参数就是这个 BaseUrl ,而不是如今的 HttpUrl ,这个接口内部就有一个方法返回 HttpUrl ,那时候只要实现 BaseUrl 后,动态改变这个方法的返回值,就能够实现动态改变 BaseUrl

可是这位大牛认为这样的作法不安全,因此提了一个 Pull Requests ,删掉了这个 BaseUrl 接口,并用上面的解决方案替代之,而亲爱的 JakeWharton 赞成了他的观点,并合并了这个 PR 因而才有了如今的 Retrofit.Builder#baseUrl(HttpUrl) 这个不能动态改变 BaseUrlApi

Retrofit 比较早的老鸟,应该知道之前有一个这个 Api,我是说后来的版本怎么没了,原来毁在了这位兄台手上

这个方案也就是利用 Interceptor 拦截器,动态改变每一个 RequestUrl 从而实现动态改变 BaseUrl,但他这个解决方案不能支持多 BaseUrl ,只要 host 一设置,直到下一次改变 Host 以前,后面的全部 Request 都必须使用同一个 Host ,还有一些弊端后面一块儿分析

几个方案的对比与分析

淘汰含有明显缺陷的方案

4个方案中,我首先淘汰的就是 民间经常使用解决方案 ,在前面已经明确了个人观点,由于我我的认为建立多个其余配置属性如出一辙,只是 BaseUrl 不同的 Retrofit 对象,太过于浪费资源,因此就算他能知足个人全部需求,除非真的没有更好的解决方案,不然我是不会选择它的

剩下的三个方案中, 官方静态解决方案 只能解决,2个需求中的支持多个 BaseUrl ,而对于动态改变 BaseUrl ,因为注解的 Value 只能为常量,因此对这个需求也是无能为力的(两个需求都知足,才表示可行)

谁是最优方案?

其实在前面已经说了 官方动态解决方案 就已经能够同时实现多 BaseUrl 和运行时动态改变 BaseUrl ,那为何我不直接选择这个方案,还要继续分析呢?

答案也很简单,我认为这个方案,虽然灵活,可是灵活却给它带来了使用上的繁琐,每一个接口每次调用都必须传入全路径做为参数,不只繁琐并且接口一多还很差管理

民间大牛解决方案 可行? 可是我在前面已经说了这个不可行啊?

这个方案虽然能够支持运行时动态切换 BaseUrl 可是它是全局处理,一经使用改变的是全部请求的 Url ,因此它并不支持多 BaseUrl

而且更可怕的是,这个方案不只不支持多 BaseUrl ,还会影响 官方静态解决方案官方动态解决方案 这两个支持多 BaseUrl 的方案,由于无论你注解里面声明的是什么全路径,它的 Interceptor 拦截器,都会强行将这个请求的 Url 改为它的 BaseUrl ,因此这个方案注定只适合只有一个 BaseUrl 但须要动态改变的项目

那岂不是 4 个解决方案都不可行?说这么久说个毛线啊?

方案所有淘汰?散会?

等等别急啊,虽然我站在个人角度, Pass 了文中提到的全部已存在的解决方案

可是你们仔细想一想,若是网上已经存在完美的解决方案,那我还写这篇文章有什么意义?一定是没有我满意的解决方案,我才会本身动手去解决并分享啊,毕竟我是一个不肯意写重复内容的有为青年,只要是我写的内容确定是会让你们学到不同的知识三 ✊,否则不是砸本身招牌

好了,不逗你们了,开整!

别急,还有大招!

虽然在已有的解决方案当中没有找到让我满意的,可是在遇到问题时,冷静分析现有解决方案是颇有必要的,理解前人的思路后才会对整个问题理解得更透彻,个人不少文章也都是以分析和解决思路为主,授人以鱼不如授人以渔,因此我不会直接告诉你答案,先分析一波,理清思路

这不,在分析 民间大牛解决方案 时,虽然最后发现这不是本身想要的解决方案,可是做为有发散思惟的我,又是灵机一动,借助原有解决方案在上面这样一改不是就可行了?

如何改善原有方案?

上面的分析已经说了 民间大牛解决方案 ,能够在 Interceptor 拦截器中设置一个全局的 Host(Host 能够理解为 BaseUrl) ,拦截器会强行将这个 Host 应用到全部的请求上,改变该请求原有的 Url,这样致使了只会同时存在一个 Host

因此我在想,将这个惟一的 Host 变量改成集合,以存储多个 Host ,在将不一样的 Host 应用到不一样的请求上,不就能够支持多 BaseUrl

实践想法

说干就干,因而我本身建了一个全局的容器来存储多个 Host,这样我就能够在 App 运行时的任什么时候间,任何地点随意新增,修改,删除 Host

遇到问题

可是问题来了,我想要将不一样的 Host 应用到不一样的请求上,但我怎么知道什么请求须要什么样的 Host ,每一个请求总要有个标记,让我知道他须要什么样的 Host

因而我就在想 Retrofit 有什么方法,能够在请求以前给每一个请求加上不一样的字符串标记,因而我很天然的想到了 Header ,Retrofit 正好有 @Headers 这个注解,能够给每一个接口方法上加入自定义 Header

再次解决难点

我给须要不一样 BaseUrl 的接口方法上加入了自定义的 Header ,以标明每一个接口须要的 HostName ,而这个 Name 对应的值就是 Host,但这个值不是在 @Headers 中被指定的,它是能够动态改变的

存储 Host 的容器是一个 Map, key 就是这个 Name ,value 才是 Host ,拦截器每次拦截到请求时,会判断这个请求是否有这个自定义 Header, 有的话,拿到这个 Header 中标注的 Name,而后用这个 Name ,去那个存储 Host 的全局 Mapget(name),拿到对应的 Host 再应用到请求上不是就达到支持多个 BaseUrl 了?

若是想动态改变某个 Host 也简单,将新的 Host 以一样的 Name put(name) 进这个全局 Map ,到时候拦截器,使用这个 Name get(name) 出来的值,就已是改变后最新的 Host ,在将这个 Host 应用到请求上不是就达到动态改变 BaseUrl 了?

这不,两个需求同时知足!

优化方案

这个方案就两步,给须要不一样 BaseUrl 的请求设置 Header (想用 Retrofit 默认 BaseUrl 的接口,或者使用 官方静态解决方案, 官方动态解决方案 就不须要设置),在经过全局容器来管理 BaseUrl

针对于那种只有一个 BaseUrl 但须要动态改变的项目,本框架提供了一个 GlobalDomain 来优化这个场景,不须要给接口加 Header ,只须要一步,向全局容器 put(GlobalDomain) 你想要改变的 BaseUrl 就能够了

官方动态解决方案 给每一个接口传全路径做为参数,要简单的多, 官方动态解决方案 注定只适合那种只有一两个须要动态改变 BaseUrl 的接口

总结

以上提到的解决方案,已经优化并封装成了三方库并上传至 Jcenter,方便你们使用

本解决方案主要适合,须要同时具有多 BaseUrl 以及动态改变 BaseUrl 的项目,或者只有一个 BaseUrl ,但须要动态改变 BaseUrl 的项目

若是对于只须要多 BaseUrl 不须要动态改变 BaseUrl 的项目,其实用 官方静态解决方案 就已经足够了,但我仍是推荐用个人这个解决方案,由于需求都是会变的,若是一旦要加入动态改变 BaseUrl 的需求,如须要动态切换 生产环境 和 开发环境 ,那这时怎么办,一个个改掉每一个接口注解里面的全路径?

Github : 具体使用看 Demo ,记得 Star !

公众号

扫码关注个人公众号 JessYan,一块儿学习进步,若是框架有更新,我也会在公众号上第一时间通知你们


Hello 我叫 JessYan,若是您喜欢个人文章,能够在如下平台关注我

-- The end

相关文章
相关标签/搜索