Android開發:從設計者角度看Retrofit原理

作者:Bezier

前言

通常我不喜歡去寫分析原始碼類的文章,流水線式的分析 枯燥乏味,但讀完Retrofit原始碼後讓我有了改變這種想法的衝動~~

一般來講讀原始碼的好處有兩點:

熟悉程式碼設計流程,使用過程碰到問題可以更快速解決。說實話僅這一點無法激起我讀原始碼的興趣,畢竟以正確的姿態使用一個優秀的框架不應該出現這種問題。

一個優秀的框架必須要保證易用性、擴充套件性,所以作者定會引入大量的思考進行設計,如若我們能吸收一二,那何嘗不是與作者進行了一次心靈互動呢!

今天我將帶著我的理解,嘗試從設計者的角度分析Retrofit原理,相信你認真讀完再加以思考,當再被面試官問Retrofit時你的答覆或許會讓他眼前一亮

提示:Retrofit基於2。9。0。文中貼的原始碼可能會有部分缺失,這是我刻意為之,目的在於篩選掉無用資訊增強可讀性

目錄

1。 什麼是REST ful API?

2。 為什麼將請求設定為(介面+註解)形式?

2。1。 迪米特法則和門面模式

2。2。 為什麼透過門面模式設計ApiService?

3。 動態代理其實不是工具

3。1。 Retrofit構建

3。2。 何為動態代理?

3。3。 動態代理獲取ApiService

4。 ReturnT、ResponseT做一次適配的意義何在?

4。1 建立HttpServiceMethod

4。2 如何管理callAdapter、responseConverter?

4。3 發起請求

1。 什麼是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將是你的不二選擇

2。 為什麼將請求設定為(介面+註解)形式?

該小節為前置知識

2。1 迪米特法則和門面模式

迪米特法則:

也稱之為最小知道原則,即模組之間儘量減少不必要的依賴,即降低模組間的耦合性。

門面模式:

基於迪米特法則拓展出來的一種設計模式,旨在將複雜的模組/系統訪問入口控制的更加單一。舉個例子:現要做一個獲取圖片功能,優先從本地快取獲取,沒有快取從網路獲取隨後再加入到本地快取,假如不做任何處理,那每獲取一張圖片都要寫一遍快取邏輯,寫的越多出錯的可能就越高,其實呼叫者只是想獲取一張圖片而已,具體如何獲取他不需要關心。此時可以透過門面模式將快取功能做一個封裝,只暴露出一個獲取圖片入口,這樣呼叫者使用起來更加方便而且安全性更高。其實函數語言程式設計也是門面模式的產物

2。2 為什麼透過門面模式設計ApiService?

Retrofit

做一次請求大致流程如下:

interface ApiService {    /**     * 獲取首頁資料     */    @GET(“/article/list/{page}/json”)    suspend fun getHomeList(@Path(“page”) pageNo: Int)    : ApiResponse}//構建Retrofitval 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也使用了這種模式

3。 動態代理其實不是工具

看過很多Retrofit相關的文章,都喜歡上來就拋動態代理,關於為什麼用隻字不提,搞的

Retrofit

動態代理像是一個工具(框架)一樣,殊不知它只是代理模式思想層面的一個產物而已。本小結會透過

Retrofit

看動態代理本質,幫你解除對它的誤解

3。1 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 callAdapterFactories = new ArrayList<>(this。callAdapterFactories);        callAdapterFactories。addAll(platform。defaultCallAdapterFactories(callbackExecutor));        //2。Converter工廠集合        List 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處兩個集合非常重要,這裡先埋個伏筆後面我們再回來看

3。2 何為動態代理?

什麼是代理模式?

代理模式概念非常簡單,比如A想做一件事可以讓B幫他做,這樣做的好處是什麼?下面透過一個例子簡要說明。需求:每一次本地資料庫CRUD都要做一次上報

最簡單粗暴的方式就是每次CRUD時都單獨做一次記錄,程式碼如下

//業務層方法test1fun test1{    //資料庫插入操作    dao。insert()    //上報    post()}//業務層方法test2fun test2(){    //資料庫更新操作    dao。update()    //上報    post()}

以上這種方式存在一個問題:

上報操作本身與具體業務無關,一旦需要對上報進行修改,那就可能影響到業務,進而可能造成不可預期的問題產生

面對以上問題可以透過代理模式完美規避,改造後的程式碼如下:

class DaoProxy(){    //資料庫插入操作    fun insert(){        dao。insert()        //上報        post()    }    //資料庫更新操作    fun update(){        dao。update()        //上報        post()    }}//業務層方法test1fun test1{    //資料庫插入操作    daoProxy。insert()}//業務層方法test2fun 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): Any {            //執行Dao各個方法(CRUD)            val result = p1。invoke(proxyObject,p2)            //上報            post()            return result        }    }}//此處規範上應該使用基於介面而非實現程式設計。如果要替換Dao透過介面程式設計可提高擴充套件性val dao:Dao = DaoProxy()。createProxy() as Daodao。insert()dao。update()

其中Proxy是JDK中用於建立動態代理的類,

InvocationHandler

是一個委託類, 內部的

invoke

(代理方法)方法會隨著目標類(Dao)任一方法的呼叫而呼叫,所以在其內部實現上報操作即可消除大量模版程式碼。

動態代理與靜態代理核心思想一致,區別是動態代理可以在執行時透過反射動態建立一個切面(

InvocationHandler#invoke

),用來消除模板程式碼。喜歡思考的同學其實已經發現,代理模式符合面向切面程式設計(AOP)思想,而代理類就是切面

3。3 動態代理獲取ApiService

2。2小節有提到可以透過

retrofit。create()

建立

ApiService

,跟一下

retrofit

create()

#Retrofit。class

public  T create(final Class 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  ServiceMethod 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 call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);    return adapt(call, args);  }

建立了一個

OkHttpCall

例項,它內部其實就是對OkHttp的一系列操作,這裡先按住不表後面我會再提到。把關注點切到返回值,返回的Call物件沒做任何操作,而是傳入到

adapter()

方法一併返回來,字面意思應該是一個適配操作,那究竟如何適配?這裡再埋一個伏筆與3。1結尾相呼應,下一小節我們再一一揭開。

動態代理講完了,那麼它解決了什麼問題?

假如不使用代理模式,那關於

ApiService

中方法註解解析的操作勢必會浸入到業務當中,一旦對其修改就有可能影響到業務,其實也就是也違背了我們前面所說的門面模式和迪米特法則,透過代理模式做一個切面操作(AOP)可以完美規避了這一問題。可見這裡的門面模式和代理模式是相輔相成的

Retrofit

事先都不知道

ApiService

方法數量,就算知道也避免不了逐一解析而產生大量的模版程式碼,此時可透過引入動態代理在執行時動態解析 從而解決這一問題。

4。 ReturnT、ResponseT做一次適配的意義何在?

ResponseT

ReturnT

Retrofit

對響應資料型別和返回值型別的簡稱

4。1 建立HttpServiceMethod

上一小節我們跟到了

adapter()

,這是一個抽象方法,其實現類是透過

HttpServiceMethod

parseAnnotations

建立的,繼續跟下去:

#HttpServiceMethod。class

static  HttpServiceMethod 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。                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 callAdapter =                createCallAdapter(retrofit, method, adapterType, annotations);        Type responseType = callAdapter。responseType();        //3。建立responseConverter        Converter 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)                    new HttpServiceMethod。SuspendForResponse<>(                            requestFactory,                            callFactory,                            responseConverter,                            (CallAdapter>) callAdapter);        } else {            //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object。            return (HttpServiceMethod)                    new HttpServiceMethod。SuspendForBody<>(                            requestFactory,                            callFactory,                            responseConverter,                            (CallAdapter>) 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()

,具體程式碼就不貼了,感興趣的自行檢視

4。2 如何管理callAdapter、responseConverter?

建立建立callAdapter

#HttpServiceMethod。class

private static  CallAdapter createCallAdapter(            Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {        return (CallAdapter) 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

的介面卡,假如你想讓Retrofit配合RxJava使用,常規方式只能在業務中單獨建立

Observable

並與

Call

融合,關於

Observable

與Call融合(適配)其實是與業務無關的,此時可以引入介面卡模式將Call適配成

Observable

,將適配細節從業務層挪到Retrofit內部,符合迪米特法則

策略模式

透過

ReturnT

獲取對應的

CallAdapter

,如果

ReturnT

Call

那獲取的是

DefaultCallAdapterFactory

建立的例項,如果是

Observable

則獲取的是

RxJava2CallAdapterFactory

建立的例項。假如想新增一種介面卡只需明確ReturnT,建立對應工廠再透過

addCallAdapterFactory

新增即可,Retrofit會透過ReturnT自動尋找對應

CallAdapter

,符合開閉原則(擴充套件開放)

建立responseConverter

關於

responseConverter

其實是做資料轉換的,可以將ResponseT適配成我們想要的資料型別,比如Gson解析只需透過

addConverterFactory

新增

GsonConverterFactory

建立的

Converter

例項即可 具體新增、獲取流程與

CallAdapter

基本一致,感興趣的同學可自行檢視

4。3 發起請求

到上一小結我們已經建立了所有需要的內容,再回到

HttpServiceMethod

的invoke,這裡會將OkHttpCall傳入到adapt執行並返回,

HttpServiceMethod

的實現類的

adapter

會執行對應

CallAdapter

adapter

我們就取預設的

CallAdapter

DefaultCallAdapterFactory

透過get獲取的

CallAdapte

r,程式碼如下:

DefaultCallAdapterFactory。classpublic @Nullable CallAdapter<?, ?> get(        return new CallAdapter>() {            @Override            public Type responseType() {                return responseType;            }            @Override            public Call adapt(Call call) {                return executor == null ? call : new DefaultCallAdapterFactory。ExecutorCallbackCall<>(executor, call);            }        };    }

內部

adapt

ApiService method

最終返回的

ExecutorCallbackCall

OkHttpCall

裝飾類,最後可透過

OkHttpCall

execute

發起請求,程式碼如下:

#OkHttpCall。class

public Response execute() throws IOException {        okhttp3。Call call;        。。。        return parseResponse(call。execute());    }

OkHttp常規操作,再把關注點放到

onResponse

parseResponse

#OkHttpCall。class

Response 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學習PDF+架構影片+面試文件+原始碼筆記

高階架構技術進階腦圖、Android開發面試專題資料,高階進階架構資料

這些都是我現在閒暇時還會反覆翻閱的精品資料。裡面對近幾年的大廠面試高頻知識點都有詳細的講解。相信可以有效地幫助大家掌握知識、理解原理,幫助大家在未來取得一份不錯的答卷。

當然,你也可以拿去查漏補缺,提升自身的競爭力。

如果你有需要的話,只需

私信我【進階】即可獲取

Android開發:從設計者角度看Retrofit原理

Android開發:從設計者角度看Retrofit原理

Android開發:從設計者角度看Retrofit原理