阿里二面:關於 Retrofit 你知道多少?看完你的offer穩了

一、整體思路

從使用方法出發,首先是怎麼使用,其次是我們使用的功能在內部是如何實現的, 實現方案上有什麼技巧,有什麼範式。全文基本上是對 Retrofit 源碼的一個分析與 導讀,非常建議大家下載 Retrofit 源碼之後,跟著本文,過一遍源碼。

阿里二面:關於 Retrofit 你知道多少?看完你的offer穩了

二、基本用例

2.1 創建 Retrofit 對象

<code>  Retrofit retrofit = 

new

Retrofit.Builder() .baseUrl(

"https://api.github.com/"

) .addConverterFactory(GsonConverterFactory.create()) .build();/<code>

2.2 定義 API 並獲取 API 實例

<code>  

public

interface

GitHubService

{ Call> listRepos( String user); } GitHubService github = retrofit.create(GitHubService

.

class

);

/<code>

先看定義,非常簡潔,也沒有什麼特別之處,除了兩個註解:@GET和 @Path 。它們的用處稍後再分析,我們接著看創建 API 實 例: retrofit.create(GitHubService.class) 。這樣就創建了 API 實例了, 就可以調用 API 的方法發起 HTTP 網絡請求了,太方便了。 但 create 方法是怎麼創建 API 實例的呢?

<code>  

public

T

create

(

final

Class service)

{

return

(T) Proxy.newProxyInstance(service.getClassLoader(),

new

Class>[] { service },

new

InvocationHandler() {

public

Object

invoke

(Object proxy, Method method, Object ... args)

throws

Throwable

{ } }); }/<code>

創建 API 實例使用的是動態代理技術。

簡而言之,就是動態生成接口的實現類(當然生成實現類有緩存機制),並創建其 實例(稱之為代理),代理把對接口的調用轉發給 InvocationHandler 實例, 而在 InvocationHandler 的實現中,除了執行真正的邏輯(例如再次轉發給真 正的實現類對象),我們還可以進行一些有用的操作,例如統計執行時間、進行初 始化和清理、對接口調用進行檢查等。 為什麼要用動態代理?因為對接口的所有方法的調用都會集中轉發到 InvocationHandler#invoke 函數中,我們可以集中進行處理,更方便了。你可 能會想,我也可以手寫這樣的代理類,把所有接口的調用都轉發到 InvocationHandler#invoke 呀,當然可以,但是可靠地自動生成豈不更方便?

2.3 調用 API 方法

獲取到 API 實例之後,調用方法和普通的代碼沒有任何區別:

<code>  Call<

List

> call = github.listRepos(

"square"

);

List

repos = call.execute().body();/<code>

這兩行代碼就發出了 HTTP 請求,並把返回的數據轉化為了 List,太方 便了!

現在我們來看看調用 listRepos 是怎麼發出 HTTP 請求的。上面 Retrofit#create 方法返回時省略的代碼如下:

<code>  

return

(T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service }, new InvocationHandler() {

private

final

Platform platform = Platform.

get

();

public

Object invoke(Object proxy, Method method, Object.. . args) throws Throwable {

if

(method.getDeclaringClass() == Object

.

class

)

{

return

method.invoke(

this

, args); }

if

(platform.isDefaultMethod(method)) {

return

platform.invokeDefaultMethod(method, service, p roxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);

return

serviceMethod.callAdapter.adapt(okHttpCall); } });/<code>

如果調用的是 Object 的方法,例如 equals, toString,那就直接調用。 如果是 default 方法(Java 8 引入),就調用 default 方法。這些我們都先不管,因 為我們在安卓平臺調用 listRepos ,肯定不是這兩種情況,那這次調用真正幹活 的就是這三行代碼了(好好記住這三行代碼,因為接下來很長的篇幅都是在講它們 :) ):

<code>   

ServiceMethod

serviceMethod = loadServiceMethod(method);

OkHttpCall

okHttpCall = new OkHttpCall<>(serviceMethod, args);

return

serviceMethod.callAdapter.adapt(okHttpCall);/<code>

在繼續分析這三行代碼之前,先看一個流程圖

阿里二面:關於 Retrofit 你知道多少?看完你的offer穩了

這三行代碼基本就是對應於流程圖中軸上部了, ServiceMethod , build OkHttpCall, CallAdapter adapt。

2.4 ServiceMethod

ServiceMethod 類的作用正如其 JavaDoc所言:

Adapts an invocation of an interface method into an HTTP call. 把對接口方法 的調用轉為一次 HTTP 調用。

一個 ServiceMethod 對象對應於一個 API interface 的一個方 法, loadServiceMethod(method) 方法負責加載 ServiceMethod :

<code>  

ServiceMethod

loadServiceMethod(Method method) {

ServiceMethod

result;

synchronized

(serviceMethodCache) {

result

=

serviceMethodCache.get(method);

if

(result == null) {

result

=

new ServiceMethod.Builder(this, method).build();

result);

}

}

return

result;

}

/<code>

這裡實現了緩存邏輯,同一個 API 的同一個方法,只會創建一次。這裡由於我們每 次獲取 API 實例都是傳入的 class 對象,而 class 對象是進程內單例的,所 以獲取到它的同一個方法 Method 實例也是單例的,所以這裡的緩存是有效的。

我們再看看 ServiceMethod 的構造函數:

<code>  ServiceMethod(Builder builder) { 
    

this

.callFactory = builder.retrofit.callFactory();

this

.callAdapter = builder.callAdapter;

this

.baseUrl = builder.retrofit.baseUrl();

this

.responseConverter = builder.responseConverter;

this

.httpMethod = builder.httpMethod;

this

.relativeUrl = builder.relativeUrl;

this

.headers = builder.headers;

this

.contentType = builder.contentType;

this

.hasBody = builder.hasBody;

this

.isFormEncoded = builder.isFormEncoded;

this

.isMultipart = builder.isMultipart;

this

.parameterHandlers = builder.parameterHandlers; }/<code>

成員很多,但這裡我們重點關注四個成 員: callFactory, callAdapter , responseConverter 和 parameterHandlers 。

1.callFactory 負責創建 HTTP 請求,HTTP 請求被抽象為了okhttp3.Call 類,它表示一個已經準備好,可以隨時執行的 HTTP 請求;


2.callAdapter 把 retrofit2.Call轉為 T (注意和 okhttp3.Call 區分開來,retrofit2.Call表示的是對一個 Retrofit 方法的調用),這個過程會發送一個 HTTP 請求,拿到服務器返回的數據(通 過 okhttp3.Call實現),並把數據轉換為聲明的 T 類型對象(通過 Converter 實現);


3.responseConverter 是 Converter 類型,負責把服 務器返回的數據(JSON、XML、二進制或者其他格式,由 ResponseBody封裝)轉化為 T 類型的對象;


4.parameterHandlers 則負責解析 API 定義時每個方法的參數,並在構造 HTTP 請求時設置參數;

它們的使用稍後再分析,這裡先看看它們的創建(代碼比較分散,就不貼太多代碼 了,大多是結論):

2.4.1 callFactory

this.callFactory =
builder.retrofit.callFactory(),所以 callFactory 實際上由 Retrofit 類提供,而我們在構造 Retrofit 對象 時,可以指定 callFactory,如果不指定,將默認設置為一個 okhttp3.OkHttpClient。

2.4.2 callAdapter

<code>  

private

CallAdapter> createCallAdapter() { Annotation[] annotations = method.getAnnotations();

try

{

return

retrofit.callAdapter(returnType, annotations); }

catch

(RuntimeException e) {

throw

methodError(e,

"Unable to create call adapter for %s"

, returnType); } }/<code>

可以看到, callAdapter 還是由 Retrofit 類提供。在 Retrofit 類內部, 將遍歷一個 CallAdapter.Factory 列表,讓工廠們提供,如果最終沒有工廠能 (根據 returnType 和 annotations)提供需要的 CallAdapter ,那將拋出 異常。而這個工廠列表我們可以在構造 Retrofit 對象時進行添加。

2.4.3, responseConverter

<code>  

private

Converter

createResponseConverter

(

)

{ Annotation[] annotations = method.getAnnotations();

try

{

return

retrofit.responseBodyConverter(responseType, annotati ons); }

catch

(RuntimeException e) {

throw

methodError(e,

"Unable to create converter for %s"

, re sponseType); } }/<code>

同樣, responseConverter 還是由 Retrofit 類提供,而在其內部,邏輯和創 建 callAdapter 基本一致,通過遍歷 Converter.Factory列表,看看有沒有 工廠能夠提供需要的 responseBodyConverter。工廠列表同樣可以在構造 Retrofit 對象時進行添加。

2.4.4 parameterHandlers

每個參數都會有一個 ParameterHandler ,由 ServiceMethod#parseParameter 方法負責創建,其主要內容就是解析每個參數 使用的註解類型(諸如 Path , Query , Field 等),對每種類型進行單獨的 處理。構造 HTTP 請求時,我們傳遞的參數都是字符串,那 Retrofit 是如何把我們 傳遞的各種參數都轉化為 String 的呢?還是由 Retrofit 類提供 converter!


Converter.Factory 除了提供上一小節提到的 responseBodyConverter,還提 供 requestBodyConverter和 stringConverter,API 方法中除了 @Body 和 @Part 類型的參數,都利用 stringConverter 進行轉換,而 @Body 和 @Part 類型的參數則利用 requestBodyConverter 進行轉換。


這三種 converter 都是通過“詢問”工廠列表進行提供,而工廠列表我們可以在構造 Retrofit 對象時進行添加。

2.4.5 工廠讓各個模塊得以高度解耦

上面提到了三種工廠: okhttp3.Call.Factory , CallAdapter.Factory 和 Converter.Factory ,分別負責提供不同的模塊,至於怎麼提供、提供何種模 塊,統統交給工廠,Retrofit 完全不摻和,它只負責提供用於決策的信息,例如參 數/返回值類型、註解等。


這不正是我們苦苦追求的高內聚低耦合效果嗎?解耦的第一步就是面向接口編程, 模塊之間、類之間通過接口進行依賴,創建怎樣的實例,則交給工廠負責,工廠同 樣也是接口,添加(Retrofit doc 中使用 install 安裝一詞,非常貼切)怎樣的工 廠,則在最初構造 Retrofit 對象時決定,各個模塊之間完全解耦,每個模塊只 專注於自己的職責,全都是套路,值得反覆玩味、學習與模仿。


除了上面重點分析的這四個成員, ServiceMethod 中還包含了 API 方法的 url 解 析等邏輯,包含了眾多關於泛型和反射相關的代碼,有類似需求的時候,也非常值 得學習模仿

2.5 OkHttpCall

終於把 ServiceMethod 看了個大概,接下來我們看看 OkHttpCall 。 OkHttpCall 實現了 retrofit2.Call ,我們通常會使用它的 execute() 和 enqueue(Callback callback) 接口。前者用於同步執行 HTTP 請求,後者 用於異步執行。

2.5.1,先看 execute()

<code>    
  

public

Response

execute

()

throws

IOException

{ okhttp3.Call call;

synchronized

(

this

) { call = rawCall;

if

(call ==

null

) {

try

{ call = rawCall = createRawCall(); }

catch

(IOException | RuntimeException e) { creationFailure = e;

throw

e; } } }

return

parseResponse(call.execute()); ...... }/<code>

主要包括三步:

創建 okhttp3.Call ,包括構造參數;執行網絡請求;解析網絡請求返回的數據;

createRawCall() 函數中,我們調用了 serviceMethod.toRequest(args) 來創建 okhttp3.Request ,而在後者中,我們之前準備好的 parameterHandlers 就派上了用場。


然後我們再調用
serviceMethod.callFactory.newCall(request) 來創建 okhttp3.Call ,這裡之前準備好的 callFactory 同樣也派上了用場,由於工 廠在構造 Retrofit 對象時可以指定,所以我們也可以指定其他的工廠(例如使 用過時的 HttpURLConnection 的工廠),來使用其它的底層 HttpClient 實現。


我們調用 okhttp3.Call#execute() 來執行網絡請求,這個方法是阻塞的,執行 完畢之後將返回收到的響應數據。收到響應數據之後,我們進行了狀態碼的檢查, 通過檢查之後我們調用了 serviceMethod.toResponse(catchingBody) 來把響 應數據轉化為了我們需要的數據類型對象。在 toResponse 函數中,我們之前準 備好的 responseConverter 也派上了用場。


好了,之前準備好的東西都派上了用場,還好沒有白費 :)

2.5.2 再看 enqueue(Callback callback)

這裡的異步交給了 okhttp3.Call#enqueue(Callback responseCallback) 來 實現,並在它的 callback 中調用 parseResponse 解析響應數據,並轉發給傳入 的 callback。

2.6 CallAdapter

終於到了最後一步了, CallAdapter#adapt(Call call) 函數負責把 retrofit2.Call 轉為 T 。這裡 T 當然可以就是 retrofit2.Call ,這時我們直接返回參數就可以了,實際上這正是 DefaultCallAdapterFactory創建的 CallAdapter 的行為。至於其他類型的 工廠返回的 CallAdapter 的行為,這裡暫且不表,後面再單獨分析。


至此,一次對 API 方法的調用是如何構造併發起網絡請求、以及解析返回數據,這 整個過程大致是分析完畢了。對整個流程的概覽非常重要,結合 stay 畫的流程圖, 應該能夠比較輕鬆地看清整個流程了。


雖然我們還沒分析完,不過也相當於到了萬里長征的遵義,終於可以舒一口氣了 :)

三、retrofit-adapters 模塊

retrofit 模塊內置了 DefaultCallAdapterFactory 和
ExecutorCallAdapterFactory ,它們都適用於 API 方法得到的類型為 retrofit2.Call 的情形,前者生產的 adapter 啥也不做,直接把參數返回,後 者生產的 adapter 則會在異步調用時在指定的 Executor 上執行回調。


retrofit-adapters 的各個子模塊則實現了更多的工 廠: GuavaCallAdapterFactory , Java8CallAdapterFactory 和 RxJavaCallAdapterFactory 。這裡我主要分析 RxJavaCallAdapterFactory ,下面的內容就需要一些 RxJava 的知識了,不過 我想使用 Retrofit 的你,肯定也在使用RxJava :)


RxJavaCallAdapterFactory#get 方法中對返回值的類型進行了檢查,只支持 rx.Single , rx.Completable 和 rx.Observable ,這裡我主要關注對 rx.Observable 的支持。


RxJavaCallAdapterFactory#getCallAdapter 方法中對返回值的泛型類型進行 了進一步檢查,例如我們聲明的返回值類型為 Observable>,泛型 類型就是 List ,這裡對 retrofit2.Response 和
retrofit2.adapter.rxjava.Result 進行了特殊處理,有單獨的 adapter 負責 進行轉換,其他所有類型都由 SimpleCallAdapter負責轉換。


那我們就來看看 SimpleCallAdapter#adapt :

<code>    
  

public

Observable

adapt

(Call call)

{ Observable observable = Observable.create(

new

CallOnSubscri be<>(call)) .lift(OperatorMapResponseToBodyOrError.instance());

if

(scheduler !=

null

) {

return

observable.subscribeOn(scheduler); }

return

observable; }/<code>

這裡創建了一個 Observable ,它的邏輯由 CallOnSubscribe 類實現,同時使 用了一個
OperatorMapResponseToBodyOrError 操作符,用來把 retrofit2.Response 轉為我們聲明的類型,或者錯誤異常類型。

我們接著看 CallOnSubscribe#call :

<code>    
  

public

void

call

(

final

Subscriber

super

Response> subscribe r)

{ Call call = originalCall.clone(); RequestArbiter requestArbiter =

new

RequestArbiter<>(call, subscriber); subscriber.add(requestArbiter); subscriber.setProducer(requestArbiter); }/<code>

代碼很簡短,只幹了三件事:

clone 了原來的 call,因為 okhttp3.Call 是隻能用一次的,所以每次都是 新 clone 一個進行網絡請求;創建了一個叫做 RequestArbiter 的 producer,別被它的名字嚇懵了,它就 是個 producer;把這個 producer設置給 subscriber;

簡言之,大部分情況下 Subscriber 都是被動接受 Observable push 過來的數據, 但要是 Observable發得太快,Subscriber 處理不過來,那就有問題了,所以就有 了一種 Subscriber 主動 pull 的機制,而這種機制就是通過 Producer 實現的。給 Subscriber 設置 Producer 之後(通過 Subscriber#setProducer 方法), Subscriber 就會通過 Producer 向上遊根據自己的能力請求數據(通過 Producer#request 方法),而 Producer 收到請求之後(通常都是 Observable 管理 Producer,所以“相當於”就是 Observable 收到了請求),再根據請求的量給 Subscriber 發數據。

那我們就看看 RequestArbiter#request:

<code>    
  

public

void

request

(

long

n)

{

if

(n

0

)

throw

new

IllegalArgumentException(

"n < 0: "

+ n);

if

(n ==

0

)

return

;

if

(!compareAndSet(

false

,

true

))

return

;

try

{ Response response = call.execute();

if

(!subscriber.isUnsubscribed()) { subscriber.onNext(response); } }

catch

(Throwable t) { Exceptions.throwIfFatal(t);

if

(!subscriber.isUnsubscribed()) { subscriber.onError(t); }

return

; }

if

(!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }/<code>

producer 相關的邏輯非常簡單,這裡就不在贅述。實際幹活的邏輯就是執行 call.execute() ,並把返回值發送給下游。


OperatorMapResponseToBodyOrError#call 也相當簡短:

<code>    
  

public

Subscriber

super

Response> call(

final

Subscriber s uper T> child) {

return

new

Subscriber>(child) {

public

void

onNext

(Response response)

{

if

(response.isSuccessful()) { child.onNext(response.body()); }

else

{ child.onError(

new

HttpException(response)); } }

public

void

onCompleted

()

{ child.onCompleted(); }

public

void

onError

(Throwable e)

{ child.onError(e); } }; }/<code>

關鍵就是調用了 response.body() 併發送給下游。這裡, body() 返回的就是 我們聲明的泛型類型了,至於 Retrofit 是怎麼把服務器返回的數據轉為我們聲明的 類型的,這就是 responseConverter的事了,還記得嗎?

最後看一張返回 Observable 時的調用棧:

阿里二面:關於 Retrofit 你知道多少?看完你的offer穩了

執行路徑就是:

Observable.subscribe ,觸發 API 調用的執行;CallOnSubscribe#call ,clone call,創建並設置 producer;RequestArbiter#request ,subscriber 被設置了 producer 之後最終調用 request,在 request 中發起請求,把結果發給下游;
OperatorMapResponseToBodyOrError$1#onNext ,把 response的 body 發 給下游;最終就到了我們subscribe 時傳入的回調裡面了;

四、retrofit-converters 模塊

retrofit 模塊內置了 BuiltInConverters ,只能處理 ResponseBody , RequestBody 和 String 類型的轉化(實際上不需要轉)。而 retrofit- converters 中的子模塊則提供了 JSON,XML,ProtoBuf 等類型數據的轉換功能, 而且還有多種轉換方式可以選擇。這裡我主要關注 GsonConverterFactory 。

代碼非常簡單:

<code>    
  

public

Converter responseBodyConverter(Type typ e, Annotation[] annotations, Retrofit retrofit) { TypeAdapter> adapter = gson.getAdapter(TypeToken.get(type));

return

new

GsonResponseBodyConverter<>(gson, adapter); }

final

class

GsonResponseBodyConverter

<

T

>

implements

Converter

<

Re

sponseBody

,

T

>

{

private

final

Gson gson;

private

final

TypeAdapter adapter; GsonResponseBodyConverter(Gson gson, TypeAdapter adapter) {

this

.gson = gson;

this

.adapter = adapter; }

public

T

convert

(ResponseBody value)

throws

IOExcept ion

{ JsonReader jsonReader = gson.newJsonReader(value.charStream( ));

try

{

return

adapter.read(jsonReader); }

finally

{ value.close(); } } }/<code>

根據目標類型,利用 Gson#getAdapter 獲取相應的 adapter,轉換時利用 Gson 的 API 即可。

最後

不知不覺自己已經做了幾年開發了,由記得剛出來工作的時候感覺自己能牛逼,現在回想起來感覺好無知。懂的越多的時候你才會發現懂的越少。

對於程序員來說,要學習的知識內容、技術有太多太多。

很多人在剛接觸這個行業的時候或者是在遇到瓶頸期的時候,總會遇到一些問題,比如學了一段時間感覺沒有方向感,不知道該從那裡入手去學習,可以關注我,每天更新各種技術乾貨,分享更多最熱程序員圈內事.


分享到:


相關文章: