Retrofit2源碼學(xué)習(xí)筆記(一)

作者:@荒井
所分析的源代碼基于Retrofit 2.3.0版本。

一般我在剛接觸一個(gè)庫(kù)時(shí),會(huì)先大致了解一下它解決什么問(wèn)題,使用了什么技術(shù)等等,這之后我會(huì)動(dòng)手寫(xiě)一個(gè)小例子并運(yùn)行起來(lái)看看效果,那么首先我來(lái)寫(xiě)一個(gè)例子。

根據(jù)官網(wǎng)描述,使用Retrofit2時(shí),是把HTTP請(qǐng)求寫(xiě)成Java接口形式,例如:

public interface ArticleService {
    @GET("users/{user}/articles")
    Call<ResponseBody> listArticles(@Path("user") String user);
}

然后,構(gòu)建一個(gè)Retrofit對(duì)象,并使用它生成ArticleService接口的實(shí)現(xiàn),例如:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("article.sample.com")
    .build();
ArticleService service = retrofit.create(ArticleService.class);

接下來(lái),使用生成的"service"對(duì)象,調(diào)用其中的方法得到一個(gè)Call<ResponseBody>類型對(duì)象"articles",代碼如下:

Call<ResponseBody> articles = service.listArticles("arai");

最后我將這個(gè)請(qǐng)求加入隊(duì)列,并等待返回結(jié)果回調(diào):

articles.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        //成功。
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        //失敗。
    }
});

如果請(qǐng)求發(fā)送成功,即可在onResponse回調(diào)方法中拿到返回的數(shù)據(jù),如果失敗則會(huì)回調(diào)onFailure方法。這樣就是一個(gè)比較完整和基本的Retrofit2使用例子了。為了更好地熟悉和使用Retrofit2這個(gè)庫(kù),我準(zhǔn)備稍微深入地分析其源代碼,那么這篇文章打算從Retrofit.create(Class<T> service)方法開(kāi)始,一步一步揭開(kāi)其神秘的面紗。由于這個(gè)方法的篇幅不算長(zhǎng),先大致看一下它的全貌:

public <T> T create(final Class<T> service) {
    /* 代碼段 1 */
    Utils.validateServiceInterface(service);

    /* 代碼段 2 */
    if (validateEagerly) {
        eagerlyValidateMethods(service);
    }

    /* 代碼段 3 */
    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);
        }
    });
}

我把整個(gè)方法分為3個(gè)代碼段,分為用代碼段1、2、3注釋。

代碼段1非常簡(jiǎn)單,看名字就知道,驗(yàn)證service是否是一個(gè)interface,除此之外,如果點(diǎn)進(jìn)方法查看,會(huì)發(fā)現(xiàn)它還限制了service不可再繼承其它interface,否則也會(huì)拋出異常。

代碼段2,根據(jù)validateEagerly的值決定是否進(jìn)入if,這個(gè)validateEagerly是Retrofit類的一個(gè)布爾常量,還記得如何得到Retrofit類的實(shí)例嗎,沒(méi)錯(cuò),用的是Retrofit的內(nèi)部類Builder的build()方法來(lái)構(gòu)造的,此處不貼代碼,直接給出結(jié)論,validateEagerly的值為默認(rèn)值false,這里不會(huì)進(jìn)入if條件內(nèi)執(zhí)行。

那么怎樣讓其執(zhí)行if內(nèi)代碼,它又做了什么呢?為了一探究竟,我在構(gòu)建Retrofit對(duì)象時(shí)設(shè)validateEagerly的值為true:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("article.sample.com")
    .validateEagerly(true)
    .build();

然后看看這個(gè)if括號(hào)里面的eagerlyValidateMethods(Class<?> service)方法:

private void eagerlyValidateMethods(Class<?> service) {
    Platform platform = Platform.get();
    for (Method method : service.getDeclaredMethods()) {
        if (!platform.isDefaultMethod(method)) {
            loadServiceMethod(method);
        }
    }
}

這個(gè)方法首先會(huì)獲取當(dāng)前的平臺(tái),Retrofit2中定義了2種特殊平臺(tái):Android和Java8,顯然我所在的是Android平臺(tái)。接下來(lái)遍歷service中聲明的方法,在這里發(fā)現(xiàn)Android.isDefaultMethod(Method method)是直接返回false,也就是會(huì)進(jìn)入if塊內(nèi),并對(duì)每一個(gè)method調(diào)用loadServiceMethod(Method method)方法。

代碼段2說(shuō)到這里,讓我們先短暫地跳出來(lái),回顧一下。使用Retrofit2時(shí),開(kāi)發(fā)者定義好代表網(wǎng)絡(luò)請(qǐng)求的interface,然后使用Retrofit.create()方法來(lái)獲取實(shí)現(xiàn)這個(gè)interface的對(duì)象,這里會(huì)遇到比如分析注解的操作,比如之前的例子中的listArticles方法就有注解@GET("users/{user}/articles"),這個(gè)過(guò)程是稍微慢一些的,Retrofit2為interface中的每一個(gè)method創(chuàng)建一個(gè)對(duì)應(yīng)的ServiceMethod對(duì)象,保存這個(gè)過(guò)程中的一些信息,以便后面復(fù)用。那么根據(jù)validateEagerly這個(gè)名字,好像是要盡早執(zhí)行這個(gè)操作的意思,此處暫時(shí)先記著有這樣一回事,后面會(huì)搞明白的。

/* 代碼段 3 */
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.
        /* 代碼段 3.1 */
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }
        /* 代碼段 3.2 */
        if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        /* 代碼段 3.3 */
        ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);
        /* 代碼段 3.4 */
        OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
        /* 代碼段 3.5 */
        return serviceMethod.callAdapter.adapt(okHttpCall);
    }
});

接下來(lái)看代碼段3,這里用到了Java的動(dòng)態(tài)代理,直接返回動(dòng)態(tài)代理對(duì)象,根據(jù)Retrofit.create(final Class<T> service)方法的定義,它返回的是一個(gè)實(shí)現(xiàn)了Service接口的對(duì)象。現(xiàn)在我們重點(diǎn)關(guān)注Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法的第三個(gè)參數(shù),當(dāng)我在實(shí)際調(diào)用返回對(duì)象中的方法時(shí),會(huì)觸發(fā)這個(gè)InvocationHandler對(duì)象"h"中的invoke(Object proxy, Method method, Object[] args)方法,在最開(kāi)始的Retrofit使用例子中已經(jīng)得知,調(diào)用返回的是Call<ResponseBody>類的對(duì)象,那么我分析一下調(diào)用過(guò)程,為了方便分析,我又把它分成若干個(gè)注釋標(biāo)記的代碼段。

代碼段3.1非常簡(jiǎn)單,判斷方法存在于用戶定義的interface中還是Object類中,定義在Object類中的方法是不需要代理的,所以這里判斷false才會(huì)往下面執(zhí)行,如果是true則直接調(diào)用。

代碼段3.2的判斷方法在之前的代碼段2處已經(jīng)分析過(guò),判斷條件會(huì)返回false,這里會(huì)直接跳過(guò)。那么如果沒(méi)有跳過(guò),會(huì)調(diào)用直接調(diào)用interface中的default方法,這是在Java8中才加入的特性,即假如我在之前的例子ArticleService中加入一個(gè)default方法的話,會(huì)直接傳入?yún)?shù)并執(zhí)行這個(gè)方法。

代碼段3.3,在這里果然又遇到了loadServiceMethod(Method method)方法,這正是在分析代碼段2時(shí)沒(méi)有分析完的那個(gè)點(diǎn)。之前我說(shuō)過(guò),Retrofit2會(huì)為interface中的每一個(gè)method建立一個(gè)ServiceMethod<?, ?>對(duì)象,并存儲(chǔ)起來(lái)。這個(gè)對(duì)象中包含了對(duì)method的分析結(jié)果,比如它的返回值、它含有的注解等。loadServiceMethod(Method method)方法的邏輯是先從Retrofit.serviceMethodCache中拿取這個(gè)方法所對(duì)應(yīng)的ServiceMethod對(duì)象,如果拿到就直接返回,拿不到則構(gòu)建這個(gè)對(duì)象,并存儲(chǔ)到Retrofit.serviceMethodCache中。

ServiceMethod對(duì)象的構(gòu)建過(guò)程還是稍微有些復(fù)雜的,它會(huì)分析method的全部注解、返回值的合法性等,并保證所定義的method能提供一個(gè)網(wǎng)絡(luò)請(qǐng)求所必須的信息,同時(shí)還會(huì)獲取這個(gè)method的Converter和CallAdapter,這兩個(gè)概念在Retrofit2中很重要,在最開(kāi)始的例子中,我所定義的interface中的method,返回的類型是Call<ResponseBody>,ResponseBody即代表HTTP請(qǐng)求所返回的Body,通過(guò)解析它得到我們要的數(shù)據(jù)。為了減輕開(kāi)發(fā)者的負(fù)擔(dān),Retrofit2提供了CallAdapter和Converter這兩個(gè)概念,可以將Call和ResponseBody轉(zhuǎn)為其它類型,這讓我們十分受益,例如使用Retrofit2+Rxjava開(kāi)發(fā)時(shí),我們可以將Service中的方法像下面那樣定義,就可以只關(guān)注拿到數(shù)據(jù)后怎么展示,而不用去解析ResponseBody,并且可以直接寫(xiě)出Rxjava式的代碼。

public interface ArticleService {
    @GET("users/{user}/articles")
    Observable<ArticleData> listArticles(@Path("user") String user);
}

解釋完loadServiceMethod(Method method)方法都做了什么,回到分析代碼段2時(shí)留下的問(wèn)題。是否設(shè)置validateEagerly的區(qū)別,在于設(shè)置validateEagerly為true的話,ServiceMethod對(duì)象會(huì)在Retrofit.create()時(shí)就構(gòu)建,而默認(rèn)情況false,會(huì)在第一次調(diào)用方法時(shí)構(gòu)建,至于哪個(gè)好,我猜了一下,可能未必對(duì),我覺(jué)得還要視情況而定,如果一個(gè)interface中的method是在APP運(yùn)行過(guò)程中必然要執(zhí)行的方法,可能提早構(gòu)建好一些,如果并不是APP運(yùn)行期間必須要調(diào)用的方法,那可能默認(rèn)false好一些。我又特意查找了網(wǎng)上對(duì)這一塊有描述的一些文章,覺(jué)得有一種說(shuō)法比較靠譜,即設(shè)validateEagerly為true會(huì)使method定義的正確性在調(diào)用Retrofit.create()就得到驗(yàn)證,這樣方便開(kāi)發(fā)時(shí)進(jìn)行測(cè)試。

代碼段3.4,通過(guò)上一步驟得到的serviceMethod對(duì)象和傳入invoke()方法的參數(shù),建立okHttpCall對(duì)象,OkHttpCall<T>類實(shí)現(xiàn)了Call<T>接口,這個(gè)類主要的作用就是得到它之后,便可以將其加入請(qǐng)求隊(duì)列,發(fā)送網(wǎng)絡(luò)請(qǐng)求。

代碼段3.5,剛才代碼段3.3分析中已經(jīng)說(shuō)過(guò),CallAdapter的作用是將Call<T>轉(zhuǎn)換為其它類型,那么這里由于我沒(méi)有任何設(shè)置,源碼會(huì)使用默認(rèn)的CallAdapter,不進(jìn)行任何轉(zhuǎn)換,所得到的還是Call<T>類型,并直接返回。

那么通過(guò)Retrofit.create()方法得到一個(gè)實(shí)現(xiàn)interface的動(dòng)態(tài)代理對(duì)象的大致過(guò)程到這里就寫(xiě)完了,休息一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,069評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,873評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,635評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,128評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,365評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,733評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,935評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,172評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,582評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,821評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,595評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,908評(píng)論 2 372

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,623評(píng)論 25 708
  • 前言 使用Retrofit已經(jīng)一段時(shí)間了,這貨挺好用的,還很特別,特別是使用接口來(lái)定義請(qǐng)求方式,這用法讓我對(duì)它的源...
    帶心情去旅行閱讀 3,369評(píng)論 3 21
  • 一、什么是Retrofit A type-safe HTTP client for Android and Jav...
    andcoder閱讀 780評(píng)論 2 3
  • 安卓開(kāi)發(fā)領(lǐng)域中,很多重要的問(wèn)題都有很好的開(kāi)源解決方案,例如Square公司提供網(wǎng)絡(luò)請(qǐng)求 OkHttp , Retr...
    aaron688閱讀 1,926評(píng)論 1 20
  • 第一天在簡(jiǎn)書(shū)開(kāi)始寫(xiě)文章,而且以后要堅(jiān)持天天寫(xiě)! 今天想寫(xiě)一下我對(duì)司馬懿的看法,其實(shí)由于《三國(guó)演義》貫穿了整個(gè)90后...
    路揚(yáng)crow閱讀 210評(píng)論 0 0