Retrofit2.0源碼解析

在開發(fā)Android APP時,肯定會使用到Http請求與服務(wù)器通信贱迟,上傳或下載數(shù)據(jù)等功能姐扮。目前開源的Http請求工具也有很多,比如Google開發(fā)的Volley衣吠,loopj的Android Async Http茶敏,Square開源的OkHttp或者Retrofit等。現(xiàn)在比較流行的網(wǎng)絡(luò)庫是:Retrofit2.0+RxJava2.0+oKhttp3.0缚俏。

我覺得Retrofit 無疑是這幾個當中最好用的一個惊搏,設(shè)計這個庫的思路很特別而且巧妙贮乳。Retrofit的代碼很少,花點時間讀它的源碼肯定會收獲很多恬惯。

本文的源碼分析基于Retrofit 2向拆,和Retrofit 1.0的Api有較大的不同, 本文主要分為幾部分:

0酪耳、Retrofit 是什么
1浓恳、Retrofit怎么用
2、Retrofit的原理是什么
3碗暗、我的心得與看法
  • Retrofit是什么
來自Retrofit官網(wǎng)的介紹:
A type-safe HTTP client for Android and Java

簡單的說它是一個基于OkHttp的RESTFUL Api請求工具颈将,從功能上來說和Google的Volley功能上很相似,但是使用上很不相似言疗。

Volley使用上更加原始而且符合使用者的直覺晴圾,當App要發(fā)送一個Http請求時,你需要先創(chuàng)建一個Request對象噪奄,指定這個Request用的是GET死姚、POST或其他方法,一個api 地址梗醇,一個處理response的回調(diào)知允,如果是一個POST請求,那么你還需要給這個Request對象設(shè)置一個body叙谨,有時候你還需要自定義添加Header什么的温鸽,然后將這個Request對象添加到RequestQueue中,接下去檢查Cache以及發(fā)送Http請求的事情手负,Volley會幫你處理涤垫。如果一個App中api不同的api請求很多,這樣代碼就會很難看竟终。

而Retrofit可以讓你簡單到調(diào)用一個Java方法的方式去請求一個api蝠猬,這樣App中的代碼就會很簡潔方便閱讀

  • Retrofit怎么用

雖然Retrofit官網(wǎng)已經(jīng)說明了,我還是要按照我的思路說一下它的使用方法
比如你要請求這么一個api统捶,查看知乎專欄的某個作者信息:
https://zhuanlan.zhihu.com/api/columns/{user}

首先榆芦,你需要創(chuàng)建一個Retrofit對象,并且指定api的域名:

public static final String API_URL = "https://zhuanlan.zhihu.com";

Create a very simple REST adapter which points the Zhuanlan API.
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(API_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

其次喘鸟,你要根據(jù)api新建一個Java接口匆绣,用Java注解來描述這個api

public interface ZhuanLanApi {
    @GET("/api/columns/{user} ")
    Call<ZhuanLanAuthor> getAuthor(@Path("user") String user)
}

再用這個retrofit對象創(chuàng)建一個ZhuanLanApi對象:

ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);
Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");

這樣就表示你要請求的api是https://zhuanlan.zhihu.com/api/columns/qinchao

最后你就可以用這個call對象獲得數(shù)據(jù)了,enqueue方法是異步發(fā)送http請求的什黑,如果你想用同步的方式發(fā)送可以使用execute()方法崎淳,call對象還提供cancel()、isCancel()等方法獲取這個Http請求的狀態(tài)

// 請求數(shù)據(jù)愕把,并且處理response
call.enqueue(new Callback<ZhuanLanAuthor>() {
    @Override
    public void onResponse(Response<ZhuanLanAuthor> author) {
        System.out.println("name: " + author.getName());
    }
    @Override
    public void onFailure(Throwable t) {
    }
});

看到?jīng)]拣凹,Retrofit只要創(chuàng)建一個接口來描述Http請求森爽,然后可以讓我們可以像調(diào)用Java方法一樣請求一個Api,是不是覺得很神奇嚣镜,很不可思議E莱佟!

  • Retrofit的原理

從上面Retrofit的使用來看祈惶,Retrofit就是充當了一個適配器(Adapter)的角色:將一個Java接口翻譯成一個Http請求雕旨,然后用OkHttp去發(fā)送這個請求

Volley描述一個HTTP請求是需要創(chuàng)建一個Request對象扮匠,而執(zhí)行這個請求呢捧请,就是把這個請求對象放到一個隊列中,在網(wǎng)絡(luò)線程中用HttpUrlConnection去請求

問題來了:Retrofit是怎么做的呢棒搜?

答案很簡單疹蛉,就是:Java的動態(tài)代理

動態(tài)代理
我剛開始看Retrofit的代碼,我對下面這句代碼感到很困惑:

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

我給Retrofit對象傳了一個ZhuanLanApi接口的Class對象力麸,怎么又返回一個ZhuanLanApi對象呢可款?進入create方法一看,沒幾行代碼克蚂,但是我覺得這幾行代碼就是Retrofit的精妙的地方:

/** Create an implementation of the API defined by the {@code service} interface. */
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);
      }
    });
}

create方法就是返回了一個Proxy.newProxyInstance動態(tài)代理對象闺鲸。那么問題來了...

動態(tài)代理是個什么東西?
看Retrofit代碼之前我知道Java動態(tài)代理是一個很重要的東西埃叭,比如在Spring框架里大量的用到摸恍,但是它有什么用呢?

Java動態(tài)代理就是給了程序員一種可能:當你要調(diào)用某個Class的方法前或后赤屋,插入你想要執(zhí)行的代碼

比如你要執(zhí)行某個操作前立镶,你必須要判斷這個用戶是否登錄,或者你在付款前类早,你需要判斷這個人的賬戶中存在這么多錢媚媒。這么簡單的一句話,我相信可以把一個不懂技術(shù)的人也講明白Java動態(tài)代理是什么東西了涩僻。

為什么要使用動態(tài)代理
你看上面代碼缭召,獲取數(shù)據(jù)的代碼就是這句:

Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");

上面api對象其實是一個動態(tài)代理對象,并不是一個真正的ZhuanLanApi接口的implements產(chǎn)生的對象逆日,當api對象調(diào)用getAuthor方法時會被動態(tài)代理攔截嵌巷,然后調(diào)用Proxy.newProxyInstance方法中的InvocationHandler對象,它的invoke方法會傳入3個參數(shù):

  • Object proxy: 代理對象屏富,不關(guān)心這個
  • Method method:調(diào)用的方法晴竞,就是getAuthor方法
  • Object... args:方法的參數(shù),就是"qinchao"

而Retrofit關(guān)心的就是method和它的參數(shù)args狠半,接下去Retrofit就會用Java反射獲取到getAuthor方法的注解信息噩死,配合args參數(shù)颤难,創(chuàng)建一個ServiceMethod對象

ServiceMethod就像是一個中央處理器,傳入Retrofit對象和Method對象已维,調(diào)用各個接口和解析器行嗤,最終生成一個Request,包含api 的域名垛耳、path栅屏、http請求方法、請求頭堂鲜、是否有body栈雳、是否是multipart等等。最后返回一個Call對象缔莲,Retrofit2中Call接口的默認實現(xiàn)是OkHttpCall哥纫,它默認使用OkHttp3作為底層http請求client

使用Java動態(tài)代理的目的就要攔截被調(diào)用的Java方法,然后解析這個Java方法的注解痴奏,最后生成Request由OkHttp發(fā)送

  • Retrofit的源碼分析
    想要弄清楚Retrofit的細節(jié)蛀骇,先來看一下Retrofit源碼的組成:

    • 一個retrofit2.http包,里面全部是定義HTTP請求的Java注解读拆,比如GET擅憔、POST、PUT檐晕、DELETE暑诸、Headers、Path棉姐、Query等等
    • 余下的retrofit2包中幾個類和接口就是全部retrofit的代碼了屠列,代碼真的很少,很簡單伞矩,因為retrofit把網(wǎng)絡(luò)請求這部分功能全部交給了OkHttp了
  • Retrofit接口
    Retrofit的設(shè)計非常插件化而且輕量級笛洛,真的是非常高內(nèi)聚而且低耦合,這個和它的接口設(shè)計有關(guān)乃坤。Retrofit中定義了4個接口:

    • Callback<T> 這個接口就是retrofit請求數(shù)據(jù)返回的接口苛让,只有兩個方法
    void onResponse(Response<T> response);
    void onFailure(Throwable t);
    
    • Converter<F, T>
      這個接口主要的作用就是將HTTP返回的數(shù)據(jù)解析成Java對象,主要有Xml湿诊、Gson狱杰、protobuf等等,你可以在創(chuàng)建Retrofit對象時添加你需要使用的Converter實現(xiàn)(看上面創(chuàng)建Retrofit對象的代碼)

    • Call<T>
      這個接口主要的作用就是發(fā)送一個HTTP請求厅须,Retrofit默認的實現(xiàn)是OkHttpCall<T>仿畸,你可以根據(jù)實際情況實現(xiàn)你自己的Call類,這個設(shè)計和Volley的HttpStack接口設(shè)計的思想非常相似,子類可以實現(xiàn)基于HttpClient或HttpUrlConnetction的HTTP請求工具错沽,這種設(shè)計非常的插件化簿晓,而且靈活

    • CallAdapter<T>
      上面說到過,CallAdapter中屬性只有responseType一個千埃,還有一個<R> T adapt(Call<R> call)方法憔儿,這個接口的實現(xiàn)類也只有一個,DefaultCallAdapter放可。這個方法的主要作用就是將Call對象轉(zhuǎn)換成另一個對象谒臼,可能是為了支持RxJava才設(shè)計這個類的吧

  • Retrofit的運行過程
    上面講到ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);代碼返回了一個動態(tài)代理對象,而執(zhí)行Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");代碼時返回了一個OkHttpCall對象耀里,拿到這個Call對象才能執(zhí)行HTTP請求蜈缤。
    上面api對象其實是一個動態(tài)代理對象,并不是一個真正的ZhuanLanApi接口的implements產(chǎn)生的對象备韧,當api對象調(diào)用getAuthor方法時會被動態(tài)代理攔截劫樟,然后調(diào)用Proxy.newProxyInstance方法中的InvocationHandler對象, 創(chuàng)建一個ServiceMethod對象

    ServiceMethod serviceMethod = loadServiceMethod(method);
    OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
    return serviceMethod.callAdapter.adapt(okHttpCall);
    
  • 創(chuàng)建ServiceMethod

剛才說到织堂,ServiceMethod就像是一個中央處理器,具體來看一下創(chuàng)建這個ServiceMethod的過程是怎么樣的

第一步奶陈,獲取到上面說到的3個接口對象:

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

第二步易阳,解析Method的注解,主要就是獲取Http請求的方法吃粒,比如是GET還是POST還是其他形式潦俺,如果沒有,程序就會報錯徐勃,還會做一系列的檢查事示,比如如果在方法上注解了@Multipart,但是Http請求方法是GET僻肖,同樣也會報錯肖爵。因此,在注解Java方法是需要嚴謹

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

if (httpMethod == null) {
   throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}

第三步臀脏,比如上面api中帶有一個參數(shù){user}劝堪,這是一個占位符,而真實的參數(shù)值在Java方法中傳入揉稚,那么Retrofit會使用一個ParameterHandler來進行替換:

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];

最后秒啦,ServiceMethod會做其他的檢查,比如用了@FormUrlEncoded注解搀玖,那么方法參數(shù)中必須至少有一個@Field或@FieldMap

  • 執(zhí)行Http請求

之前講到余境,OkHttpCall是實現(xiàn)了Call接口的,并且是真正調(diào)用OkHttp3發(fā)送Http請求的類。OkHttp3發(fā)送一個Http請求需要一個Request對象芳来,而這個Request對象就是從ServiceMethod的toRequest返回的暴氏。

總的來說,OkHttpCall就是調(diào)用ServiceMethod獲得一個可以執(zhí)行的Request對象绣张,然后等到Http請求返回后答渔,再將response body傳入ServiceMethod中,ServiceMethod就可以調(diào)用Converter接口將response body轉(zhuǎn)成一個Java對象侥涵。

結(jié)合上面說的就可以看出沼撕,ServiceMethod中幾乎保存了一個api請求所有需要的數(shù)據(jù),OkHttpCall需要從ServiceMethod中獲得一個Request對象芜飘,然后得到response后务豺,還需要傳入ServiceMethod用Converter轉(zhuǎn)換成Java對象。

你可能會覺得我只要發(fā)送一個HTTP請求嗦明,你要做這么多事情不會很“慢”嗎笼沥?不會很浪費性能嗎?

我覺得娶牌,首先現(xiàn)在手機處理器主頻非常高了奔浅,解析這個接口可能就花1ms可能更少的時間(我沒有測試過),面對一個HTTP本來就需要幾百ms诗良,甚至幾千ms來說不值得一提汹桦;而且Retrofit會對解析過的請求進行緩存,就在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();這個對象中鉴裹。

  • 如何在Retrofit中使用RxJava

由于Retrofit設(shè)計的擴展性非常強舞骆,你只需要添加一個CallAdapter就可以了

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

上面代碼創(chuàng)建了一個Retrofit對象,支持Proto和Gson兩種數(shù)據(jù)格式径荔,并且還支持RxJava

  • 最后

Retrofit非常巧妙的用注解來描述一個HTTP請求督禽,將一個HTTP請求抽象成一個Java接口,然后用了Java動態(tài)代理的方式总处,動態(tài)的將這個接口的注解“翻譯”成一個HTTP請求狈惫,最后再執(zhí)行這個HTTP請求
Retrofit的功能非常多的依賴Java反射,代碼中其實還有很多細節(jié)辨泳,比如異常的捕獲虱岂、拋出和處理,大量的Factory設(shè)計模式(為什么要這么多使用Factory模式菠红?)
Retrofit中接口設(shè)計的恰到好處第岖,在你創(chuàng)建Retrofit
對象時,讓你有更多更靈活的方式去處理你的需求试溯,比如使用不同的Converter蔑滓、使用不同的CallAdapter,這也就提供了你使用RxJava來調(diào)用Retrofit的可能.

我也慢慢看了Picasso和Retrofit的代碼了,收獲還是很多的键袱,也更加深入的理解面向接口的編程方法燎窘,這個寫代碼就是好的代碼就是依賴接口而不是實現(xiàn)最好的例子

好感謝開源的世界,讓我能讀到大牛的代碼蹄咖。我一直覺得一個人如果沒有讀過好的代碼是不太可能寫出好代碼的褐健。什么是好的代碼?像Picasso和Retrofit這樣的就是好的代碼澜汤,擴展性強蚜迅、低耦合、插件化.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俊抵,一起剝皮案震驚了整個濱河市谁不,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徽诲,老刑警劉巖刹帕,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谎替,居然都是意外死亡偷溺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門院喜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亡蓉,“玉大人,你說我怎么就攤上這事喷舀。” “怎么了淋肾?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵硫麻,是天一觀的道長。 經(jīng)常有香客問我樊卓,道長拿愧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任碌尔,我火速辦了婚禮浇辜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唾戚。我一直安慰自己柳洋,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布叹坦。 她就那樣靜靜地躺著熊镣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绪囱,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天测蹲,我揣著相機與錄音,去河邊找鬼鬼吵。 笑死扣甲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的齿椅。 我是一名探鬼主播琉挖,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼媒咳!你這毒婦竟也來了粹排?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤涩澡,失蹤者是張志新(化名)和其女友劉穎顽耳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妙同,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡射富,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了粥帚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胰耗。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芒涡,靈堂內(nèi)的尸體忽然破棺而出柴灯,到底是詐尸還是另有隱情,我是刑警寧澤费尽,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布赠群,位于F島的核電站,受9級特大地震影響旱幼,放射性物質(zhì)發(fā)生泄漏查描。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一柏卤、第九天 我趴在偏房一處隱蔽的房頂上張望冬三。 院中可真熱鬧,春花似錦缘缚、人聲如沸勾笆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匠襟。三九已至钝侠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酸舍,已是汗流浹背帅韧。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留啃勉,地道東北人忽舟。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像淮阐,于是被迫代替她去往敵國和親叮阅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

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