入木三分:從設計者角度看Retrofit原理
作者:Bezier
連結:https://juejin.cn/post/6963202606676049957
前言
通常我不喜歡去寫分析原始碼類的文章,流水線式的分析 枯燥乏味,但讀完Retrofit原始碼後讓我有了改變這種想法的衝動~~
一般來講讀原始碼的好處有兩點:
-
熟悉程式碼設計流程,使用過程碰到問題可以更快速解決。說實話僅這一點無法激起我讀原始碼的興趣,畢竟以正確的姿態使用一個優秀的框架不應該出現這種問題。
-
一個優秀的框架必須要保證易用性、擴充套件性,所以作者定會引入大量的思考進行設計,如若我們能吸收一二,那何嘗不是與作者進行了一次心靈互動呢!
今天我將帶著我的理解,嘗試從設計者的角度分析Retrofit原理,相信你認真讀完再加以思考,當再被面試官問Retrofit時你的答覆或許會讓他眼前一亮
提示:Retrofit基於2.9.0。文中貼的原始碼可能會有部分缺失,這是我刻意為之,目的在於篩選掉無用資訊增強可讀性
什麼是REST ful API?
一句話概括REST ful API:在我們使用HTTP協議做資料傳輸時應當遵守HTTP的規矩,包括請求方法、資源型別、Uri格式等等..
不久前在群裡看到某小夥伴提出一個問題:“應後端要求需要在GET請求加入Body但Retrofit 中GET 請求新增Body會報錯,如何解決?” 一時間討論的好不熱鬧,有讓把Body塞到Header裡的,有讓自定義攔截器、也有人直接慫恿改原始碼...但問題的本質不是後端先違反規則在先嗎?兩個人打架總不能把捱打的抓起來吧。
俗話說無規矩不成方圓,面對以上這種情況應當讓錯誤方去修改,因為所有人都知道GET沒有Body,否則一旦其他人接手你的程式碼很容易被搞懵。
Retrofit對REST ful API的相容做的很優秀,不符合規範直接給你報錯,強行規範你的程式碼。所以你們公司正在使用REST ful API而Retrofit將是你的不二選擇
為什麼將請求設定為(介面+註解)形式?
迪米特法則和門面模式
-
迪米特法則:也稱之為最小知道原則,即模組之間儘量減少不必要的依賴,即降低模組間的耦合性。
-
門面模式:基於迪米特法則拓展出來的一種設計模式,旨在將複雜的模組/系統訪問入口控制的更加單一。 舉個例子:現要做一個獲取圖片功能,優先從本地快取獲取,沒有快取從網路獲取隨後再加入到本地快取,假如不做任何處理,那每獲取一張圖片都要寫一遍快取邏輯,寫的越多出錯的可能就越高,其實呼叫者只是想獲取一張圖片而已,具體如何獲取他不需要關心。此時可以通過門面模式將快取功能做一個封裝,只暴露出一個獲取圖片入口,這樣呼叫者使用起來更加方便而且安全性更高。其實函數語言程式設計也是門面模式的產物
為什麼通過門面模式設計ApiService?
用Retrofit做一次請求大致流程如下:
interface ApiService { /** * 獲取首頁資料 */ @GET("/article/list/{page}/json") suspend fun getHomeList(@Path("page") pageNo: Int) : ApiResponse<ArticleBean> } //構建Retrofit val retrofit = Retrofit.Builder().build() //建立ApiService例項 val apiService =retrofit.create(ApiService::class.java) //發起請求(這裡用的是suspend會自動發起請求,Java中可通過返回的call請求) apiService.getHomeList(1)
然後通過Retrofit建立ApiService型別例項呼叫對應方法即可發起請求。乍一看感覺很普通,但實際上Retrofit通過這種模式(門面模式)幫我們過濾掉了很多無用資訊
tips:我們都知道Retrofit只不過是對OkHttp做了封裝。
如果直接使用OkHttp,當在構造Request時要做很多繁瑣的工作,最要命的是Request可能在多處被構造(ViewModel、Repository...),寫的越分散出錯時排查的難度就越高。而Retrofit通過註解的形式將Request需要的必要資訊全依附在方法上(還是個抽象方法,儘量撇除一切多餘資訊),作為使用者只需要呼叫對應方法即可實現請求。至於如何解析、構造、發起請求 Retrofit內部會做處理,呼叫者不想也不需要知道,
所以Retrofit通過門面模式幫呼叫者遮蔽了一些無用資訊,只暴露出唯一入口,讓呼叫者更專注於業務開發。像我們常用的Room、GreenDao也使用了這種模式
動態代理其實不是工具
看過很多Retrofit相關的文章,都喜歡上來就拋動態代理,關於為什麼用隻字不提,搞的Retrofit動態代理像是一個工具(框架)一樣,殊不知它只是代理模式思想層面的一個產物而已。本小結會透過Retrofit看動態代理本質,幫你解除對它的誤解
Retrofit構建
Retrofit構建如下所示:
Retrofit.Builder() .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(ApiConstants.BASE_URL) .build()
很典型的構建者模式,可以配置OkHttp、Gson、RxJava等等,最後通過build()做構建操作,跟一下build()程式碼:
#Retrofit.class public Retrofit build() { //1.CallAdapter工廠集合 List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories); callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor)); //2.Converter工廠集合 List<Converter.Factory> converterFactories = new ArrayList<>( 1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize()); converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this.converterFactories); converterFactories.addAll(platform.defaultConverterFactories()); return new Retrofit( callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly); }
將一些必要資訊注入到Retrofit並建立返回。註釋1、2處兩個集合非常重要,這裡先埋個伏筆後面我們再回來看
何為動態代理?
什麼是代理模式?
代理模式概念非常簡單,比如A想做一件事可以讓B幫他做,這樣做的好處是什麼?下面通過一個例子簡要說明。需求:每一次本地資料庫CRUD都要做一次上報
最簡單粗暴的方式就是每次CRUD時都單獨做一次記錄,程式碼如下
//業務層方法test1 fun test1{ //資料庫插入操作 dao.insert() //上報 post() } //業務層方法test2 fun test2(){ //資料庫更新操作 dao.update() //上報 post() }
以上這種方式存在一個問題:
上報操作本身與具體業務無關,一旦需要對上報進行修改,那就可能影響到業務,進而可能造成不可預期的問題產生
面對以上問題可以通過代理模式完美規避,改造後的程式碼如下:
class DaoProxy(){ //資料庫插入操作 fun insert(){ dao.insert() //上報 post() } //資料庫更新操作 fun update(){ dao.update() //上報 post() } } //業務層方法test1 fun test1{ //資料庫插入操作 daoProxy.insert() } //業務層方法test2 fun test2(){ //資料庫更新操作 daoProxy.update() }
新增一個代理類DaoProxy,將dao以及上報操作在代理類中執行,業務層直接操作代理物件,這樣就將上報從業務層抽離出來,從而避免業務層改動帶來的問題。實際使用代理模式時應遵守基於介面而非實現程式設計思想,但文章側重於傳授思想,規範上可能欠缺
此時還有一個問題,每次CRUD都會手動做一次上報操作,這顯然是模版程式碼,如何解決?下面來看動態代理:
什麼是動態代理?
java中的動態代理就是在執行時通過反射為目標物件做一些附加操作,程式碼如下:
class DaoProxy() { //建立代理類 fun createProxy(): Any { //建立dao val proxyAny = Dao() val interfaces = proxyAny.javaClass.interfaces val handler = ProxyHandler(proxyAny) return Proxy.newProxyInstance(proxyAny::class.java.classLoader, interfaces, handler) } //代理委託類 class ProxyHandler(private val proxyObject:Any): InvocationHandler { //代理方法,p1為目標類方法、p2為目標類引數。呼叫proxyObject任一方法時都會執行invoke override fun invoke(p0: Any, p1: Method, p2: Array<out Any>): Any { //執行Dao各個方法(CRUD) val result = p1.invoke(proxyObject,p2) //上報 post() return result } } } //此處規範上應該使用基於介面而非實現程式設計。如果要替換Dao通過介面程式設計可提高擴充套件性 val dao:Dao = DaoProxy().createProxy() as Dao dao.insert() dao.update()
其中Proxy是JDK中用於建立動態代理的類,InvocationHandler是一個委託類, 內部的invoke(代理方法)方法會隨著目標類(Dao)任一方法的呼叫而呼叫,所以在其內部實現上報操作即可消除大量模版程式碼。
動態代理與靜態代理核心思想一致,區別是動態代理可以在執行時通過反射動態建立一個切面(InvocationHandler#invoke),用來消除模板程式碼。喜歡思考的同學其實已經發現,代理模式符合面向切面程式設計(AOP)思想,而代理類就是切面
動態代理獲取ApiService
可以通過retrofit.create()建立ApiService,跟一下retrofit的create()
#Retrofit.class public <T> T create(final Class<T> service) { //第一處 validateServiceInterface(service); return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] {service}, new InvocationHandler() { //第二處 @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ... return platform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); } }); }
create()大致可以分為兩部分:
-
第一部分為validateServiceInterface()內容,用來驗證ApiService合法性,比較簡單就不多描述,感興趣的同學可自行檢視。
-
第二部分就是invoke(),通過3.2小節可知這是一個代理方法,可通過呼叫ApiService中的任一方法執行,其中引數method和args代表ApiService對應的方法和引數。返回值中有一個isDefaultMethod,這裡如果是Java8的預設方法直接執行,畢竟我們只需要代理ApiService中方法即可。經過反覆篩選最後重任落在了loadServiceMethod,這也是Retrofit中最核心的一個方法,下面我們來跟一下
#Retrofit.class ServiceMethod<?> loadServiceMethod(Method method) { ServiceMethod<?> result = serviceMethodCache.get(method); if (result != null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); } } return result; }
大致就是對ServiceMethod做一個很常見的快取操作,這樣做的目的是為了提升執行效率,畢竟建立一個ServiceMethod會用到大量反射。建立ServiceMethod物件是通過其靜態方法parseAnnotations實現的,再跟一下這個方法:
#ServiceMethod.class static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) { //第一步 RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); Type returnType = method.getGenericReturnType(); ... //第二步 return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory); }
第一步:
通過RequestFactory的parseAnnotations()解析method(ApiService的method)中的註解資訊,具體程式碼很簡單就不再貼了。不過需要注意這一步只是解析註解並儲存在RequestFactory工廠中,會在請求時再通過RequestFactory將請求資訊做拼裝。
第二步:
呼叫HttpServiceMethod的parseAnnotations建立ServiceMethod,這個方法很長並且資訊量很大,下一小節我再詳細描述,此處你只需知道它做了什麼即可。其實到這方法呼叫鏈已經很繞了,我先幫大家捋一下 HttpServiceMethod其實是ServiceMethod的子類,Retrofit動態代理裡面的loadServiceMethod就是HttpServiceMethod型別物件,最後來看一下它的invoke()方法。
#HttpServiceMethod.class @Override final @Nullable ReturnT invoke(Object[] args) { Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter); return adapt(call, args); }
建立了一個OkHttpCall例項,它內部其實就是對OkHttp的一系列操作,這裡先按住不表後面我會再提到。把關注點切到返回值,返回的Call物件沒做任何操作,而是傳入到adapter()方法一併返回來,字面意思應該是一個適配操作,那究竟如何適配?這裡再埋一個伏筆與3.1結尾相呼應,下一小節我們再一一揭開。
動態代理講完了,那麼它解決了什麼問題?
-
假如不使用代理模式,那關於ApiService中方法註解解析的操作勢必會浸入到業務當中,一旦對其修改就有可能影響到業務,其實也就是也違背了我們前面所說的門面模式和迪米特法則,通過代理模式做一個切面操作(AOP)可以完美規避了這一問題。可見這裡的門面模式和代理模式是相輔相成的
-
Retrofit事先都不知道ApiService方法數量,就算知道也避免不了逐一解析而產生大量的模版程式碼,此時可通過引入動態代理在執行時動態解析 從而解決這一問題。
ReturnT、ResponseT做一次適配的意義何在?
ResponseT、ReturnT是 Retrofit 對響應資料型別和返回值型別的簡稱
建立HttpServiceMethod
上一小節我們跟到了adapter(),這是一個抽象方法,其實現類是通過HttpServiceMethod的parseAnnotations建立的,繼續跟下去:
#HttpServiceMethod.class static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; boolean continuationWantsResponse = false; boolean continuationBodyNullable = false; Annotation[] annotations = method.getAnnotations(); Type adapterType; //1.獲取adapterType,預設為method返回值型別 if (isKotlinSuspendFunction) { Type[] parameterTypes = method.getGenericParameterTypes(); Type responseType = Utils.getParameterLowerBound( 0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]); if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) { // Unwrap the actual body type from Response<T>. responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType); continuationWantsResponse = true; } else { } adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType); annotations = SkipCallbackExecutorImpl.ensurePresent(annotations); } else { adapterType = method.getGenericReturnType(); } //2.建立CallAdapter CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); //3.建立responseConverter Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory; //4.建立HttpServiceMethod型別具體例項 if (!isKotlinSuspendFunction) { return new HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); } //相容kotlin suspend方法 else if (continuationWantsResponse) { //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object. return (HttpServiceMethod<ResponseT, ReturnT>) new HttpServiceMethod.SuspendForResponse<>( requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter); } else { //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object. return (HttpServiceMethod<ResponseT, ReturnT>) new HttpServiceMethod.SuspendForBody<>( requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); } }
-
註釋1:獲取adapterType,這裡的adapter指的是Retrofit構建時通過addCallAdapterFactory()新增的型別,如果新增的是RxJava那adapterType便是Observable。預設是method返回值,同時也會做kotlin suspend適配
-
註釋2:建立callAdapter,暫時掠過,下面詳細描述
-
註釋3:建立responseConverter,暫時掠過,下面詳細描述
-
註釋4:這裡會建立具體的HttpServiceMethod型別例項,總共有三種類型CallAdapted、SuspendForResponse、SuspendForBody,第一種為預設型別,後兩種可相容kotlin suspend。內部主要做的事情其實很簡單,就是通過內部的adapter()呼叫callAdapter adapter(),具體程式碼就不貼了,感興趣的自行檢視
如何管理callAdapter、responseConverter?
建立建立callAdapter
#HttpServiceMethod.class private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter( Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) { return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations); ... }
通過retrofit#callAdapter()獲取CallAdapter,繼續跟
#Retrofit.class public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) { return nextCallAdapter(null, returnType, annotations); } public CallAdapter<?, ?> nextCallAdapter( @Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) { int start = callAdapterFactories.indexOf(skipPast) + 1; for (int i = start, count = callAdapterFactories.size(); i < count; i++) { //通過returnType在callAdapterFactories獲取adapter工廠,再get adapter CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this); if (adapter != null) { return adapter; } } ... }
先通過returnType在callAdapterFactories獲取adapter工廠,再通過工廠get()獲取CallAdapter例項。callAdapterFactories是3.1結尾build()中初始化的,通過platform新增預設型別,也可以通過addCallAdapterFactory()新增RxJava之類的介面卡型別。
這裡用到了兩個設計模式介面卡跟策略
介面卡模式
返回的CallAdapter其實就是Call
策略模式
通過ReturnT獲取對應的CallAdapter,如果ReturnT是Call
建立responseConverter
關於responseConverter其實是做資料轉換的,可以將ResponseT適配成我們想要的資料型別,比如Gson解析只需通過addConverterFactory新增GsonConverterFactory建立的Converter例項即可 具體新增、獲取流程與CallAdapter基本一致,感興趣的同學可自行檢視
發起請求
到上一小結我們已經建立了所有需要的內容,再回到HttpServiceMethod的invoke,這裡會將OkHttpCall傳入到adapt執行並返回,HttpServiceMethod的實現類的adapter會執行對應CallAdapter的adapter 我們就取預設的CallAdapter 即DefaultCallAdapterFactory通過get獲取的CallAdapter,程式碼如下:
DefaultCallAdapterFactory.class public @Nullable CallAdapter<?, ?> get( return new CallAdapter<Object, Call<?>>() { @Override public Type responseType() { return responseType; } @Override public Call<Object> adapt(Call<Object> call) { return executor == null ? call : new DefaultCallAdapterFactory.ExecutorCallbackCall<>(executor, call); } }; }
內部adapt即ApiService method最終返回的ExecutorCallbackCall是OkHttpCall裝飾類,最後可通過OkHttpCall的execute發起請求,程式碼如下:
#OkHttpCall.class public Response<T> execute() throws IOException { okhttp3.Call call; ... return parseResponse(call.execute()); }
OkHttp常規操作,再把關注點放到onResponse的parseResponse
#OkHttpCall.class Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ... T body = responseConverter.convert(catchingBody); ... return Response.success(body, rawResponse); }
responseConverter會對Body做一個適配,如果addConverterFactory添加了GsonConvert那解析操作就會在此處進行
至此Retrofit全部流程分析完畢
綜上所述
-
Retrofit通過REST ful API從正規化層面約束程式碼
-
通過門面模式設計ApiService可以讓開發者更專注於業務
-
動態代理只是將功能程式碼從業務剝離,並解決了模板程式碼問題
-
ReturnT、ResponseT引入介面卡模式可以讓結果更加靈活
掃描二維碼
獲取更多精彩
Android補給站

點個 在看 你最好看
- 協程到底是怎麼切換執行緒的?
- 元件化開花,就問你香不香
- Kotlin 基礎 | 拒絕語法噪音
- 玩轉LayoutInflater
- 淺談Android熱更新的前因後果
- 如何學好設計,做好架構? 核心思想才是關鍵
- Kotlin 內聯類 inline class請了解一下
- Android MaterialButton使用詳解,告別shape、selector
- 入木三分:從設計者角度看Retrofit原理
- 使用Jetpack Compose完成你的自定義Layout
- 為了能夠摸魚,我走上了歧路
- 自信,這是最好的ThreadLocal分析
- Android&Kotlin編譯速度原理剖析
- Android 面試之必問Java基礎
- Kotlin 基礎 | 委託及其應用
- 引入Jetpack架構後,你的App會發生哪些變化?
- Handler那些事
- android佈局優化的幾個建議
- 面試:ViewModel為何橫豎屏切換時不銷燬?
- Android Java Zygote啟動