[Android] Retrofit 源碼分析之執(zhí)行流程

前言

由于是第一次自己翻看源代碼進行學(xué)習(xí),加上基礎(chǔ)不好围橡,在看源代碼的過程中簡直痛苦不堪暖混,但同時也暴露出了自己的許多問題。我覺得學(xué)習(xí)源代碼是一件耗時但也收益頗多的學(xué)習(xí)方式翁授,哪怕你暫時沒有足夠的時間自己去分析學(xué)習(xí)儒恋,也要擅于學(xué)習(xí)別人的經(jīng)驗總結(jié)。

Java 基礎(chǔ)知識點

Retrofit 的功能涉及到了 Java 的『反射』黔漂、『注解』和『動態(tài)代理』诫尽。

公共技術(shù)點之 Java 反射 Reflection

公共技術(shù)點之 Java 注解 Annotation

公共技術(shù)點之 Java 動態(tài)代理

Retrofit 的主要接口和類

Retrofit 的代碼并不是很多,其底層網(wǎng)絡(luò)通信時交由 OkHttp 來完成的炬守。其包結(jié)構(gòu)如下圖所示:



其中 retrofit2.http 包里面全部是用來定義 HTTP 請求的自定義注解牧嫉。

接口

Call<T>

Call 接口的主要作用是發(fā)送一個 Http 請求,在 Retrofit 中的默認(rèn)實現(xiàn)是 OkHttpCall<T>减途,也可以根據(jù)實際情況實現(xiàn)自己的 Call 類酣藻。Call 實現(xiàn)類需要實現(xiàn)兩個請求發(fā)送方法:

// 同步請求方法,返回請求的結(jié)果
Response<T> execute() throws IOException;

// 異步請求方法鳍置,在 CallBack 中處理返回的結(jié)果
void enqueue(Callback<T> callback);

Callback

Call 的回調(diào)辽剧,該接口是 Retrofit 異步請求數(shù)據(jù)返回的接口,包含兩個方法:

void onResponse(Call<T> call, Response<T> response);
void onFailure(Call<T> call, Throwable t);

Converter

數(shù)據(jù)轉(zhuǎn)換器税产,該接口將 Http 請求返回的數(shù)據(jù)解析成 Java 對象怕轿,我們之前創(chuàng)建 Retrofit實例時有一句:

.addConverterFactory(GsonConverterFactory.create())

就是添加了一個 GsonConverter 來使用 Gson 將我們的結(jié)果轉(zhuǎn)換成 Model 類。

CallAdapter

Call 的適配器辟拷,負(fù)責(zé)將 Call 對象轉(zhuǎn)化成另一個對象撞羽,同樣可在創(chuàng)建 Retrofit 實例時調(diào)用 .addCallAdapterFactory(Factory) 來添加。

Retrofit 執(zhí)行步驟

在上一篇介紹 Retrofit 初步使用的文章里衫冻,已經(jīng)知道 Retrofit 使用的基本步驟诀紊,這里再重新簡單地介紹一遍:

// 創(chuàng)建接口
public interface APIInterface {
    @GET("/users/{user}")
    Call<TestModel> repo(@Path("user") String user);
  }

// 創(chuàng)建 Retrofit 實例
Retrofit retrofit= new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build();

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

// 調(diào)用接口實現(xiàn)類的請求方法,獲取 Call 對象
Call<TestModel> model = service.repo("Guolei1130");

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

Retrofit 步驟分析

這里從代碼層面來對上述步驟中的關(guān)鍵進行簡要的分析:

創(chuàng)建 Retrofit 實例隅俘,生成接口的實現(xiàn)類

生成接口實現(xiàn)類時邻奠,編寫了以下語句:

APIInterface service = retrofit.create(APIInterface.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);
      }
    });
}

這里使用了『動態(tài)代理』,返回了一個 Proxy 代理類为居,調(diào)用接口中的任何方法都會調(diào)用 proxy 里的 invoke 方法碌宴。

調(diào)用接口實現(xiàn)類的請求方法,獲取 Call 對象

接著上一步分析颜骤,我們知道當(dāng)我們調(diào)用請求方法時:

Call<TestModel> model = service.repo("Guolei1130");

實際上會調(diào)用到 Proxy 的 invoke 方法唧喉。在該方法內(nèi)捣卤,下面的三行代碼是最為主要和重要的:

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

第一行 loadServiceMethod(method):

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

該方法會根據(jù) 接口請求方法 method 來建造一個 ServiceMethod忍抽,put 到緩存里并返回八孝。在建造 ServiceMethod時,會調(diào)用 createCallAdapter() 來為 ServiceMethod 添加一個 CallAdapter:

// ServiceMethod.Builder(this, method).build();
public ServiceMethod build() {
  callAdapter = createCallAdapter();
  ...
}

// ServiceMethod.createCallAdapter()
private CallAdapter<?> createCallAdapter() {
  Type returnType = method.getGenericReturnType();
  if (Utils.hasUnresolvableType(returnType)) {
    throw methodError(
        "Method return type must not include a type variable or wildcard: %s", returnType);
  }
  if (returnType == void.class) {
    throw methodError("Service methods cannot return void.");
  }
  Annotation[] annotations = method.getAnnotations();
  try {
    return retrofit.callAdapter(returnType, annotations);
  } catch (RuntimeException e) { // Wide exception range because factories are user code.
    throw methodError(e, "Unable to create call adapter for %s", returnType);
  }
}

這里會根據(jù) method 的返回類型來創(chuàng)建相應(yīng)的 CallAdapter鸠项,由于 Retrofit 的默認(rèn)實現(xiàn)是 OkHttpCall干跛,所以在這里會創(chuàng)建一個默認(rèn)的 DefaultCallAdapter。至此祟绊,我們再回到 invoke() 的最后一行返回語句:

return serviceMethod.callAdapter.adapt(okHttpCall);

調(diào)用了 DefaultCallAdapter 不對傳進來的 Call 對象做任何處理楼入,所以我們通過調(diào)用接口實現(xiàn)類的方法,實際上最終獲得了一個 OkHttpCall 對象牧抽,其具有兩個請求執(zhí)行方法嘉熊,一個同步,一個異步扬舒。

調(diào)用同步方法時阐肤,會使用應(yīng)用線程來發(fā)送請求;調(diào)用異步方法時會通過 OkHttp 的 Dispatcher 提供的線程來執(zhí)行請求讲坎。

總結(jié)

通過本文孕惜,較為粗糙地從表層了解了 Retrofit 執(zhí)行步驟中一步步在代碼中傳遞的過程,盡管暫時沒能深入代碼內(nèi)部透徹解析各個類和方法晨炕,但是通過這次分析衫画,我自己對 Retrofit 的基本步驟已經(jīng)有了更為深入的了解。

另外這種對框架瓮栗,不能說了如指掌削罩,但對他的實現(xiàn)多了一份認(rèn)識的感覺,真的只有自己去研究過才能體會得到费奸。

也希望各位大神能指出本文中結(jié)構(gòu)或理解上的一些錯誤鲸郊,同時希望這篇文章能幫助您粗略了解 Retrofit 的運行。

參考資料

Retrofit2 源碼解析

Retrofit分析-漂亮的解耦套路

Retrofit2 源代碼初步解讀

Retrofit2 源碼解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末货邓,一起剝皮案震驚了整個濱河市秆撮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌换况,老刑警劉巖职辨,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異戈二,居然都是意外死亡舒裤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門觉吭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腾供,“玉大人,你說我怎么就攤上這事“楸睿” “怎么了节值?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榜聂。 經(jīng)常有香客問我搞疗,道長,這世上最難降的妖魔是什么须肆? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任匿乃,我火速辦了婚禮,結(jié)果婚禮上豌汇,老公的妹妹穿的比我還像新娘幢炸。我一直安慰自己,他們只是感情好拒贱,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布阳懂。 她就那樣靜靜地躺著,像睡著了一般柜思。 火紅的嫁衣襯著肌膚如雪岩调。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天赡盘,我揣著相機與錄音号枕,去河邊找鬼。 笑死陨享,一個胖子當(dāng)著我的面吹牛葱淳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抛姑,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赞厕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了定硝?” 一聲冷哼從身側(cè)響起皿桑,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔬啡,沒想到半個月后诲侮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡箱蟆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年沟绪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空猜。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡绽慈,死狀恐怖恨旱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坝疼,我是刑警寧澤搜贤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站裙士,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏管毙。R本人自食惡果不足惜腿椎,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夭咬。 院中可真熱鬧啃炸,春花似錦、人聲如沸卓舵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掏湾。三九已至裹虫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間融击,已是汗流浹背筑公。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尊浪,地道東北人匣屡。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像拇涤,于是被迫代替她去往敵國和親捣作。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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