Retrofit原理淺析

Retrofit原理淺析

做Android也有幾年了急黎,各種Android http類庫也用過不少慰安,自己的做過的項(xiàng)目中也一直在嘗試怎么封裝讓API接口定義和API使用者解耦,但一直感覺沒有可以讓人滿意的框架拧晕,直到無意中在網(wǎng)上看到了JakeWharton大神的Retrofit這個(gè)類庫睛榄。不得不說,真心是炒雞解耦落剪。

下面來簡單分析一下這個(gè)Retrofit2的使用和實(shí)現(xiàn)睁本。

類庫使用示例

首先定義請(qǐng)求接口

public interface GitHub {
    @GET("/repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> contributors(
        @Path("owner") String owner,
        @Path("repo") String repo);
  }

然后通過Retrofit生成一個(gè)剛才定義的接口的實(shí)現(xiàn)類,使用的是動(dòng)態(tài)代理忠怖。

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

// Create an instance of our GitHub API interface.
GitHub github = retrofit.create(GitHub.class);

之后就可以使用接口進(jìn)行請(qǐng)求了

// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");

類庫原理解析

注解

Retrofit使用注解+java接口來定義后臺(tái)服務(wù)API接口

注解主要分為 方法注解 和 參數(shù)注解

注解 類型 作用
@GET 方法注解 表明HTTP請(qǐng)求方法為GET,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@POST 方法注解 表明HTTP請(qǐng)求方法為POST,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@PUT 方法注解 表明HTTP請(qǐng)求方法為PUT,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@DELETE 方法注解 表明http請(qǐng)求方法為DELETE,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@PATCH 方法注解 表明HTTP請(qǐng)求方法為PATCH,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@HEAD 方法注解 表明HTTP請(qǐng)求方法為HEAD,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@OPTIONS 方法注解 表明HTTP請(qǐng)求方法為OPTIONS,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@HTTP 方法注解 通過@HTTP注解指定http協(xié)議的請(qǐng)求方法,是否允許body,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url
@FormUrlEncoded 方法注解 表明發(fā)起HTTP請(qǐng)求的RequestBody是form表單方式
@Multipart 方法注解 表明發(fā)起HTTP請(qǐng)求的RequestBody是Multipar方式
@Headers 方法注解 使用注解的value值數(shù)組作為HTTP請(qǐng)求的頭呢堰,用于一些固定的Header參數(shù)
@Streaming 方法注解 用于需要直接返回流的函數(shù)
@Url 參數(shù)注解 HTTP請(qǐng)求的url路徑(相對(duì)/絕對(duì)),可以包含{path_holder},如:http://xxx.com/{user_holder}/detail
@Path 參數(shù)注解 用于動(dòng)態(tài)替換URL路徑中的path_holder
@Body 參數(shù)注解 表明此參數(shù)用作HTTP請(qǐng)求的body
@Field 參數(shù)注解 表明此參數(shù)用作HTTP請(qǐng)求的form表單參數(shù),key為注解的value值
@FieldMap 參數(shù)注解 以map形式傳入的form表單參數(shù)
@Header 參數(shù)注解 表明此參數(shù)用作HTTP請(qǐng)求的header凡泣,key為注解的value值
@HeaderMap 參數(shù)注解 以map形式傳入的多個(gè)header鍵值對(duì)
@Part 參數(shù)注解 表明參數(shù)為Http的multipart參數(shù)之一
@PartMap 參數(shù)注解 以map形式傳入的multipart參數(shù)表
@Query 參數(shù)注解 GET方法的query參數(shù)枉疼,用于拼接完整請(qǐng)求路徑
@QueryMap 參數(shù)注解 以map傳入的GET方法的query參數(shù)皮假,用于拼接完整請(qǐng)求路徑

生成動(dòng)態(tài)代理實(shí)例

Retrofit使用的關(guān)鍵一步就是Retrofit.create函數(shù)創(chuàng)建接口動(dòng)態(tài)代理的示例,代碼如下

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
  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);
          }
        });
  }

可以看到是為接口的每個(gè)method創(chuàng)建了一個(gè)對(duì)應(yīng)的ServiceMethod,并使用這個(gè)ServiceMethod對(duì)象創(chuàng)建OkHttpCall,并使用ServiceMethod實(shí)例的callAdapter來調(diào)用okhttpCall并返回結(jié)果骂维。

調(diào)用流程

通過上面代碼可以看到調(diào)用關(guān)鍵的就是三步:

  • 1 加載對(duì)應(yīng)method的ServiceMethod實(shí)例
  • 2 使用ServiceMethod實(shí)例和方法調(diào)用參數(shù)創(chuàng)建OkHttpCall
  • 3 調(diào)用serviceMethod.callAdapter.adapt(okHttpCall)來產(chǎn)生method所定義的返回(Call<T>或者其他自定義CallAdapter支持的返回)

第一步惹资、加載對(duì)應(yīng)method的ServiceMethod實(shí)例

ServiceMethod中有以下四個(gè)變量比較重要

final okhttp3.Call.Factory callFactory;
final CallAdapter<?> callAdapter;
private final Converter<ResponseBody, T> responseConverter;
private final ParameterHandler<?>[] parameterHandlers;
  • callFactory是用來創(chuàng)建真正要執(zhí)行的okhttp3.Call的工廠類,可以Retrofit.Builder中設(shè)置航闺,如果不設(shè)置褪测,默認(rèn)會(huì)new一個(gè)OkHttpClient作為callFactory
  • callAdapter是用來最終處理OkHttpCall實(shí)例并返回接口Method所定義的返回
  • responseConverter 用來將Http請(qǐng)求的結(jié)果轉(zhuǎn)換成接口Method所定義的結(jié)果(return或者Callback<T>中的T)
  • parameterHandlers 根據(jù)接口Method參數(shù)的注解所生成的參數(shù)處理Handler數(shù)組

然后我們來看Retrofit.loadServiceMethod方法

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

可以看到此處先檢查serviceMethodCache是否有該method對(duì)應(yīng)的ServiceMethod實(shí)例緩存,如果沒有潦刃,則創(chuàng)建一個(gè)該method對(duì)應(yīng)的ServiceMethod實(shí)例并保存到緩存中侮措。
ServiceMethod的創(chuàng)建使用的是建造者模式。

在ServiceMethod.Builder的build方法中乖杠,通過解析傳入的method的方法定義(參數(shù)類型萝毛,返回類型,參數(shù)注解滑黔,方法注解)生成對(duì)應(yīng)的callAdapter,responseConverter,parameterHandlers及其他一些創(chuàng)建請(qǐng)求需要用到的信息。

    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      ......檢查返回結(jié)果類型......
      responseConverter = createResponseConverter();
      //生成方法注解的處理器
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      .....方法與注解合法性檢查.....
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        .....注解合法性檢查....
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
      ......方法與注解合法性檢查......
      return new ServiceMethod<>(this);
    }

此處在ServiceMethod.Builder.build()過程在生成過程中還會(huì)對(duì)method的定義做合法性檢查环揽,如:http方法是get就不允許方法參數(shù)中有body類型的參數(shù)略荡;方法為post則必須有參數(shù)為Body類型。

第二步歉胶、使用ServiceMethod實(shí)例和方法調(diào)用參數(shù)創(chuàng)建OkHttpCall

獲取到method對(duì)應(yīng)的ServiceMethod實(shí)例后汛兜,會(huì)使用該ServiceMethod實(shí)例和方法調(diào)用的參數(shù)Object... args生成一個(gè)OkHttpCall。而OkHttpCall實(shí)際上是okhttp3.Call的一個(gè)包裝類通今,實(shí)際調(diào)用OkHttpCall的相關(guān)執(zhí)行方法時(shí)最終是調(diào)用OkHttpCall內(nèi)部用ServiceMethod.callFactory創(chuàng)建的okhttp3.Call來執(zhí)行網(wǎng)絡(luò)請(qǐng)求粥谬。

第三步、調(diào)用serviceMethod.callAdapter.adapt(okHttpCall)來產(chǎn)生method所定義的返回

Retrofit2默認(rèn)支持的返回是返回一個(gè)Call<T>,利用此Call<T>實(shí)例可執(zhí)行

Response<T> result = call.execute();//同步執(zhí)行

//異步執(zhí)行
call.enqueue(new Callback(){
    public void onResponse(Call<T> call, Response<T> response){
        //TODO 
    }
    public void onFailure(Call<T> call, Throwable t){
        //TODO 
    }   
});

其中在Android平臺(tái)Retrofit2會(huì)自動(dòng)使用主線程handler構(gòu)造一個(gè)ExecutorCallAdapterFactory辫塌,調(diào)用enqueue(Callback)漏策,callback回調(diào)會(huì)在主線程中回調(diào)

另外在Retrofit的擴(kuò)展Adapter中還提供了RxJavaCallAdapterFactory,Java8CallAdapterFactory,GuavaCallAdapterFactory

以RxJavaCallAdapterFactory為例,RxJavaCallAdapterFactory創(chuàng)建的callAdapter在執(zhí)行adapt時(shí)將OkHttpCall包裝一個(gè)Rx的Observable臼氨,在Observable被subscribe時(shí)才會(huì)真正的執(zhí)行http請(qǐng)求掺喻。

寫在最后

其實(shí)Retrofit已經(jīng)不是一個(gè)新庫了,目前版本也是第二個(gè)大版本了储矩,本身Retrofit已經(jīng)讓http請(qǐng)求解耦很好了感耙,再加上與Rx的配合,就真的是靈活到不行持隧。我也是從去年下半年才開始關(guān)注和研究Retrofit的實(shí)現(xiàn)即硼,此文章也只是一個(gè)淺析,希望能對(duì)各位開發(fā)同仁有些幫助吧屡拨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末只酥,一起剝皮案震驚了整個(gè)濱河市褥实,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌层皱,老刑警劉巖性锭,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叫胖,居然都是意外死亡草冈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門瓮增,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怎棱,“玉大人,你說我怎么就攤上這事绷跑∪担” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵砸捏,是天一觀的道長谬运。 經(jīng)常有香客問我,道長垦藏,這世上最難降的妖魔是什么梆暖? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮掂骏,結(jié)果婚禮上轰驳,老公的妹妹穿的比我還像新娘。我一直安慰自己弟灼,他們只是感情好级解,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著田绑,像睡著了一般勤哗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掩驱,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天俺陋,我揣著相機(jī)與錄音,去河邊找鬼昙篙。 笑死腊状,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的苔可。 我是一名探鬼主播缴挖,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼焚辅!你這毒婦竟也來了映屋?” 一聲冷哼從身側(cè)響起苟鸯,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棚点,沒想到半個(gè)月后早处,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘫析,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年砌梆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贬循。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咸包,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杖虾,到底是詐尸還是另有隱情烂瘫,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布奇适,位于F島的核電站坟比,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嚷往。R本人自食惡果不足惜温算,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望间影。 院中可真熱鬧,春花似錦茄茁、人聲如沸魂贬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽付燥。三九已至,卻和暖如春愈犹,著一層夾襖步出監(jiān)牢的瞬間键科,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工漩怎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勋颖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓勋锤,卻偏偏與公主長得像饭玲,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叁执,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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