解決Retrofit多BaseUrl及運行時動態改變BaseUrl?

原文地址: http://www.lxweimin.com/p/2919bdb8d09a

前言

Hello,我是 JessYan,作為一個喜歡探索新穎解決方案的我,在 上篇文章 中,向大家介紹了怎樣通過一行代碼即可實現上傳下載以及 Glide 進度監聽,現在又給大家帶來了另一項大家都很期待的問題的解決方案,這個問題起源于 MVPArms 的一個 Issues ,當然使用 Retrofit 時,多個 BaseUrl 以及動態切換 BaseUrl 這兩個需求,在其他地方也經常被討論,那么下面就來講講我的思路和解決方案

Github : 你的 Star 是我堅持的動力 ?

gif

需求出現的場景

也許在日常開發中有些人已經遇到了這兩個需求的場景,但為了讓一些之前沒遇到這些場景的朋友,也能看懂這篇文章,所以先在前面提一提

多個 BaseUrl 的需求場景

如果項目是聚合型 App ,比如像一些新聞資訊類客戶端,可能數據源來自于多個平臺,比如說知乎啊,豆瓣啊,今日頭條啊,所以這樣就會涉及到多個 BaseUrl

如果項目使用到多個三方服務提供商,比如圖片的讀取使用到一個服務商,文件的存儲又使用到另一個服務商,這個也會存在一個 App 出現多個 BaseUrl

動態改變 BaseUrl 的需求場景

如果項目的 BaseUrl 會在 App 啟動時,請求服務器,根據服務器的返回結果,來確定項目最終的 BaseUrl,就會涉及到運行時動態切換 BaseUrl

如果項目的某個三方服務提供商,并不是固定的,也許會出現變更的情況,比如存儲服務從七牛遷移至其他云存儲,那我們為了避免更改代碼導致重新打包以及發版,就會從服務器獲取三方服務提供商的 BaseUrl ,然后在運行時動態改變這個 BaseUrl

解決方案

其實官方 Api 早已經提供了解決方案來支持多個 BaseUrl 以及運行時動態改變 BaseUrl ,民間也同樣有很多解決方案

<a name="1"></a>

官方靜態解決方案

熟悉 Retrofit 的開發者應該知道 @Get , @Post 這些標注到每個接口方法上的注解不僅可以傳相對路徑,還可以傳全路徑,這樣我們就可以做到不同的接口使用不同的 BaseUrl ,從而達到使用多個 BaseUrl 的需求,但是注解上的值只能是 Final 的常量,不能動態改變,所以我稱這個解決方案為靜態解決方案

<a name="2"></a>

官方動態解決方案

熟悉 Retrofit 的開發者也同樣知道 @Url 這個標注到每個接口方法參數上的注解,它可以將全路徑作為參數傳進接口作為每次請求的 Url 地址,每次請求接口都可以將不同的全路徑作為參數,從而達到支持多個 BaseUrl 以及在運行時動態改變 BaseUrl ,所以很多請求圖片等資源的接口都是使用這個方案(咦,看樣子這個官方解決方案不是同時解決我提到的這兩個問題嗎,別急,先往后面看!)

<a name="3"></a>

民間常用解決方案

之前也看過很多開源的聚合類 App 源碼,像一些整合 知乎 , 豆瓣 , Gank 等多個平臺數據的 App ,因為各自平臺的域名不同,所以大多數這類 App 會給每個平臺都各自創建一個 Retrofit 對象,即不同的 BaseUrl 使用不同的 Retrofit 對象來創建 ApiService 進行請求,這樣只要新增一個不同的 BaseUrl ,那就需要重新創建一個新的 Retrofit 對象

這樣也可以同時實現,支持多個 BaseUrl 以及運行時動態改變 BaseUrl 這兩個需求,但是以個人的觀點,創建多個其他配置屬性一模一樣,只是 BaseUrl 不一樣的 Retrofit 對象,太過于浪費資源

<a name="4"></a>

民間大牛解決方案

之前偶然看到了一個 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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,590評論 25 707
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,776評論 18 139
  • 本文翻譯自https://futurestud.io/tutorials/retrofit-getting-sta...
    sakasa閱讀 18,511評論 3 54
  • 痛飲了一場春夏之交的雨 城市小區里的人工湖 醉熏熏地伴著晚風起伏 角角落落里的青蛙 也被雨水灌得有些醉意 呱呱呱地...
    胡劍廷閱讀 582評論 0 1
  • 家里有小孩子如何找尋學習的空間,兩個孩子在家一會這樣一會那樣,確實不知道在什么時間能安下心來思考,只有上班的時間在...
    藍天白云789閱讀 379評論 0 0