如果你還不知道Retrofit,沒關系,okhttp你總知道吧。retrofit就是對okhttp再做了一層封裝。你只需要通過簡單的配置就能順利使用retrofit來做網絡請求了。還沒有使用過retrofit的小伙伴們,不妨嘗嘗鮮。
本篇是retrofit番外篇。只講retrofit中的設計模式以及我個人的理解與延伸。如果你還沒看過retrofit源碼,不妨先看看這篇Retrofit分析-漂亮的解耦套路。
還是先上圖:
以前用的volley, async-http-lib, xUtils。這些libs基本都是上圖這個workflow。向服務器請求API總共分三步。
- build request(API參數配置)
- executor(這里可以有很多變體,比如有無隊列,進出順序,線程管理)
- parse callback(解析數據,返回T給上層)
如今的retrofit也是換湯不換藥的。也是這三步。
- 通過注解配置API參數
-
CallAdapter
(你可以把它理解成executor) -
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()
當我們自己寫的代碼的時候盡量也要這樣來做。
比如我們有一個獨立并公用的模塊,需要供其他模塊來調用。比如download
,location
,socialshare
等
最好我們寫一個module
,將所有相關的代碼都放在這個module
中。這是第一步。
第二步,為你的module
提供一個漂亮的門面。比如下載的DownloadManager
, 經緯度的LocationTracker
, 社交分享的SocialManager
。它們做為功能模塊的入口,要盡量的簡潔,方法命名好記易理解,類上要有完整的示例注釋
第三步,閉門造車。不管你在里面干什么,外面都是不知道的,就像薛定諤的那只貓,外層不調用它,永遠不知道它是否好用。
不過為了以后好維護,不給他人留坑,還是盡量寫的工整一些。
裝飾模式
裝飾模式跟靜態代理很像。
每次一說裝飾模式,就想成decorator,實際上叫wrapper更直觀些。既然是wrapper,那就得有源的句柄,在構造wrapper時得把source作為參數傳進來。wrapper了source,同樣還wrapper其他功能。
代理模式,Proxy Delegate,實際上Delegate也不知道自己被代理了,Proxy偽裝成Delegate來執行,既然是proxy,那proxy不應該提供delegate沒有的public方法,以免被認出來。
拋開理論的描述,我們直接來看下面的代碼。
你可以將ExecutorCallbackCall
當作是Wrapper,而真正去執行請求的源Source是OkHttpCall
。之所以要有個Wrapper類,是希望在源Source操作時去做一些額外操作。這里的操作就是線程轉換,將子線程切換到主線程上去。
簡單的解釋下,enqueue()方法是異步的,也就是說,當你調用
OkHttpCall
的enqueue方法,回調的callback是在子線程中的,如果你希望在主線程接受回調,那需要通過Handler轉換到主線程上去。ExecutorCallbackCall
就是用來干這個事。當然以上是原生retrofit使用的切換線程方式。如果你用rxjava,那就不會用到這個ExecutorCallbackCall
而是RxJava
的Call了。這里不展開。
動態代理
再來說動態代理。以往的動態代理和靜態代理使用的場景是類似的。都想在delegate調用方法前后做一些操作。如果我的代理類有很多方法,那我得額外寫很多代碼,所以這時候就引入了動態代理。通過動態設置delegate,可以處理不同代理的不同方法??床欢疀]關系,直接上代碼:
簡而言之,動態代理就是攔截調用的那個方法,在方法前后來做一些操作。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方法,先判斷有沒初始化,然后再返回。
差不多就是這樣,沒有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
。
適配器模式就是,已經存在的OkHttpCall
,要被不同的標準,平臺來調用。設計了一個接口CallAdapter
,讓其他平臺都是做不同的實現來轉換,這樣不花很大的代價就能再兼容一個平臺。666。
策略模式?
在retrofit里,這個適配器模式不是那么明顯。而且和其他模式交錯在一起,所以看起來很麻煩。比如這個CallAdapter又夾雜著策略模式(僅是個人看法)。你可以看看Rxjava里如何去創建adapter的,它是根據api方法聲明的returnType來創建具體的CallAdapter
實例的。上代碼你就明白了。
是不是很像根據不同的策略使用不同的算法?不同的returnType聲明就是set不同的Strategy。
總結
來張提綱挈領的流程圖,沒保存的趕緊存起來。以后就能照著它自己開車了。
好,大概就將這么多啦。這些就是retrofit的解耦套路了。通過一系列的設計模式,封裝思想來解耦,看到現在,其實retrofit就是一個負責調度的controller。先給retrofit配置好,讓它能夠正常工作。你給它一個方法調用,它就在內部開始運轉。這個方法以前我消化過嗎,沒消化那就用一個ServiceMethod來解析它。解析后要用來配置一個request請求。但它自己搞不定這事啊,所以需要給它一個轉接頭,通過轉接頭來使用okhttpcall。請求是做好了,但是response它又不認識,
所以又請來convertor來幫忙,轉換完畢之后才吐出一個我們最終要的那個對象。
終點站到啦,請下車。覺得老司機開的穩,坐的舒心。不妨再刷個卡吧。。滴滴。。
如果看文章不夠過癮,可以看Stay精心錄制的視頻Retrofit分析-漂亮的解耦套路,看完你再也不怕看不懂retrofit了。而且你還可以用Stay這種分析套路來輕松看懂其他源碼。
擴展閱讀:
關于Retrofit源碼分析可以看我另外一篇文章
Retrofit分析-漂亮的解耦套路沒耐心自己分析源碼的同學,還可以參考Stay錄制的視頻版,包學包會: )
Retrofit分析-漂亮的解耦套路(視頻版)另外怎么選擇開源library,可以參考這么多開源框架,該用哪個好?