解決 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

相關文章
相關標籤/搜索