[Android] Retrofit 源碼分析之 Retrofit 對象

前言

在上一周學習了一下 Retrofit 的執(zhí)行流程。接下來的文章要更為深入地學習 Retrofit 的各個類荸百,這次我們先學習一下 Retrofit 框架里的 Retrofit 對象囚似,有沒有十分的拗口剩拢。。

本文主要講 Retrofit 對象的創(chuàng)建及其 .create 方法饶唤⌒旆ィ基本包括了這個類的全部內(nèi)容。

Retrofit 對象

簡介

Retrofit 通過使用方法上的『注解』來定義請求的構(gòu)成募狂,將我們聲明的 Http 接口轉(zhuǎn)化成一個 Call 對象呵晨。

這個 Call 對象呢,我們上周提到過熬尺,可以調(diào)用同步或非同步方法來發(fā)送請求摸屠,之后就交給 OkHttp 去執(zhí)行啦。

使用

Retrofit 類用到了創(chuàng)建者模式粱哼,我們需要使用 Retrofit.Builder 來創(chuàng)建它的實例季二,接著調(diào)用 Retrofit.create(Class<T>) 方法就能夠生成我們的接口實現(xiàn)類了。

這里回顧一下 Retrofit 相關(guān)的使用:

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

 MyApi api = retrofit.create(MyApi.class);

Retrofit.Builder

Retrofit.Builder 是 Retrofit 對象的一個嵌套類,負責用來創(chuàng)建 Retrofit 實例對象胯舷,使用『建造者模式』的好處是清晰明了可定制化刻蚯。

在執(zhí)行 .build() 方法前,只有 .baseUrl() 是必須調(diào)用來設置訪問地址的桑嘶,其余方法則是可選的炊汹。

首先看一下 Builder.build() 最后的返回語句:

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

這里的參數(shù)包括了 Call 工廠,地址逃顶,轉(zhuǎn)換器讨便,CallAdapter 工廠, 執(zhí)行 Callback 的線程池以及 validateEagerly 標識以政。

下面我們挑選其中幾個參數(shù)來進行分析:

baseUrl

baseUrl 其實是 okHttp3 的 HttpUrl 類實例霸褒,一個 http 或者 https 協(xié)議的 URL。

為 Retrofit.Builder 添加 baseUrl盈蛮,有兩個重載的方法 baseUrl(String baseUrl) 和 baseUrl(HttpUrl baseUrl)废菱,但實際最后調(diào)用的都是后者。

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;
}

可以看到抖誉,檢查驗證后就設置了 Retrofit 對象的 URL殊轴。

callbackExecutor

callbackExecutor 是 Callback 調(diào)用中用于執(zhí)行 Callback 的 線程池。

如果不自行設置的話袒炉,會根據(jù)平臺設置一個默認的 Executor梳凛。

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

這里的 .defaultCallbackExecutor() 是 Platform 抽象類的一個方法。包含了 Converter梳杏,Client 等屬性。他有三個實現(xiàn)類:Android淹接,Java8十性,IOS。分別設置了各個平臺下的一些默認參數(shù)塑悼。

在創(chuàng)建 Retrofit.Buidler 時會獲取并設置當前環(huán)境的 Platform:

public Builder() {
  this(Platform.get());
}

最后我們找到 Platform 的安卓實現(xiàn)類看一下:

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);
    }
  }
}

了解過 Handler 機制的同學肯定十分眼熟劲适,這里獲取了主線程的 Looper 并構(gòu)造了一個 主線程的 Handler,于是在 Android 平臺厢蒜,調(diào)用 Callback 時會將該請求 post 到主線程上去執(zhí)行霞势。

validateEagerly 標識

validateEagerly 是一個布爾類型的參數(shù)

我們知道當我們調(diào)用接口方法時,代理類會為方法創(chuàng)建一個 ServiceMethod斑鸦。

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
  eagerlyValidateMethods(service);
  }
  ...
}

如果將 validateEagerly 標識設置為 True愕贡,那么在我們調(diào)用 .eagerlyValidateMethods(service) 方法之前就提前驗證并創(chuàng)建好啦。

以上便是 Retrofit.Builder 的一些參數(shù)和方法巷屿,更具體的大家可以參照官方文檔來學習固以。

.create 方法

現(xiàn)在我們通過嵌套類 build 了一個 Retrofit 對象,就可以開始執(zhí)行下一步了。

// 將 Http 接口 轉(zhuǎn)化為 Call 對象
MyApi api = retrofit.create(MyApi.class);

我們先直接把 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);
      }
    });
}

下面一步步進行分析:

Utils.validateServiceInterface(service);

validateServiceInterface(service) 會驗證我們的 Http 接口是否是 Interface诫钓,是否未包含了其他的接口。若為否則會拋出錯誤篙螟。

if (validateEagerly) {
  eagerlyValidateMethods(service);
}

validateEagerly 的標簽的作用則在之前已經(jīng)說過了菌湃,算是一個提前驗證標識。

接下來便返回了一個動態(tài)代理遍略,其實仔細看會發(fā)現(xiàn)這里只是返回了動態(tài)代理的實例方法而已:

return (T) Proxy.newProxyInstance(...);

代理類首先獲取了當前的平臺 Platform惧所,然后當你調(diào)用接口方法時,會調(diào)用到代理類的 invoke 方法墅冷。

我們看看 invoke 方法里到底做了什么:

if (method.getDeclaringClass() == Object.class) {
  return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
    return platform.invokeDefaultMethod(method, service, proxy, args);
}

如果我們調(diào)用的是來自 Object 類或者平臺默認的方法纯路,則會交給方法執(zhí)行或者平臺執(zhí)行,但從代碼上看 isDefaultMethod(method) 直接返回的是 false寞忿,可能是為了方便開發(fā)者擴展設置各個平臺的不同方法調(diào)用驰唬。

ServiceMethod serviceMethod = loadServiceMethod(method);

經(jīng)過兩個判斷后,會將我們的方法轉(zhuǎn)換成一個 ServiceMethod 對象腔彰,我們可以來看看 loadServiceMethod 方法內(nèi)發(fā)生了什么:

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;
}

代碼很簡單叫编,每個 Method 對應一個 ServiceMethod,如果緩存里沒有霹抛,則新建一個搓逾。至于這個 ServiceMethod 是什么呢?我們具體可能要以后再詳細分析杯拐。

這里簡單的了解一下:之前我們說 Retrofit 對象的作用是將我們聲明的 Http 接口轉(zhuǎn)化成一個 Call 對象霞篡。實際上真正的工作是由 ServiceMethod 的來完成的,在其內(nèi)部分析并轉(zhuǎn)換了我們自定義的注解端逼,并生成了一個 Call 對象朗兵。

OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

接下來創(chuàng)建了一個 OkHttpCall。并使用 serviceMethod.CallAdapter 對 OkHttpCall 進行了轉(zhuǎn)化顶滩。

我們在創(chuàng)建 serviceMethod 時余掖,傳入了 Retrofit 對象作為參數(shù),這個 CallAdapter 就是從我們最開始構(gòu)建 Retrofit 時所添加的 CallAdapterFatory所生成的礁鲁。如果你沒有設置的話盐欺,在 Android 平臺,系統(tǒng)會為你設置一個 ExecutorCallAdapterFactory仅醇。

ExecutorCallAdapter會先返回一個 CallAdapter 實現(xiàn)類冗美,.adapt(okHttpCall) 就是這個類的方法。

終于析二,callAdapter.adapt 把 okHttpCall 轉(zhuǎn)化成了 ExecutorCallbackCall:

@Override public <R> Call<R> adapt(Call<R> call) {
    return new ExecutorCallbackCall<>(callbackExecutor, call);
}

于是我們就完成了 .create() 方法的調(diào)用墩衙,實際上 Retrofit 的使用我們也幾乎掌握了,因為之后的事情是交給 okHttp 去做的。

我們可以看看這個 ExecutorCallbackCall<>(callbackExecutor, call)漆改,參數(shù)里的 callbackExecutor心铃,有沒有很眼熟,之前 Retrofit.Builder 我們提到的默認添加的 Executor挫剑,這里其實就是我們 APP 應用的主線程去扣。

也就是我們的網(wǎng)絡請求完成后 Callback 回調(diào)的 onResponse 和 onFailure 方法,都會 post 到主線程上的 Handler 來執(zhí)行樊破。

總結(jié)

似乎這次文章的內(nèi)容有點長愉棱?總結(jié)一句話就是:Retrofit 如何將 Http 接口方法調(diào)用轉(zhuǎn)換成一個 Call 請求類。

這次我在學習代碼和寫文章到最后時哲戚,確實發(fā)現(xiàn)了之前的許多錯誤奔滑。目前難免會有許多理解不到位的地方,文章也寫的比較散亂顺少,希望各位能多多提出意見。

參考資料

Retrofit 2.0.0 API

快速Android開發(fā)系列網(wǎng)絡篇之Retrofit

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脆炎,一起剝皮案震驚了整個濱河市梅猿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秒裕,老刑警劉巖几蜻,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颖低,死亡現(xiàn)場離奇詭異扼褪,居然都是意外死亡,警方通過查閱死者的電腦和手機赏寇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門忙迁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恰梢,“玉大人删豺,你說我怎么就攤上這事∨畹” “怎么了谍咆?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我羔巢,道長匪燕,這世上最難降的妖魔是什么浆劲? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任膨报,我火速辦了婚禮现柠,結(jié)果婚禮上拟烫,老公的妹妹穿的比我還像新娘置媳。我一直安慰自己拇囊,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拦耐,像睡著了一般耕腾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杀糯,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天扫俺,我揣著相機與錄音,去河邊找鬼固翰。 笑死狼纬,一個胖子當著我的面吹牛羹呵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疗琉,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冈欢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盈简?” 一聲冷哼從身側(cè)響起凑耻,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柠贤,沒想到半個月后香浩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碍现,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡抗斤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年遏佣,在試婚紗的時候發(fā)現(xiàn)自己被綠了莺匠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湖苞。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡措译,死狀恐怖吗浩,靈堂內(nèi)的尸體忽然破棺而出揩页,到底是詐尸還是另有隱情猖败,我是刑警寧澤速缆,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站恩闻,受9級特大地震影響艺糜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜幢尚,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一破停、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尉剩,春花似錦真慢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至皂林,卻和暖如春朗鸠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背础倍。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工烛占, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沟启。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓忆家,卻偏偏與公主長得像呀癣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子弦赖,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

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