Retrofit分析-經典設計模式案例

如果你還不知道Retrofit,沒關系,okhttp你總知道吧。retrofit就是對okhttp再做了一層封裝。你只需要通過簡單的配置就能順利使用retrofit來做網絡請求了。還沒有使用過retrofit的小伙伴們,不妨嘗嘗鮮。

本篇是retrofit番外篇。只講retrofit中的設計模式以及我個人的理解與延伸。如果你還沒看過retrofit源碼,不妨先看看這篇Retrofit分析-漂亮的解耦套路

還是先上圖:


retrofit00.png

以前用的volley, async-http-lib, xUtils。這些libs基本都是上圖這個workflow。向服務器請求API總共分三步。

  1. build request(API參數配置)
  2. executor(這里可以有很多變體,比如有無隊列,進出順序,線程管理)
  3. parse callback(解析數據,返回T給上層)

如今的retrofit也是換湯不換藥的。也是這三步。

  1. 通過注解配置API參數
  2. CallAdapter(你可以把它理解成executor)
  3. Converter(解析數據并轉換成T)

本來還應該有個CallFactory來切換具體的http client的。就像volley那樣>=9使用HttpUrlConnection, <9使用HttpClient。不過我想都是square出品,彼此互推也是理所當然的啊。我相信以后okhttp肯定是唯一的請求client。

上面說CallAdapter可以理解成executor,具體是什么,我們下面講具體設計模式時再詳細討論。

好,鋪墊了這么多,我要開車了。Stay先帶你看看沿途一些的設計模式,等到終點了再來看整體的架構。

先來說個最簡單的設計模式。

外觀模式(門面模式)

Retrofit給我們暴露的方法和類不多。核心類就是Retrofit,我們只管配置Retrofit,然后做請求。剩下的事情就跟上層無關了,只需要等待回調。這樣大大降低了系統的耦合度。對于這種寫法,我們叫外觀模式(門面模式)。

幾乎所有優秀的開源library都有一個門面。比如Glide.with() ImageLoader.load() Alamofire.request()。有個門面方便記憶,學習成本低,利于推廣品牌。 Retrofit的門面就是retrofit.create()

當我們自己寫的代碼的時候盡量也要這樣來做。

比如我們有一個獨立并公用的模塊,需要供其他模塊來調用。比如downloadlocation,socialshare
最好我們寫一個module,將所有相關的代碼都放在這個module中。這是第一步。
第二步,為你的module提供一個漂亮的門面。比如下載的DownloadManager, 經緯度的LocationTracker, 社交分享的SocialManager。它們做為功能模塊的入口,要盡量的簡潔,方法命名好記易理解,類上要有完整的示例注釋
第三步,閉門造車。不管你在里面干什么,外面都是不知道的,就像薛定諤的那只貓,外層不調用它,永遠不知道它是否好用。
不過為了以后好維護,不給他人留坑,還是盡量寫的工整一些。

裝飾模式

裝飾模式跟靜態代理很像。

每次一說裝飾模式,就想成decorator,實際上叫wrapper更直觀些。既然是wrapper,那就得有源的句柄,在構造wrapper時得把source作為參數傳進來。wrapper了source,同樣還wrapper其他功能。

代理模式,Proxy Delegate,實際上Delegate也不知道自己被代理了,Proxy偽裝成Delegate來執行,既然是proxy,那proxy不應該提供delegate沒有的public方法,以免被認出來。

拋開理論的描述,我們直接來看下面的代碼。

retrofit02.png

你可以將ExecutorCallbackCall當作是Wrapper,而真正去執行請求的源Source是OkHttpCall。之所以要有個Wrapper類,是希望在源Source操作時去做一些額外操作。這里的操作就是線程轉換,將子線程切換到主線程上去。

簡單的解釋下,enqueue()方法是異步的,也就是說,當你調用OkHttpCall的enqueue方法,回調的callback是在子線程中的,如果你希望在主線程接受回調,那需要通過Handler轉換到主線程上去。ExecutorCallbackCall就是用來干這個事。當然以上是原生retrofit使用的切換線程方式。如果你用rxjava,那就不會用到這個ExecutorCallbackCall而是RxJava的Call了。這里不展開。

動態代理

再來說動態代理。以往的動態代理和靜態代理使用的場景是類似的。都想在delegate調用方法前后做一些操作。如果我的代理類有很多方法,那我得額外寫很多代碼,所以這時候就引入了動態代理。通過動態設置delegate,可以處理不同代理的不同方法??床欢疀]關系,直接上代碼:

retrofit03.png

簡而言之,動態代理就是攔截調用的那個方法,在方法前后來做一些操作。Retrofit里的動態代理比較巧妙。實際上它根本就沒有delegate。因為這個方法沒有真正的實現。使用動態代理,只是單純的為了拿到這個method上所有的注解。所有的工作都是由proxy做了。比起我們總說代理就是打log要高明多了。

我以前自己寫數據庫框架時,也碰到這樣的場景。一個類里有很多一對一,一對多關系。如果從db里fetch出來都去做初始化,那會非常影響性能。但如果不初始化,到使用時再去手動初始化就更麻煩了。怎么辦呢?

    class A{
        private B b;
        private ArrayList<C> cs;
        
        public B getB(){
            return b;
        }
        
        public ArrayList<C> getCs(){
            return cs;
        }
    }

當類A里的get方法被invoke時,我就判斷,這個類有沒有被初始化,如果有,那就不做任何操作。如果沒有,那得等會,我把數據從數據庫中fetch出來給你賦值后,再去invoke。這個場景可以叫懶加載,可以套用AOP面向切面編程。

動態代理能實現這個需求嗎?可以,但是支持的很糟糕。因為動態代理依賴接口實現,總不能將所有的pojo中的方法都申明到接口里吧?那真是要命了。

所以我用了種替代方案,既然是AOP,有個面向切面的框架AspectJ。你可以通過它來切入這些get方法,先判斷有沒初始化,然后再返回。

retrofit04.png

差不多就是這樣,沒有Proxy的概念,只是在編譯時,把這些切面織入進去。對于pojo而言完全是透明的。是不是很6。不過這里也有很多其他的性能瓶頸,比如說我在第一次調用時,要先去數據庫fetch,這也是耗時操作。這個先跳過,有機會再跟大家八一八,我那數據庫框架是怎么擼出來的。

適配器模式

如果你已經看過retrofit源碼,很可能被CallAdapter玩壞。這個CallAdapter不是那么好理解。先拋開代碼,我們來看看適配器模式。

Adapter簡單來說,就是將一個已存在的東西轉換成適合我們使用的東西。就比方說電源Adapter。出國旅游都要帶轉接頭。比方說,RecyclerView里的Adapter是這么定義的。Adapters provide a binding from an app-specific data set to views。

再回來看看Retrofit,為什么我們需要轉接頭呢。那個被轉換的是誰?我們看看CallAdapter的定義。Adapts a {@link Call} into the type of {@code T}. 這個Call是OkHttpCall,它不能被我們直接使用嗎?被轉換后要去實現什么特殊的功能嗎?

我們假設下。一開始,retrofit只打算在android上使用,那就通過靜態代理ExecutorCallbackCall來切換線程。但是后來發現rxjava挺好用啊,這樣就不需要Handler來切換線程了嘛。想要實現,那得轉換一下。將OkHttpCall轉換成rxjava(Scheduler)的寫法。再后來又支持了java8(CompletableFuture)甚至居然還有iOS支持。大概就是這樣一個套路。當然我相信square的大神肯定一開始就考慮了這種情況,從而設計了CallAdapter。

retrofit06.png

適配器模式就是,已經存在的OkHttpCall,要被不同的標準,平臺來調用。設計了一個接口CallAdapter,讓其他平臺都是做不同的實現來轉換,這樣不花很大的代價就能再兼容一個平臺。666。

策略模式?

在retrofit里,這個適配器模式不是那么明顯。而且和其他模式交錯在一起,所以看起來很麻煩。比如這個CallAdapter又夾雜著策略模式(僅是個人看法)。你可以看看Rxjava里如何去創建adapter的,它是根據api方法聲明的returnType來創建具體的CallAdapter實例的。上代碼你就明白了。

retrofit05.png

retrofit07.png

是不是很像根據不同的策略使用不同的算法?不同的returnType聲明就是set不同的Strategy。

總結

來張提綱挈領的流程圖,沒保存的趕緊存起來。以后就能照著它自己開車了。

retrofit01.png

好,大概就將這么多啦。這些就是retrofit的解耦套路了。通過一系列的設計模式,封裝思想來解耦,看到現在,其實retrofit就是一個負責調度的controller。先給retrofit配置好,讓它能夠正常工作。你給它一個方法調用,它就在內部開始運轉。這個方法以前我消化過嗎,沒消化那就用一個ServiceMethod來解析它。解析后要用來配置一個request請求。但它自己搞不定這事啊,所以需要給它一個轉接頭,通過轉接頭來使用okhttpcall。請求是做好了,但是response它又不認識,
所以又請來convertor來幫忙,轉換完畢之后才吐出一個我們最終要的那個對象。

終點站到啦,請下車。覺得老司機開的穩,坐的舒心。不妨再刷個卡吧。。滴滴。。

如果看文章不夠過癮,可以看Stay精心錄制的視頻Retrofit分析-漂亮的解耦套路,看完你再也不怕看不懂retrofit了。而且你還可以用Stay這種分析套路來輕松看懂其他源碼。

擴展閱讀:

  1. 關于Retrofit源碼分析可以看我另外一篇文章
    Retrofit分析-漂亮的解耦套路

  2. 沒耐心自己分析源碼的同學,還可以參考Stay錄制的視頻版,包學包會: )
    Retrofit分析-漂亮的解耦套路(視頻版)

  3. 另外怎么選擇開源library,可以參考這么多開源框架,該用哪個好?

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

推薦閱讀更多精彩內容