Retrofit 2.3.0 源碼解析

前言

Retrofit A type-safe HTTP client for Android and Java

Retrofit,是一個(gè)基于http請(qǐng)求庫(kù)二次封裝的HTTP客戶端,將 REST API 轉(zhuǎn)換為 Java 接口脯厨。

基于注解逞力,進(jìn)一步解放了生產(chǎn)力,使得http請(qǐng)求就像調(diào)用方法一樣簡(jiǎn)單厘肮,如絲般順滑转砖。

結(jié)構(gòu)概覽

architecture.png

項(xiàng)目結(jié)構(gòu)整體分四個(gè)部分恢筝,Builder -> Proxy -> Invocation -> RawCall
這里我們把基于Retrofit的HTTP通信比做是郵遞信件。

郵遞信件

  • 信封:當(dāng)我們準(zhǔn)備好信件之后料睛,要在信封上寫(xiě)郵寄地址丐箩,收件人,可能還要備注勿折(是的恤煞,我暴露了我的年齡屎勘,如今很多人可能都沒(méi)有過(guò)寫(xiě)信寄信的體驗(yàn))。
  • 郵遞員:然后我們親自去送信嗎居扒?No概漱,我們把信投入郵箱,交給郵遞員代為送信就行了苔货。
  • 郵局:然后郵遞員會(huì)根據(jù)信封上的信息對(duì)信件進(jìn)行分揀犀概,寄信或收信均經(jīng)由郵局統(tǒng)一處理
  • 郵寄方式:最后就是交給運(yùn)送單位送信了立哑,空運(yùn)或是陸運(yùn)等。

基于Retrofit的HTTP通信

  • Builder:當(dāng)我們準(zhǔn)備好數(shù)據(jù)之后姻灶,要指定服務(wù)端的通信地址铛绰,處理接口地址,請(qǐng)求方法产喉,可能還要備注是否有body捂掰、是否是multipart。
  • Proxy:然后通信的事交給代理去做曾沈,代理會(huì)幫你做好一系列的工作这嚣,比如注解解析,Call適配塞俱,以及請(qǐng)求調(diào)度等
  • Invocation:這里負(fù)責(zé)調(diào)度同步或異步請(qǐng)求姐帚,請(qǐng)求裝配和響應(yīng)解析
  • RawCall:這里就是具體的通信工具了,可選Okhttp等框架來(lái)做具體的Http通信障涯。

來(lái)看看寄信和Retrofit之間的對(duì)比:

arch_flow.png

大概過(guò)程就是這樣罐旗,郵遞員會(huì)把信送出去,并在適合的時(shí)機(jī)把對(duì)方的回信取回來(lái)送給你唯蝶,當(dāng)然如果你的信件是表白情書(shū)九秀,那也很可能會(huì)收不到回信的,畢竟表白成功的概率要看人品的粘我。不要傷心鼓蜒,HTTP通信也會(huì)有時(shí)候收不到服務(wù)端的回信噢。

目錄概覽

官方 Javadoc

│  BuiltInConverters.java               # 內(nèi)建Converter
│  Call.java                            # 發(fā)送請(qǐng)求接收響應(yīng)的retrofit方法調(diào)用
│  CallAdapter.java                     # 適配Call的響應(yīng)類(lèi)型征字,將默認(rèn)響應(yīng)類(lèi)型R轉(zhuǎn)換為類(lèi)型T
│  Callback.java                        # 返回服務(wù)端或離線請(qǐng)求的響應(yīng)體
│  Converter.java                       # HTTP交互中都弹,轉(zhuǎn)換對(duì)象為數(shù)據(jù) 或 從數(shù)據(jù)轉(zhuǎn)換為對(duì)象
│  DefaultCallAdapterFactory.java       # 默認(rèn)CallAdapter工廠
│  ExecutorCallAdapterFactory.java      # http請(qǐng)求執(zhí)行器工廠
│  HttpException.java                   # 非2xx HTTP響應(yīng)的異常處理
│  OkHttpCall.java                      # 真正調(diào)用OkHttp3發(fā)送Http請(qǐng)求的類(lèi)
│  package-info.java                    # 包描述
│  ParameterHandler.java                # 參數(shù)注解解析器
│  Platform.java                        # 平臺(tái)適配(Java/Android)
│  RequestBuilder.java                  # 請(qǐng)求拼裝
│  Response.java                        # 原汁原味的HTTP 響應(yīng)體,所謂 T body
│  Retrofit.java                        # 組裝工廠柔纵,基于建造者模式拼裝自定義HTTP交互所需的組件缔杉,并作為總調(diào)度暴露接口
│  ServiceMethod.java                   # 框架核心處理類(lèi)锤躁,注解解析器調(diào)度搁料,生成請(qǐng)求(包含api url、path系羞、http請(qǐng)求方法郭计、請(qǐng)
                                        # 求頭、是否是multipart等等),并返回用于發(fā)起http請(qǐng)求的Call對(duì)象
│  Utils.java                           # 工具類(lèi)
│  
└─http                              # http注解定義 (直接引用了Javadoc中的描述椒振,均為提高生產(chǎn)力的注解)

        Body.java                       # control the request body of a POST/PUT request
        DELETE.java                     # Make a DELETE request
        Field.java                      # Named pair for a form-encoded request
        FieldMap.java                       # Named key/value pairs for a form-encoded request
        FormUrlEncoded.java                 # Denotes that the request body will use form URL encoding
        GET.java                        # Make a GET request
        HEAD.java                       # Make a HEAD request
        Header.java                     # Replaces the header with the value of its target
        HeaderMap.java                      # Adds headers specified in the Map
        Headers.java                        # Adds headers literally supplied in the value
        HTTP.java                       # Use a custom HTTP verb for a request
        Multipart.java                      # Denotes that the request body is multi-part
        OPTIONS.java                        # Make an OPTIONS request
        package-info.java                   # Package description
        Part.java                       # Denotes a single part of a multi-part request
        PartMap.java                        # Denotes name and value parts of a multi-part request
        PATCH.java                      # Make a PATCH request
        Path.java                       # Named replacement in a URL path segment
        POST.java                       # Make a POST request
        PUT.java                        # Make a PUT request
        Query.java                      # Query parameter appended to the URL
        QueryMap.java                       # Query parameter keys and values appended to the URL
        QueryName.java                      # Query parameter appended to the URL that has no value
        Streaming.java                      # Treat the response body on methods returning Response as is, i.e. 
                                # without converting body() to byte[]
        Url.java                        # URL resolved against the base URL

Retrofit的基本用法

讓我們從基本用法開(kāi)始昭伸,先看如何使用,順著這個(gè)藤澎迎,摸摸如何實(shí)現(xiàn)的瓜庐杨。

用 Java 接口的方式定義一個(gè)HTTP API.

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

Retrofit 類(lèi)生成一個(gè) GitHubService 接口的實(shí)現(xiàn)實(shí)例.

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.
GitHubService實(shí)例的每一個(gè)方法調(diào)用都支持同步或異步HTTP請(qǐng)求.

Call<List<Repo>> repos = service.listRepos("octocat");

執(zhí)行同步或異步HTTP請(qǐng)求选调,得到HTTP響應(yīng)數(shù)據(jù).

Response<List<Repo>> response = repos.execute();

Retrofit的源碼解析

首先我們心里要有個(gè)概念,Retrofit的核心關(guān)鍵詞:注解灵份、動(dòng)態(tài)代理仁堪、轉(zhuǎn)換器、適配器

Retrofit就是基于這四個(gè)關(guān)鍵詞搭建起來(lái)的充分解耦填渠,靈活弦聂,可插拔的優(yōu)秀框架。

下面我們結(jié)合Retrofit設(shè)計(jì)圖流程來(lái)解讀代碼氛什。 還記得流程嗎莺葫? Builder -> Proxy -> Invocation -> RawCall.

Flow - Builder

Retrofit.Builder() .baseUrl("https://api.github.com/") ... .build();
Tips.設(shè)計(jì)模式之Builder模式

基于Builder模式,裝配一系列零部件,比如base請(qǐng)求地址枪眉,gson轉(zhuǎn)換器捺檬,Rxjava適配器,HTTP請(qǐng)求client(比如裝配OKHTTP)等贸铜。

// Retrofit.java -> class Builder

public Retrofit build() {
      
      ...

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

返回一個(gè)裝配了 callFactory欺冀,converterFactories,adapterFactories萨脑,callbackExecutor 和指定了 baseUrl 的 Retrofit 實(shí)例隐轩。
注:validateEagerly,用于指定是否預(yù)先解析注解渤早,加速接口訪問(wèn)效率职车。

Flow - Proxy

GitHubService service = retrofit.create(GitHubService.class);
我們知道,Java 接口是不可以直接 new 實(shí)例的鹊杖,那么這個(gè) create 方法看起來(lái)又像是返回了一個(gè) GitHubService 接口類(lèi)型的實(shí)現(xiàn)實(shí)例悴灵,這是怎么回事呢?我們來(lái)看下 create 的實(shí)現(xiàn)骂蓖。

// Retrofit.java

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

create方法主要就一個(gè)return,返回了一個(gè)Proxy.newProxyInstance生成的動(dòng)態(tài)代理對(duì)象积瞒。原來(lái)這里是通過(guò)動(dòng)態(tài)代理的方式生成了 GitHubService 接口的代理實(shí)例,那么后續(xù) GitHubService 接口的方法都可以通過(guò)代理去調(diào)用了登下。
為什么用動(dòng)態(tài)代理茫孔?
這是Retrofit設(shè)計(jì)的核心思路,基于動(dòng)態(tài)代理被芳,可以為后續(xù)在調(diào)用 GitHubService 接口的相關(guān)方法時(shí)先攔截下來(lái)缰贝,做完一系列工作后(即注解解析,請(qǐng)求轉(zhuǎn)換畔濒,適配等)剩晴,再去完成方法本尊想要完成的工作,這就是動(dòng)態(tài)代理的魅力侵状。

Tips.動(dòng)態(tài)代理

Call<List<Repo>> repos = service.listRepos("octocat");
通過(guò)代理對(duì)象 service 調(diào)用接口方法 listRepos 赞弥,會(huì)被動(dòng)態(tài)代理攔截毅整,調(diào)用Proxy.newProxyInstance方法中的InvocationHandler對(duì)象的 invoke 方法。

invoke中主要由ServiceMethod和CallAdapter完成了三件事:

  • 請(qǐng)求方法的注解解析
  • 創(chuàng)建OkHttpCall實(shí)例绽左,為后續(xù)流程中的HTTP請(qǐng)求執(zhí)行做準(zhǔn)備毛嫉,詳見(jiàn) Flow - Invocation.
  • 適配Call的響應(yīng)類(lèi)型,將默認(rèn)響應(yīng)類(lèi)型R轉(zhuǎn)換為類(lèi)型T
ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);

ServiceMethod.java

// ServiceMethod.java

public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      
      ...

      responseConverter = createResponseConverter();

      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      ...

      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        
        ...

        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];

        ...

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

      ...

      return new ServiceMethod<>(this);
    }

獲取callAdapter妇菱、responseType承粤、responseConverter接口對(duì)象

解析Method的注解

解析Method的參數(shù)注解

解析Method的參數(shù)中使用了依賴請(qǐng)求API的動(dòng)態(tài)參數(shù)的注解,交由ParameterHandler處理

CallAdapter.java

public interface CallAdapter<R, T> {
 
  Type responseType();

  ...

  T adapt(Call<R> call);

  ...
  
  }

適配Call的響應(yīng)類(lèi)型闯团,將默認(rèn)響應(yīng)類(lèi)型R轉(zhuǎn)換為類(lèi)型T.比如官方的RxJavaCallAdapter可以結(jié)合Rxjava特性對(duì)Call的響應(yīng)做RxJava觀察者模式轉(zhuǎn)換辛臊,進(jìn)一步解放生產(chǎn)力。

注:未在Builder階段指定CallAdapter(如 RxJavaCallAdapterFactory )的情況下房交,默認(rèn)的 CallAdapter 不對(duì)Call做任何處理彻舰。
見(jiàn) DefaultCallAdapterFactory:

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

  ...
      ...

      @Override public Call<Object> adapt(Call<Object> call) {
        return call;
      }
  }
}

Flow - Invocation

Response<List<Repo>> response = repos.execute();

這一步開(kāi)始基于同步的方式執(zhí)行HTTP請(qǐng)求讽坏,并得到返回的HTTP響應(yīng)數(shù)據(jù).

本質(zhì)上是執(zhí)行了 OkHttpCall 的 execute方法.

// OkHttpCall.java

@Override public Response<T> execute() throws IOException {

    synchronized (this) {
     
     ...
        ...
          call = rawCall = createRawCall();

    }

    ...

    return parseResponse(call.execute());
  }

如你所見(jiàn)坠陈,這里創(chuàng)建了RawCall,即真正的去執(zhí)行HTTP請(qǐng)求任務(wù)的對(duì)象坎缭。
這里還負(fù)責(zé)HTTP請(qǐng)求的響應(yīng)數(shù)據(jù)解析白群。
我們看下createRawCall()干了什么尚胞。

// OkHttpCall.java

private okhttp3.Call createRawCall() throws IOException {
    Request request = serviceMethod.toRequest(args);
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    ...
    return call;
  }

serviceMethod.toRequest()的功能:

// ServiceMethod.java

/** Builds an HTTP request from method arguments. */
  Request toRequest(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    ...

    return requestBuilder.build();
  }

toRequest 方法通過(guò) RequestBuilder 創(chuàng)建了 okhttp3 做 HTTP 請(qǐng)求時(shí)需要的 Request 對(duì)象。

serviceMethod.callFactory.newCall(request)的功能:
建立一個(gè)請(qǐng)求通道帜慢,為執(zhí)行HTTP請(qǐng)求做準(zhǔn)備笼裳。
這里callFactory可以由使用者指定,默認(rèn)為 OkHttpClient粱玲,見(jiàn):

// Retrofit.java

okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

回頭看下 OkHttpCall 中 execute 方法最后一句: return parseResponse(call.execute());
這里調(diào)用真正的HTTP請(qǐng)求客戶端的請(qǐng)求執(zhí)行方法躬柬。也就是來(lái)到了接下來(lái)的一個(gè)流程。

Flow - RawCall

上個(gè) Flow 中最后一步抽减, call.execute(),開(kāi)啟了真正的HTTP請(qǐng)求允青,即通過(guò) okhttp3 完成HTTP請(qǐng)求。
這個(gè)部分沒(méi)什么代碼可講卵沉,屬于面向接口開(kāi)發(fā)的典范颠锉,要講就該去講 Okhttp 框架的源碼了。

這個(gè)部分引出了 Retrofit 的開(kāi)源擁有者-Square 公司的另一個(gè)優(yōu)秀的開(kāi)源項(xiàng)目 Okhttp,是不是也很想一探究竟偎箫?

End

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末木柬,一起剝皮案震驚了整個(gè)濱河市皆串,隨后出現(xiàn)的幾起案子淹办,更是在濱河造成了極大的恐慌,老刑警劉巖恶复,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怜森,死亡現(xiàn)場(chǎng)離奇詭異速挑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)副硅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)姥宝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人恐疲,你說(shuō)我怎么就攤上這事腊满。” “怎么了培己?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵碳蛋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我省咨,道長(zhǎng)肃弟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任零蓉,我火速辦了婚禮笤受,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘敌蜂。我一直安慰自己箩兽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布章喉。 她就那樣靜靜地躺著比肄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪囊陡。 梳的紋絲不亂的頭發(fā)上芳绩,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音撞反,去河邊找鬼妥色。 笑死,一個(gè)胖子當(dāng)著我的面吹牛遏片,可吹牛的內(nèi)容都是我干的嘹害。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吮便,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼笔呀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起髓需,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤许师,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體微渠,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搭幻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逞盆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檀蹋。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖云芦,靈堂內(nèi)的尸體忽然破棺而出俯逾,到底是詐尸還是另有隱情,我是刑警寧澤舅逸,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布纱昧,位于F島的核電站,受9級(jí)特大地震影響堡赔,放射性物質(zhì)發(fā)生泄漏识脆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一善已、第九天 我趴在偏房一處隱蔽的房頂上張望灼捂。 院中可真熱鬧,春花似錦换团、人聲如沸悉稠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)的猛。三九已至,卻和暖如春想虎,著一層夾襖步出監(jiān)牢的瞬間卦尊,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工舌厨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岂却,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓裙椭,卻偏偏與公主長(zhǎng)得像躏哩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子揉燃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容