Retrofit2 源碼解析

本文的源碼分析基于Retrofit 2,和Retrofit 1.0的Api有較大的不同遇绞, 本文主要分為幾部分:1、Retrofit 是什么燎窘,2摹闽、Retrofit怎么用,3褐健、Retrofit的原理是什么钩骇,4、Retrofit的源碼分析铝量。

1 Retrofit是什么

來自Retrofit官網(wǎng)的介紹:

A type-safe HTTP client for Android and Java

簡單的說它是一個基于OkHttp的RESTFUL Api請求工具倘屹,從功能上來說和Google的Volley功能上很相似,但是使用上很不相似慢叨。Volley使用上更加原始而且符合使用者的直覺纽匙,當(dāng)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中的代碼就會很簡潔方便閱讀

2 Retrofit怎么用

雖然Retrofit官網(wǎng)已經(jīng)說明了拿愧,我還是要按照我的思路說一下它的使用方法

首先,你需要創(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ù)膳灶,并且處理
responsecall.enqueue(new Callback<ZhuanLanAuthor>() { 
    @Override 
    public void onResponse(Response<ZhuanLanAuthor> author) {

         System.out.println("name: " + author.getName()); 
    } 
    @Override 
    public void onFailure(Throwable t) { 

    }
  });

3 Retrofit的原理

從上面Retrofit的使用來看,Retrofit就是充當(dāng)了一個適配器(Adapter)的角色:將一個Java接口翻譯成一個Http請求立由,然后用OkHttp去發(fā)送這個請求Volley描述一個HTTP請求是需要創(chuàng)建一個Request對象轧钓,而執(zhí)行這個請求呢,就是把這個請求對象放到一個隊列中锐膜,在網(wǎng)絡(luò)線程中用HttpUrlConnection去請求
Retrofit是怎么做的呢毕箍?
就是:
Java的動態(tài)代理**

動態(tài)代理

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)代理

你看上面代碼媒咳,獲取數(shù)據(jù)的代碼就是這句:

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

上面api對象其實是一個動態(tài)代理對象,并不是一個真正的ZhuanLanApi接口的implements產(chǎn)生的對象种远,當(dāng)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接口的默認(rèn)實現(xiàn)是OkHttpCall,它默認(rèn)使用OkHttp3作為底層http請求client

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

4 Retrofit的源碼分析

先來看一下Retrofit源碼的組成:

  1. 一個retrofit2.http包,里面全部是定義HTTP請求的Java注解钧唐,比如GET忙灼、POST、PUT钝侠、DELETE该园、Headers、Path帅韧、Query等等里初。

  2. 余下的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默認(rèn)的實現(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)生的對象该溯,當(dāng)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方法是需要嚴(yán)謹(jǐn)

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對象

如何在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蛙粘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垫卤,一起剝皮案震驚了整個濱河市威彰,隨后出現(xiàn)的幾起案子出牧,更是在濱河造成了極大的恐慌,老刑警劉巖歇盼,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舔痕,死亡現(xiàn)場離奇詭異,居然都是意外死亡豹缀,警方通過查閱死者的電腦和手機伯复,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邢笙,“玉大人啸如,你說我怎么就攤上這事〉撸” “怎么了叮雳?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長妇汗。 經(jīng)常有香客問我帘不,道長,這世上最難降的妖魔是什么杨箭? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任寞焙,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捣郊。我一直安慰自己辽狈,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布呛牲。 她就那樣靜靜地躺著稻艰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侈净。 梳的紋絲不亂的頭發(fā)上尊勿,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音畜侦,去河邊找鬼元扔。 笑死,一個胖子當(dāng)著我的面吹牛旋膳,可吹牛的內(nèi)容都是我干的澎语。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼验懊,長吁一口氣:“原來是場噩夢啊……” “哼擅羞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起义图,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤减俏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碱工,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娃承,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年怕篷,在試婚紗的時候發(fā)現(xiàn)自己被綠了历筝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡廊谓,死狀恐怖梳猪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒸痹,我是刑警寧澤春弥,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站电抚,受9級特大地震影響惕稻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝙叛,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一俺祠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蜘渣、人聲如沸淌铐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腿准。三九已至,卻和暖如春拾碌,著一層夾襖步出監(jiān)牢的瞬間吐葱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工校翔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弟跑,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓防症,卻偏偏與公主長得像孟辑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蔫敲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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