【Android】Retrofit2.0源碼解析

Retrofit

前言

使用Retrofit已經(jīng)一段時間了枯饿,這貨挺好用的甩十,還很特別巧骚,特別是使用接口來定義請求方式,這用法讓我對它的源碼很是好奇圾结。今天就來看看源碼吧...

參靠源碼retrofit:2.0.2

基本的用法

首先來簡單得實現(xiàn)一次GET請求

  • 定義接口
interface Service {
    @GET("News")
    Call<ResponseBody> getNews(
            @Query("limit") String limit);
}
  • 完成一次請求
        Retrofit retrofit =new Retrofit.Builder()
                .baseUrl("http://hcy.com/api/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        Service service = retrofit.create(Service.class);
        Call<ResponseBody> call = service.getNews("10");
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call,
                                   Response<ResponseBody> response) {
                Log.i(TAG, "onResponse");
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.i(TAG, "onFailure");
            }
        });

根據(jù)這個請求一步步進行解析瑰剃,接下來就是這篇的重點,Retrfit內(nèi)部的實現(xiàn)筝野。
還不會用Retrofit培他?少年去看看Retrofit 2.0 的使用吧>榱健!舀凛!

源碼解析

這里分別說明了都調用了哪些源碼俊扳,都是怎么實現(xiàn)的。(里面涉及到一些設計模式猛遍,什么馋记?你還不知道有什么設計模式?下面會涉及到Builder模式(外觀模式)懊烤、工廠模式梯醒、動態(tài)代理。stay 的 Retrofit分析-經(jīng)典設計模式

  • 1.Retrofit的創(chuàng)建

首先看下Retrofit對象的創(chuàng)建

        Retrofit retrofit =new Retrofit.Builder()
                .baseUrl("http://hcy.com/api/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

這里使用了Builder設計模式(外觀模式)腌紧,Retrofit.BuilderRetrofit的一個內(nèi)部類茸习,用來配置一些成員變量,這里配置了baseUrlConverterFactory(對象的序列號/反序列化組件)壁肋,然后創(chuàng)建一個Retrofit對象号胚。這些代碼都做了什么,下面一一說明浸遗。

  • Retrofit.Builder()
    看看new Retrofit.Builder()調用的代碼
    public Builder() {
      this(Platform.get());
    }

Platform.get()又是什么猫胁?抱著一貫的好奇,點進去看看跛锌。主要代碼如下

  private static final Platform PLATFORM = findPlatform();
  static Platform get() {
      return PLATFORM;
  }
  private static Platform findPlatform() {
      try {
          Class.forName("android.os.Build");
          if (Build.VERSION.SDK_INT != 0) {
          return new Android();
        }
        } catch (ClassNotFoundException ignored) {
        }
      try {
          Class.forName("java.util.Optional");
          return new Java8();
        } catch (ClassNotFoundException ignored) {
        }
      try {
          Class.forName("org.robovm.apple.foundation.NSObject");
          return new IOS();
        } catch (ClassNotFoundException ignored) {
        }
      return new Platform();
  }

機智的你一定能看出來弃秆,主要就是這個findPlatform()方法。這里面的代碼髓帽,就是判斷當前運行的平臺菠赚。可以看到里面有Android郑藏、Java8锈至、IOS。等下译秦,怎會有IOS峡捡,什么鬼(為什么會有IOS就交給你去研究了)。
我們在Android上運行的話筑悴,就調用了return new Android()们拙。進一步往下看,Android()是什么

  static class Android extends Platform {
      @Override public Executor defaultCallbackExecutor() {
        return new MainThreadExecutor();
      }
      @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
        return new ExecutorCallAdapterFactory(callbackExecutor);
      }

      static class MainThreadExecutor implements Executor {
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override public void execute(Runnable r) {
          handler.post(r);
        }
      }
  }

他繼承了Platform重寫了defaultCallbackExecutordefaultCallAdapterFactory方法阁吝。
defaultCallbackExecutor:返回的是用于執(zhí)行 Callback 的 線程池砚婆。可以看到MainThreadExecutor 獲取了主線程的 Looper 并構造了一個主線程的 Handler,調用 Callback 時會將該請求 post 到主線程上去執(zhí)行装盯。這就解釋了為什么請求后完成的回調都是在主線中坷虑。
defaultCallAdapterFactory:將返回的適配類型默認為
Call
類型(如果使用RxJava的話,就可以通過配置.addCallAdapterFactory(RxJavaCallAdapterFactory.create())將配置類型改成Observable埂奈。)

    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }
    public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

這里有兩個重載的方法迄损,創(chuàng)建了okhttp3HttpUrl 實例。

  • .addConverterFactory(GsonConverterFactory.create())
    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

往轉換工廠集合中添加了我們指定的轉換工廠账磺,最后將返回的數(shù)據(jù)類型轉換成對應的實體類對象的Converter類型芹敌。在我們的例子里面 GsonConverterFactory 將選用 GsonConverter 來轉換。

  • .build();
    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

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

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

看最后一句

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

這里才是創(chuàng)建Retrofit對象的地方垮抗,之前的只是一些配置氏捞。里面的參數(shù):
callFactory(Call工廠):看到了吧callFactory = new OkHttpClient();,這里用的是okhttp3冒版;
baseUrl(服務器基本地址):這個我們上面配置過液茎;
converterFactories(對象的序列號/反序列化組件):我們上面配置過。
adapterFactories(適配類型)辞嗡、callbackExecutor(執(zhí)行 Callback 的線程池):從我們上面提到的platform中獲取默認值捆等。
validateEagerly(標識):先不說,后面會用到

  • 總:完成基本的配置欲间,創(chuàng)建一個Retrofit對象

2.Service的創(chuàng)建以及接口的調用

我們創(chuàng)建了一個接口的實例楚里,用于調用接口断部×蕴看下代碼吧

Service service = retrofit.create(Service.class);
Call<ResponseBody> call = service.getNews("10");

Service是之前定義的接口,這里代碼通過retrofit.create(Service.class)就得到了Service的實例蝴光,他是怎么做到的她渴?進入create()方法看看

  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, 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 serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

看到這里的代碼,相信有些同學開始懵逼了蔑祟,Proxy.newProxyInstance...這是什么鬼趁耗?這代碼鬼認識...

哈哈,這叫動態(tài)代理疆虚,可以生成接口對應的對象苛败,之后使用這個對象調用方法時都會調用InvocationHandler中的invoke方法。(我不會告訴你們我一開始也是懵逼的~~)
對動態(tài)代理還不熟悉的看看這里: 公共技術點之 Java 動態(tài)代理
下面我們來一步步分析這個create方法:

  • Utils.validateServiceInterface(service);
    源碼我就不貼出來了径簿,這個方法主要就是判斷了參數(shù)service是否為Interface罢屈,是否包含了其他接口;
  • eagerlyValidateMethods(service);
    這里根據(jù)validateEagerly判斷是否需要提前創(chuàng)建ServiceMethod篇亭,調用loadServiceMethod()方法缠捌,這個方法我們自后面會講到。
  • invoke
    接下來看Proxy.newProxyInstance中重寫的方法invoke默勾,這才是這次解析的重點盔沫。看看里面都做了什么
    • 代碼
          if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
          }
          if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
          }

第一個if用來判斷的是否為Object方法馍乙,如果是就直調用哑芹;
第二個if則是判斷平臺炎辨,不過進入.isDefaultMethod(method)源碼可以看到,直接返回false绩衷,應該是為了之后的擴展用的蹦魔。

  • ServiceMethod serviceMethod = loadServiceMethod(method);
    創(chuàng)建了一個ServiceMethod對象,看下loadServiceMethod的源碼
  ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
           }
        }
    return result;
  }

loadServiceMethod做了三件事:
1.先去serviceMethodCache中查找否存在method(看來這貨是有緩存的咳燕,這里采用了LinkedHashMap來緩存這些Method的解析結果)勿决,存在的話跳過第二步;
2.method不存在的話就創(chuàng)建一個招盲,然后添加到緩存中低缩;
3.返回ServiceMethod 對像。
可以看到ServiceMethod也使用了Bulider設計模式曹货,繼續(xù)往里面看咆繁?看看new ServiceMethod.Builder(this, method).build();都干嘛了?

這里就簡單說說ServiceMethod的功能顶籽,再講下去這層次結果有點深...
ServiceMethod的定義:把對接口中的方法的調用轉化成一次HTTP調用玩般。
(說人話...)
呃...,就是解析了接口中@GET("News")礼饱、@Query("limit") String limit等一些列有關請求的信息坏为,然后還保存了Retrofit中的一些重要信息,如:
1镊绪、callFactory:Call工廠匀伏,負責創(chuàng)建 HTTP 請求
2、callAdapter:確定返回的retrofit2.Call<T>類型(接口定義時的返回類型蝴韭,例子中的Call<ResponseBody>)够颠;
3、responseConverter:數(shù)據(jù)轉換類型榄鉴,負責將服務器返回的數(shù)據(jù)(Json履磨、xml等各式)轉換成我們需要用到的T類型的對象;
4庆尘、parameterHandlers:則負責解析 API 定義時每個方法的參數(shù)剃诅,并在構造 HTTP 請求時設置參數(shù)。(如例子中的@Query("limit")中的limit)
總之就是基本包含了這次請求的全部內(nèi)容

  • OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
    創(chuàng)建了一個OkHttpCall 對象减余,用來發(fā)起請求综苔。OkHttpCall 中有個我們常用的方法enqueue()
  @Override public void enqueue(final Callback<T> callback) {
      if (callback == null) throw new NullPointerException("callback == null");  
        okhttp3.Call call;
         ....
        call.enqueue(new okhttp3.Callback() {
            @Override public void onResponse(okhttp3.Call call, 
                okhttp3.Response rawResponse) throws IOException {
                Response<T> response;
                try {
                  response = parseResponse(rawResponse);
                } catch (Throwable e) {
                  callFailure(e);
                  return;
                }
                callSuccess(response);
              }
            @Override public void onFailure(okhttp3.Call call, IOException e) {
                try {
                  callback.onFailure(OkHttpCall.this, e);
              } catch (Throwable t) {
                  t.printStackTrace();
                }
              }
            ...
          });
  }

都說Retrofit內(nèi)部是用okhttp實現(xiàn)請求的,原來在這里,哈哈...
拿著這個對象我們就可以發(fā)起請求了如筛,不過Retrofit還要適配返回類型堡牡,所以還要下面這句代碼。

  • return serviceMethod.callAdapter.adapt(okHttpCall);
    這里將我們創(chuàng)建的OkHttpCall 對象適配成對應的類型(例子中得到的是Call杨刨,如果用RxJava得到的就是Observable)晤柄。

3.發(fā)起請求

      call.enqueue(new Callback<ResponseBody>() {
          @Override
          public void onResponse(Call<ResponseBody> call,
                                 Response<ResponseBody> response) {
              Log.i(TAG, "onResponse");
          }
          @Override
          public void onFailure(Call<ResponseBody> call, Throwable t) {
              Log.i(TAG, "onFailure");
          }
      });

最后就是使用得到的call對象來發(fā)起請求(這個callretrofit2.Call)。
通過上面的解析可以知道妖胀,這里其實就是調用了okhttp3里面的okhttp3.Call來完成這次請求芥颈。
還不滿足?想知道okhttp3是怎么完成請求的赚抡?
自己去研究吧爬坑,少年~~

以上有錯之處,還望不吝賜教涂臣。謝謝6芗啤!

參考

拆輪子系列:拆 Retrofit
Retrofit源碼1: 為什么寫一個interface就可以實現(xiàn)http請求
Retrofit2 源碼解析
Retrofit 源碼分析之 Retrofit 對象
Retrofit分析-漂亮的解耦套路
Retrofit分析與實現(xiàn)

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赁遗,一起剝皮案震驚了整個濱河市署辉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岩四,老刑警劉巖哭尝,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剖煌,居然都是意外死亡材鹦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門末捣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侠姑,“玉大人创橄,你說我怎么就攤上這事箩做。” “怎么了妥畏?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵邦邦,是天一觀的道長。 經(jīng)常有香客問我醉蚁,道長燃辖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任网棍,我火速辦了婚禮黔龟,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己氏身,他們只是感情好巍棱,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛋欣,像睡著了一般航徙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陷虎,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天到踏,我揣著相機與錄音,去河邊找鬼尚猿。 笑死窝稿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的凿掂。 我是一名探鬼主播讹躯,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缠劝!你這毒婦竟也來了潮梯?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤惨恭,失蹤者是張志新(化名)和其女友劉穎秉馏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脱羡,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡萝究,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锉罐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帆竹。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脓规,靈堂內(nèi)的尸體忽然破棺而出栽连,到底是詐尸還是另有隱情,我是刑警寧澤侨舆,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布秒紧,位于F島的核電站,受9級特大地震影響挨下,放射性物質發(fā)生泄漏熔恢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一臭笆、第九天 我趴在偏房一處隱蔽的房頂上張望叙淌。 院中可真熱鬧秤掌,春花似錦、人聲如沸鹰霍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衅谷。三九已至椒拗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間获黔,已是汗流浹背蚀苛。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玷氏,地道東北人堵未。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像盏触,于是被迫代替她去往敵國和親渗蟹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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