框架源碼 — 可能會有趣一點地簡析學習 Retrofit

看過很多篇 Retrofit 的源碼分析文章岗憋,但是別人一問起來總是講不清楚到底 Retrofit 是怎么個流程逸邦,所以還是得自己親自去看看源碼,一步一步的分析。果然只有親自動手實踐捐腿,才有自己的收獲。
告誡自己柿顶,慢慢來茄袖,會很快。

目錄

Retrofit 簡介

Retrofit 源碼開頭的解釋

* Retrofit adapts a Java interface to HTTP calls by using annotations on the declared methods to
* define how requests are made. Create instances using {@linkplain Builder
* the builder} and pass your interface to {@link #create} to generate an implementation.

Retrofit 利用方法上的注解將接口轉(zhuǎn)化成一個 HTTP 請求嘁锯。

簡單知道是什么了之后宪祥,我們對此提出疑問:

  • 如何將接口轉(zhuǎn)換為網(wǎng)絡請求?
  • 誰去進行網(wǎng)絡請求家乘?

接下來我們將從 Retrofit 的使用作為入口分析蝗羊。

Retrofit 分析

具體使用

首先建立 API 接口類:

interface GankApi {
    String host = "http://gank.io/api/data/";
    @GET("Android/10/{page}")
    Call<Android> getAndroid(@Path("page") int page);
}

// 創(chuàng)建 Retrofit 實例
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(GankApi.host)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

// 生成接口實現(xiàn)類
GankApi gankApi = retrofit.create(GankApi.class);

// 調(diào)用接口定義的請求方法,并且返回 Call 對象
Call<Android> call = gankApi.getAndroid(1);

// 調(diào)用 Call 對象的異步執(zhí)行方法
call.enqueue(Callback callback)

簡單的使用就是這樣的流程∪示猓現(xiàn)在我們開始層層剖析耀找。

工具箱:Retrofit.Builder()

private Platform platform;
private okhttp3.Call.Factory callFactory;
private HttpUrl baseUrl;
private List<Converter.Factory> converterFactories = new ArrayList<>();
private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
private Executor callbackExecutor;
private boolean validateEagerly;

創(chuàng)建 Retrofit 的實例,進行一些配置业崖,這里我們不用多說野芒。但是有一個參數(shù)必須得講講。

  • Platform

在構建 Retrofit 的時候双炕,會對當前使用平臺進行判斷狞悲,Java8,Android雄家,iOS效诅。

我們看看 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);
    }
  }
}

從代碼中我們得知兩點:

  1. 在 Android 里我們默認使用的 CallAdapter 是 ExecutorCallAdapterFactory() 它會返回的是 Call.class胀滚。關于 ExecutorCallAdapterFactory() 我們稍后再說趟济,你先知道這是 Android 默認 CallAdapter 就好。
  2. 默認的 Callback 是在主線程咽笼。

外殼:Create()

// 生成接口實現(xiàn)類
GankApi gankApi = retrofit.create(GankApi.class);

我在源碼里寫好了注釋:

public <T> T create(final Class<T> service) {
    // 檢查傳入的類是否為接口并且無繼承
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    // 重點是這里
    // 首先會返回一個利用代理實現(xiàn)的 GankApi 對象
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          // 我們調(diào)用該對象的方法都會進入到這里
          @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);
            }
            // 解析方法 這里用到了注解(Runtime)這里我們標記下(A)稍后來看看里面具體實現(xiàn)
            ServiceMethod serviceMethod = loadServiceMethod(method);
            // 將剛剛解析完畢包裝后的具體方法封裝成 OkHttpCall 顷编,你可以在該實現(xiàn)類找到 okhttp 請求所需要的參數(shù)
            // 所以它是用來跟 okhttp 對接的。
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            // 將以上我們封裝好的 call 返回給上層剑刑,這個時候我們就可以執(zhí)行 call 的同步方法或者異步進行請求媳纬。
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

切合我們實際運用來看看順序:

GankApi gankApi = retrofit.create(GankApi.class);—->
return (T) Proxy.newProxyInstance(...){...}—->
Call<Android> call = gankApi.getAndroid(1); —->
public Object invoke(...){...} 調(diào)用代理類的invoke()

直到這里我們已經(jīng)宏觀地了解 Retrofit 是怎樣的一個流程施掏。
達成 初窺門徑 成就钮惠。

千萬別驕傲,為了以后走的更遠更穩(wěn)七芭,我們得好好筑基素挽,上面我們用到的是動態(tài)代理,強烈建議認真閱讀兩篇文章狸驳。

結構:ServiceMethod

Retrofit 有一個雙鏈表用來緩存方法
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();

  ServiceMethod loadServiceMethod(Method method) {
  ServiceMethod result;
  synchronized (serviceMethodCache) {
      // 從緩存中獲取該方法
    result = serviceMethodCache.get(method);
    if (result == null) {
        // 沒有就進行創(chuàng)建并且存入鏈表緩存
      result = new ServiceMethod.Builder(this, method).build();
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

我們發(fā)現(xiàn)主要的方法是 new ServiceMethod.Builder(this, method).build(); 预明,所以接下來我們深入看看如何 解析注解 以及 構建請求方法 缩赛。

  • 初始化一些參數(shù)
public Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit;
  this.method = method;
  this.methodAnnotations = method.getAnnotations();
  this.parameterTypes = method.getGenericParameterTypes();
  this.parameterAnnotationsArray = method.getParameterAnnotations();
}
  • build()

這里的源碼很長,做了很多異常處理撰糠,我截取重點來分析下酥馍。

callAdapter = createCallAdapter();
responseConverter = createResponseConverter();

一個是用來發(fā)送請求的 client ,一個是結果的轉(zhuǎn)換器(Gson阅酪,F(xiàn)astJson ...)之類旨袒,后面我們再講這個。
上層配置就是當我們調(diào)用 Retrofit 的 addConverterFactory()addCallAdapterFactory()术辐,內(nèi)部會自動使用我們定義的組件峦失。

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

在這里可以看到遍歷我們使用方法的注解,并且解析他們术吗。parseMethodAnnotation() 內(nèi)部就是解析好 HTTP 的請求方式尉辑。

為了篇幅大小,可以在 源碼 里看看具體的操作较屿。

同時也可以看看 http 包下注解用到的接口隧魄,你會發(fā)現(xiàn) @Retention(RUNTIME) 所以,從這里我們就可以明白隘蝎,Retrofit 是在在運行期通過反射訪問到這些注解的购啄。

  • return Call

請求方法參數(shù),請求客戶端嘱么,返回值轉(zhuǎn)換狮含,我們都定義好了之后,便完成最后一步曼振,構建好適合請求客戶端的請求方法几迄,Retrofit 默認的是 okhttpCall 。

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

最后將 call 返回給上層冰评,用戶調(diào)用方法進行請求映胁。

  • 總結
/** Adapts an invocation of an interface method into an HTTP call. */

ServiceMethod 類開頭注釋已經(jīng)很清楚的說明了作用,將接口方法改變成一個 HTTP call 甲雅。它對于 Retrofit 是很重要的存在解孙,整個槍支內(nèi)部都是由它來支撐起來。

子彈:xxxFactory()

Retrofit 給我們最大的便利就是自身框架優(yōu)雅的設計抛人,只需要很小的改動弛姜,便可以優(yōu)雅的適應不同的需求。所以很需要我們再補充點額外知識妖枚,了解什么是適配器模式廷臼,然后回到這里看看 Retrofit 是如何應用的。

在構建 ServiceMethod 對象的時候,有三個方法可以單獨說說

  1. build()createCallAdapter() —-> retrofit.callAdapter()
  2. 解析接口方法內(nèi)注解時parseParameterAnnotation()調(diào)用到的retrofit.requestBodyConverter()
  3. build()createResponseConverter() —-> retrofit.responseBodyConverter()

callAdapter()

最終會調(diào)用到 nextCallAdapter() 該方法主要是從 callAdapterFactories 中獲取新的 CallAdapter中剩,它會跳過 skipPast忌穿,以及 skipPast 之前的 Factory,然后找到與 returnType 和 annotations 都匹配的 CallAdapterFactory 结啼。

requestBodyConverter() & responseBodyConverter()

最終會調(diào)用到 nextRequestBodyConverter()/nextResponseBodyConverter利用 converterFactories 創(chuàng)建一個與 RequestBody/ResponseBody 對應的 Converter 對象掠剑。

所以在這里我們就可以裝填我們需要的子彈類型了。

進入實戰(zhàn)郊愧,為我們的 Retrofit 添加 RxJava 和 Gson朴译。

  • Rxjava:
23:33:50.jpg

adapter-rxjava 我們重點看 RxJavaCallAdapterFactory 即可,它是實現(xiàn)了 CallAdapter.Factory 并在對應方法里將 Call 包裝成 Observable.class 返回属铁。
然后給 Retrofit 對象加上 .addCallAdapterFactory(RxJavaCallAdapterFactory.create())眠寿,這樣我們才可以優(yōu)雅的使用 Retrofit + RxJava 。

  • Gson:
23:34:46.jpg

我相信通過類名我們就可以知道每個類是用來做什么的焦蘑,我在這里太過深入到具體實現(xiàn)反而一葉障目盯拱,
如果我們需要自定義數(shù)據(jù)轉(zhuǎn)換格式,也是同樣這樣做例嘱。
繼承 Converter.Factory 類作為適配類狡逢,同時創(chuàng)建兩個實現(xiàn) Converter 的類包裝請求和響應的數(shù)據(jù)形式。

開槍打靶: Call.enqueue()

<div class="tip">
注意:我這里只列舉一個默認狀態(tài)下的情況
</div>

?還記得我工具箱里我們提到的 ExecutorCallbackCall 嗎拼卵?
這里的 Call 是對應我們選擇的 call 奢浑,而此時是默認的 ExecutorCallbackCall 。如果還要問我為什么腋腮,請去看看 工具箱:Retrofit.Builder() 里 Android 平臺的源碼雀彼。

static final class ExecutorCallbackCall<T> implements Call<T> {
  final Executor callbackExecutor;
  final Call<T> delegate;

  ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
    this.callbackExecutor = callbackExecutor;
    this.delegate = delegate;
  }

  @Override public void enqueue(final Callback<T> callback) {
    if (callback == null) throw new NullPointerException("callback == null");

    delegate.enqueue(new Callback<T>() {
      @Override public void onResponse(Call<T> call, final Response<T> response) {
        callbackExecutor.execute(new Runnable() {
          @Override public void run() {
            if (delegate.isCanceled()) {
              // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
              callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
            } else {
              callback.onResponse(ExecutorCallbackCall.this, response);
            }
          }
        });
      }

      // 省略
}

這里的 delegate 對應的就是 okhttp 的 call ,不禁有疑問了即寡,這里調(diào)用的是異步請求徊哑,但是我們的回調(diào)是怎么回到主線程的呢?

帶著疑問我們來看看嘿悬。
首先回調(diào)是在 callbackExecutor.execute() 我們從這里入手实柠。
我們發(fā)現(xiàn)在 Retrofit 的 build() 方法里:

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

平臺默認的回調(diào)調(diào)度器,連忙回到工具箱看看:

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

我們發(fā)現(xiàn)善涨,Android 默認的調(diào)度器是主線程的 Handler ,execute()方法也只是 mainHandler.post() 草则。

所以這下就可以解決我們的疑問了钢拧。

callbackExecutor.execute(new Runnable() {
  @Override public void run() {
    if (delegate.isCanceled()) {
      // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
      callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
    } else {
      callback.onResponse(ExecutorCallbackCall.this, response);
    }
  }
});

這段代碼我們就可以改寫為:

mainHandler.post(new Runnable() {
  @Override public void run() {
    if (delegate.isCanceled()) {
      // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
      callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
    } else {
      callback.onResponse(ExecutorCallbackCall.this, response);
    }
  }
});

如果看到這,還不理解為什么那就得好好補補 handler 的知識啦炕横!

我這里推薦 melo 寫的這篇源内,風趣易懂 帶著這篇去通關所有Handler的提問

最后放上兩張開源社區(qū)畫的流程圖份殿,我覺得特別清晰:

23:27:17.jpg
23:28:01.jpg

以上膜钓∷越唬〃′?`)

參考

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颂斜,隨后出現(xiàn)的幾起案子夫壁,更是在濱河造成了極大的恐慌,老刑警劉巖沃疮,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盒让,死亡現(xiàn)場離奇詭異,居然都是意外死亡司蔬,警方通過查閱死者的電腦和手機邑茄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來俊啼,“玉大人肺缕,你說我怎么就攤上這事∈谂粒” “怎么了搓谆?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長豪墅。 經(jīng)常有香客問我泉手,道長,這世上最難降的妖魔是什么偶器? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任斩萌,我火速辦了婚禮,結果婚禮上屏轰,老公的妹妹穿的比我還像新娘颊郎。我一直安慰自己,他們只是感情好霎苗,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布姆吭。 她就那樣靜靜地躺著,像睡著了一般唁盏。 火紅的嫁衣襯著肌膚如雪内狸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天厘擂,我揣著相機與錄音昆淡,去河邊找鬼。 笑死刽严,一個胖子當著我的面吹牛昂灵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼眨补,長吁一口氣:“原來是場噩夢啊……” “哼管削!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撑螺,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤含思,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后实蓬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茸俭,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年安皱,在試婚紗的時候發(fā)現(xiàn)自己被綠了调鬓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡酌伊,死狀恐怖腾窝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情居砖,我是刑警寧澤虹脯,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站奏候,受9級特大地震影響循集,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔗草,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一咒彤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咒精,春花似錦镶柱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至范咨,卻和暖如春故觅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背湖蜕。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工逻卖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昭抒。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親灭返。 傳聞我的和親對象是個殘疾皇子盗迟,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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