别人家SDK的设计模式——Android Retrofit库源码解读
作者:網易合作產品部·李若昆
我們在日常編寫代碼中免不了會用到各種各樣第三方庫,網絡請求、圖片加載、數據庫等等。有些lib接入可能方便到幾行代碼搞定,有些lib可能從demo、文檔到測試都是坑(比如lib嵌套lib導致資源沖突、lib中定義的類無法擴展、兼容性差導致大量崩潰等),相信接過第三方庫的童鞋不會沒有過這樣的吐槽。筆者也是在最近修改一個bug的過程中翻看了一些第三方庫的源碼,發現其中存在點設計技巧,于是結合最近看的設計模式,來討論一下在SDK中如何使用,與大家相互交流,也為本人之后SDK的開發工作做點鋪墊。
引入Retrofit
這個第三方lib叫做Retrofit,是個用在Java中支持restful的網絡庫。Retrofit是在基于OkHttp3的基礎上,用動態代理和annotation實現了restful標準的規范,令開發者使用起來異常方便。Retrofit當然也實現了網絡請求的異步處理,并且用工廠模式給開發者預留了很大的擴展空間,可以與ReactiveX結合,也可以由開發者定義自己的同步或異步請求、回調方式。
為了方便講解設計模式的實現,我們先來看看代碼中如何使用Retrofit。引用官方文檔的介紹,只需要這樣聲明好你的api接口:
public interface GitHubService { ?@GET("users/{user}/repos")Call<List<Repo>> listRepos(@Path("user") String user); }在初始化時傳入這個接口的class:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); GitHubService service = retrofit.create(GitHubService.class);調用接口時只需兩行代碼即可:
Call<List<Repo>> repos = service.listRepos("octocat");//獲取網絡請求實例 repos.excute();//執行請求,異步請求用repos.enqueue(callback);其中List是對請求返回數據的定義,repos是執行請求的實例(實現了Call接口,后面會詳細介紹)。
從以上代碼可以看到,我們做的僅僅是聲明了一個接口,涵蓋所需的api接口,Retrofit就自動幫我們創建了一個實現這個api接口的實例,我們只需坐享其成調用實例的方法即可完成網絡請求。Retrofit的這種“智能”是如何實現的呢?那就是接下來要談的動態代理模式。
Retrofit中的代理模式
為什么需要代理呢?代理其實就是我們想做一件事的時候不親自動手,也就是“創建網絡請求實例”這件事,交給一個代理去創建,這樣不管它內部怎樣實現,只要能幫我們創建出一個可用的實例就可以了,通常這個實例也是實現了某個接口的(比如文中的Call接口),所以即使底層的實現改變,或者創建過程改變,使用者的代碼是不需要調整的。就像我們在攜程、去哪兒上買機票,我們也不關心他們到底是從航空公司官方買票,還是從中間商手中買票,只要最終我們能拿到票就行了(所以也會買到用里程數換來的機票,噗…)。
言歸正傳,Retrofit用到的動態代理,類圖如下:?
籃框中的就是代理部分,代理了用戶定義接口(即GitHubService)中的所有函數,創建一個Call對象,代理實例通過這句代碼來產生:
GitHubService service = retrofit.create(GitHubService.class);進去看create函數源碼會發現這里是通過反射實現的,直接返回了java.lang.reflect.Proxy中的方法newProxyInstance:
public <T> T create(final Class<T> service) {... ? ?return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, ? ? ? ?new InvocationHandler() { ? ? ? ? ?@Override public Object invoke(Object proxy, Method method, Object... args)throws Throwable { ? ? ? ? ? ?//這里有判斷method是否為Object類聲明的方法...ServiceMethod serviceMethod = loadServiceMethod(method);OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); ? ? ? ? ? ?return serviceMethod.callAdapter.adapt(okHttpCall);}});}這個代理實例可以將接口(也就是我們定義的GitHubService)指定的所有方法都指派到invocationHandler中去,當調用service的接口方法時,就會執行InvocationHandler中的Invoke方法,可以看到Retrofit就是在這里創建一個網絡請求實例OkHttpCall,將其返回(其實返回的是callAdapter.adapt(okHttpCall),將okHttpCall適配轉換過的對象,詳見后面適配器模式),我們就可以利用此實例進行網絡請求了。這里invoke方法三個參數中proxy就是代理對象,method表示要調用的方法,args是對method方法傳入的參數。
Retrofit中的適配器模式
適配器模式是將一個類的接口,轉換成客戶期望的另一個接口,讓原本接口不兼容的類可以合作無間。比如生活中的電源適配器,將220v電壓轉換成電子設備需要的輸入電壓,比如Android中的ListView,Adapter將各種各樣的數據轉換后傳給ListView用來顯示。Retrofit中的Adapter是用來轉換網絡請求Call接口的,而這里的Adapter可以由使用者自定義,從而轉換成使用者希望的類,具有很強的擴展性,見類圖:?
圖中綠色部分就是適配器模式。這個適配器是怎樣運作的呢?
在剛才的代理模式中Retrofit已經幫我們智能創建了網絡請求實例Call,Call是對網絡請求定義的接口。Retrofit實際默認new的對象是OkHttpCall(一個封裝了okhttp3.Call的類),我們并不在意它具體是什么類,能按照Call接口的定義來使用就夠了。但用起來才發現我們會有很多額外需求,比如OkHttpCall的回調函數是在工作線程調用的,而網絡回調函數我們通常要更新UI,再用handler轉到主線程?對使用者來說太麻煩了。于是適配器華麗登場,CallAdapter可以將默認生成的OkHttpCall轉換成你想要的任何類型。
比如Retrofit默認提供的Adapter,就是這樣將OkHttpCall適配成ExecutorCallbackCall:
new CallAdapter<Call<?>>() {... ?@Override public <R> Call<R> adapt(Call<R> call) { ? ?return new ExecutorCallbackCall<>(callbackExecutor, call);} }static final class ExecutorCallbackCall<T> implements Call<T> {... ?@Override public void enqueue(final Callback<T> callback) {delegate.enqueue(new Callback<T>() { ? ? ?@Override public void onResponse(Call<T> call, final Response<T> response) {callbackExecutor.execute(new Runnable() {...});}});} }可以看到ExecutorCallbackCall在enqueue方法中,添加了一層回調,用自定義線程(通常就是主線程)執行器執行外部callback,而在CallAdapter.adapt函數直接返回ExecutorCallbackCall的新實例就可以了,也就是動態代理中提到的這句:
return serviceMethod.callAdapter.adapt(okHttpCall);這樣在適配器的幫助下既可以增強擴展添加新功能,又不會增加使用者代碼量。比如你希望在網絡回調時統一處理一些錯誤碼,或者希望與RxJava結合使用,又或者希望單獨處理cancel函數等等。這些都可以通過適配器來將Retrofit返回的Call適配成你想要的類。
然而還存在個問題,適配器的adapt方法是在Retrofit內部調用的,它怎么知道使用者要用哪個或哪幾個適配器呢?使用者如何設置自己的適配器呢?這就引出了下面要介紹的工廠模式。
Retrofit中的工廠模式
工廠模式分為簡單工廠模式、工廠方法模式和抽象工廠模式。應用場景大部分是需要根據不同類型來生成不同對象時使用。剛接觸工廠模式時,以為這三種模式一個比一個高級,是層層遞進的關系。然而并不是,簡單工廠模式的確是最簡單的一種,但工廠方法模式和抽象工廠模式應該屬于平級,只是為了解決不同維度的問題而存在。
簡單工廠模式就是依據變化封裝的原則,將生產對象的部分封裝在工廠內部,根據不同需求返回不同類型實例,結構簡單但擴展起來麻煩,需要對工廠類進行修改。因此生產的類型一旦變多,就需要工廠方法模式了,將工廠定義成一個接口(或抽象類),每新增一類產品就新增一個工廠實例即可,完全符合開放關閉原則,滿足大多數情況的需求。而抽象工廠模式適用于多個產品樹的情況,比如原本工廠方法模式可以生產轎車、越野車和跑車,但這時候新增了一個產品樹:電動轎車、電動越野車和電動跑車,就需要用到抽象工廠模式了,但這種模式對新增產品族,比如新增了商務車,修改起來較復雜。
上面談了適配器adapter的作用,而適配器的產生就是由工廠模式來完成的,見類圖:?
圖中紅框就是工廠方法模式,CallAdapter的生產由CallAdapter.Factory這個接口定義,包含了一個get函數,會返回一個CallAdapte,至于是個什么樣的CallAdapter則由子類來實現。比如上面講適配器時提到的將OkHttpCall轉換成ExecutorCallbackCall的適配器,就是由這個ExecutorCallAdapterFacotry生產的。工廠方法模式重點就在于將方法抽象為接口或父類,利用繼承關系和子類的差異化創建不同的Adapter,從而將默認生成的OkHttpCall轉換成你所需要的各種類型。
談了這么多還是感覺不到這些設計模式的作用嗎?沒關系,來看下我們拓展后的類圖:?
圖中灰色的就是默認的和擴展的工廠模塊。除了Retrofit默認提供的ExecutorCallAdapterFactory和ExecutorCallbackCall以外,我們還可以擴展出自己的Call和Factory,比如圖中的GACall和GACallAdapterFacotry,我這里擴展的GACall修改了cancel()的行為,調用cancel()之后就會切除callback在IO線程中的引用,不再收到回調,從而方便處理頁面銷毀后網絡請求才收到返回的情形。當然你還能擴展出其他Factory、Call和Callback(比如RxJava對Retrofit專門實現了一個Factory,直接拿來用就行了),只要記得將你的Factory添加到Retrofit類的adapterFactories列表中就好。
但用戶添加了這么多工廠,真正生產網絡請求實例時,要用哪個工廠呢?仔細看工廠接口的get方法:
public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);第一個參數是returnType,也就是網絡請求返回的數據類型:
public interface GitHubService { ?@GET("users/{user}/repos") ?Call<List<Repo>> listRepos(@Path("user") String user); ?@GET("users/{user}/repos") ?GACall<List<Repo>> listRepos2(@Path("user") String user); }上面請求聲明的返回類型分別是Call和GACall,工廠會根據傳入的returnType來分辨是否屬于自己的生產范圍,于是returnType為Call就會由Retrofit默認的工廠生產Adapter,returnType為用戶自定義的類型(如GACall),則由用戶定義的工廠(如GACallAdapterFacotry)生產Adapter。
以上就是本人結合了之前看的設計模式,分析了一些Retrofit源碼拿來和大家分享。大體思路就是先用反射代理幫用戶生產請求實例,再由適配器轉換成用戶期望的類型,而這個適配器是通過工廠方法模式讓用戶無限擴展和自定義的。其實深究下去里面還有很多設計模式的體現,這次就先挑這三種具有代表性的好了。只要我們留意身邊的源代碼,就會發現別人巧妙的設計無處不在。
·END·
推薦閱讀
Android View的事件分發機制解析
跨語言編程問題迎刃而解的3個要點
網易云信∣真正穩定的IM云服務
http://netease.im ?長按識別,關注精彩
總結
以上是生活随笔為你收集整理的别人家SDK的设计模式——Android Retrofit库源码解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网易汪源: 网易云将如何激活 互联网+产
- 下一篇: 100offer接入云信专线电话,实现H