Retrofit 的神秘面紗

初識Retrofit時总寒,覺得很神奇扶歪,體現(xiàn)在以下兩點:

神奇之一:僅憑注解和接口便可執(zhí)行網(wǎng)絡請求。

public interface GithubService {

    @GET("users/{username}")
    Call<User> getUser(@Path("username") String username);

}

神奇之二:接口方法的返回值變幻無窮摄闸。

無論你是用默認的方式善镰,或者是RxJava,無論是在Java或是Android中調(diào)用年枕,Retrofit均可滿足炫欺,體現(xiàn)在接口的返回值,如下代碼:

public interface GithubService {

    //默認方式
    @GET("users/{username}")
    Call<User> getUserByDefault(@Path("username") String username);

    //RxJava方式
    @GET("users/{username}")
    Observable<User> getUserByRxJava(@Path("username") String username);

}

這兩個神奇之處熏兄,猶如兩層神秘面紗品洛,將Retrofit原本俊俏嬌羞的臉龐,遮掩得若隱若現(xiàn)摩桶。若不揭開面紗桥状,一睹芳容,便無處釋放程序員們最原始的沖動硝清,于是一起來閱讀源碼吧辅斟。

尋找接口的實現(xiàn)類

對于第一個神奇之處,首先我們來看看GithubService接口是如何調(diào)用的:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);
Call<User> call = githubService.getUser("geniusmart");

仔細斟酌這段代碼芦拿,GitHubService本身是個接口砾肺,并調(diào)用了getUser()方法獲得返回值,那么問題來了:

  1. GitHubService是由我們定義的接口防嗡,但是我們并沒有定義實現(xiàn)類变汪,實現(xiàn)類到底在哪里?
  2. 既然我們沒有定義實現(xiàn)類蚁趁,那么大膽假設該實現(xiàn)類是由Retrofit框架提供的裙盾。對于框架而言,該接口是由開發(fā)者自定義的,框架無法做到未卜先知番官,那么他是如何做到的庐完?

以上兩個問題可以總結(jié)為:框架如何動態(tài)生成GitHubService的實現(xiàn)類?答案也不難猜測——動態(tài)代理模式徘熔。(對動態(tài)代理有疑問的同學门躯,可以先看下《公共技術點之 Java 動態(tài)代理》

接下來驗證我們的猜測酷师,GitHubService的來源在此行代碼中:

GitHubService service = retrofit.create(GitHubService.class);

查看create()的源碼讶凉,所有謎底都將揭開:

通過JDK實現(xiàn)動態(tài)代理

這個方法生成并返回了GitHubService的動態(tài)代理對象,在執(zhí)行接口的每個方法時山孔,實際執(zhí)行的是invoke()懂讯,而該方法里的邏輯便是此框架的主體流程,如下代碼:

public Object invoke(Object proxy, Method method, Object... args){
    //1.負責注解的解析
    ServiceMethod serviceMethod = loadServiceMethod(method);
    //2.負責與OKHttp3的對接台颠,處理同步和異步發(fā)送網(wǎng)絡請求
    OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
    //3.第二層面紗褐望,下文再做解釋
    return serviceMethod.callAdapter.adapt(okHttpCall);
}

這個方法里,做了三件事情:

  1. 解析方法的注解串前,比如get/post瘫里、url、Headers等荡碾,解析的結(jié)果存放于ServiceMethod對象中谨读。
  2. 創(chuàng)建OkHttpCall,該對象負責與OKHttp3對接玩荠,處理同步或異步的網(wǎng)絡請求漆腌。
  3. 這是第二層神秘面紗的謎底贼邓,下文再做解釋阶冈。

至此,我們揭開了Retrofit的第一層神秘面紗塑径,這是一位簡單而優(yōu)雅的小女子女坑,表面樸實無華,恬靜清新统舀,令人迫不及待想要揭開她的第二層面紗匆骗,知曉她的內(nèi)心世界。

變幻無窮的適配

首先來回顧下上文提到的第二個神秘之處誉简,同樣是獲取用戶信息的網(wǎng)絡請求碉就,可以返回Call<User>Observable<User>,這是如何做到的闷串?

上文中瓮钥,invoke()做的第三件事情便是獲取返回值,如下:

return serviceMethod.callAdapter.adapt(okHttpCall);

其中,adapt是適配的意思碉熄,其目的是變廢為寶桨武,將指定的輸入類型適配成實際需要的輸出類型,來查看下它的源碼:

public interface CallAdapter<T> {
  <R> T adapt(Call<R> call);
}

我們重點關注下adapt的輸入輸出锈津。這里的設計非常大膽呀酸,輸入是有約束的,即 Call類型琼梆,而輸出為泛型T性誉,也就是說輸出是沒有任何約束的,如此一來叮叹,擴展性極強艾栋,可以指定任何的類型。

剩下來的問題便是:

  1. adapt由哪個對象觸發(fā)蛉顽,即CallAdapter的具體實現(xiàn)是什么蝗砾?
  2. 輸入類型Call的具體實現(xiàn)是什么?
  3. 輸出類型T的具體實現(xiàn)是什么携冤?

這里直接給出答案悼粮,如下兩圖:

默認方式的適配
RxJava方式的適配
  • 通過上面兩圖,可以看到曾棕,輸入對象是固定的扣猫,即OkHttpCall對象,網(wǎng)絡請求實際上便是由該對象發(fā)起的翘地。
  • 默認方式的輸出對象是ExecutorCallbackCall申尤,它持有OkHttpCall對象,指定了網(wǎng)絡請求執(zhí)行結(jié)束后的回調(diào)函數(shù)在UI線程中執(zhí)行衙耕。
  • RxJava方式的輸出對象是Observable昧穿,持有該對象便具備函數(shù)式編程的能力。
  • adapt()的調(diào)用者——適配器CallAdapter是動態(tài)配置的橙喘,默認方式無需配置时鸵,如果使用RxJava,則需要配置適配器的工廠對象厅瞎,如下代碼:
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();

這樣設計簡直是解耦得一塌糊涂饰潜,我們可以任意變換調(diào)用方式,也可以任意擴展CallAdapter來定義新的調(diào)用方式和簸,而框架無需做任何調(diào)整彭雾。比如JakeWharton前幾天開源的RxJava2的適配——retrofit2-rxjava2-adapter,比如針對Google函數(shù)式編程庫 Agera的適配——retrofit-agera-call-adapter锁保,簡直O(jiān)CP得一塌糊涂薯酝。

至此南誊,第二層神秘面紗緩緩而落,這是一位心靈手巧的奇女子蜜托,你給她一針一線抄囚,她還你一幅刺繡,刺繡上可以是錦繡山河橄务,也可以是紫氣東來幔托;你給她新鮮的食材,她變幻出色香味俱全的美味佳肴蜂挪。

總結(jié)

動態(tài)代理重挑、適配是Retrofit的核心,理解清楚這兩點棠涮,再去梳理框架的其他細節(jié)谬哀,方可事半功倍。除此之外严肪,框架內(nèi)部使用了大量的Builder模式和Factory模式史煎,這兩個模式使得創(chuàng)建對象和獲取對象更有條理,更清晰易懂驳糯。值得一提的是篇梭,這個框架配備了完整的單元測試用例,非常值得我們學習酝枢。

參考文章

http://square.github.io/retrofit/
https://github.com/square/retrofit

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恬偷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帘睦,更是在濱河造成了極大的恐慌袍患,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竣付,死亡現(xiàn)場離奇詭異诡延,居然都是意外死亡,警方通過查閱死者的電腦和手機卑笨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門孕暇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仑撞,“玉大人赤兴,你說我怎么就攤上這事∷硐” “怎么了桶良?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沮翔。 經(jīng)常有香客問我陨帆,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任疲牵,我火速辦了婚禮承二,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纲爸。我一直安慰自己亥鸠,他們只是感情好,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布识啦。 她就那樣靜靜地躺著负蚊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颓哮。 梳的紋絲不亂的頭發(fā)上家妆,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機與錄音冕茅,去河邊找鬼伤极。 笑死,一個胖子當著我的面吹牛姨伤,可吹牛的內(nèi)容都是我干的塑荒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼姜挺,長吁一口氣:“原來是場噩夢啊……” “哼齿税!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起炊豪,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤凌箕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后词渤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牵舱,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年缺虐,在試婚紗的時候發(fā)現(xiàn)自己被綠了芜壁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡高氮,死狀恐怖慧妄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剪芍,我是刑警寧澤塞淹,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站罪裹,受9級特大地震影響饱普,放射性物質(zhì)發(fā)生泄漏运挫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一套耕、第九天 我趴在偏房一處隱蔽的房頂上張望谁帕。 院中可真熱鬧,春花似錦冯袍、人聲如沸雇卷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽关划。三九已至,卻和暖如春翘瓮,著一層夾襖步出監(jiān)牢的瞬間贮折,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工资盅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留调榄,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓呵扛,卻偏偏與公主長得像每庆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子今穿,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

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