Retrofit2源碼分析

準(zhǔn)備知識(shí)

我眼中的Java-Type體系(2)對(duì)Method解析的時(shí)候要用到
在分析源碼前有哪些疑問呢?
CallAdapter是什么加勤?
Converter是什么?
private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
保存Converter為什么是一個(gè)List?
private List<Converter.Factory> converterFactories = new ArrayList<>();
保存CallAdapter為什么也是一個(gè)List家制?
無論你是不是對(duì)上面有疑問秕重,在看完之后相信你都會(huì)有答案了不同。

總結(jié)

  1. Retrofit是OKHttp輔助工具
  2. Retrofit本身是一個(gè)注解解析器
  3. Retrofit可以和RxJava更容易配合使用
  4. ServiceMethod 的主要作用是解析構(gòu)建Request 和Response的解析
  5. OkHttpCall 主要作用是實(shí)現(xiàn)OKhttp3的調(diào)用
  6. CallAdapter 作用Retrofit.Call命令器的轉(zhuǎn)換成自定義的命令器
  7. Converter<?, RequestBody> requestBodyConverter是實(shí)現(xiàn)Request自定義的類轉(zhuǎn)換成RequestBody
  8. Converter<ResponseBody, ?> responseBodyConverter是將ResponseBody轉(zhuǎn)換成我們自定義的類

在對(duì)源碼進(jìn)行分析之前先看一下Retrofit,類是很少的溶耘。



Retrofit使用過程

我們分析Retrofit2二拐,帶著設(shè)計(jì)者實(shí)現(xiàn)功能的想法去分析。下面是對(duì)1到4點(diǎn)的說明

  1. 構(gòu)建OKHttpClient 幫我們實(shí)現(xiàn)Http請(qǐng)求
  2. 構(gòu)建Retrofit凳兵,保存相關(guān)的參數(shù)(參數(shù)作用后面會(huì)提及)
  3. 實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求
  4. 構(gòu)建Http請(qǐng)求的傳遞參數(shù)和返回參數(shù)

在分析Retrofit前我們看一下Okhttp請(qǐng)求


大概的步驟是
1創(chuàng)建一個(gè)OKHTTPClient(配置連接時(shí)間百新,讀寫時(shí)間,超時(shí)等參數(shù))
2創(chuàng)建一個(gè)Request(配置url留荔,head吟孙,get,post方法聚蝶,body等)
3發(fā)送請(qǐng)求獲得response
4 對(duì)response進(jìn)行解析
這也是一個(gè)HTTP請(qǐng)求的過程杰妓。
正式開始我們分析的旅程
Retrofit提供一些輔助類讓我們更加方便的

我們現(xiàn)在有一個(gè)像www.ss.com/xiMing/tt.php發(fā)送一個(gè)Student ,返回一個(gè)Person的需求。(假設(shè)我們用Json格式數(shù)據(jù)傳輸)如果我們用OKHTTP的方式碘勉,我們要把Student轉(zhuǎn)換成json格式巷挥,在服務(wù)器返回的時(shí)候又要把json格式轉(zhuǎn)換成Person。如果我們有多次這樣的請(qǐng)求验靡,我們每個(gè)都要這樣做倍宾。Retrofit把這個(gè)過程給我們處理了雏节。而之前Retrofit做的,我們不需要關(guān)注的高职,就是我們今天要學(xué)習(xí)的钩乍。
對(duì) @POST("{user}/tt.php")Call<Person> postStudent(@Body Student student,@Path("user") String user);解析最核心的類是ServiceMethod類。

create方法是創(chuàng)建RequestService的實(shí)現(xiàn)怔锌。創(chuàng)建一個(gè)interface的實(shí)現(xiàn)寥粹,要么用生成工具,要么用反射埃元。作者使用了后者涝涤。Proxy.newProxyInstance反射實(shí)現(xiàn)了RequestService。真正對(duì)
Call<Person> call = requestService.postStudent(new Student(),"xiMing");進(jìn)行實(shí)現(xiàn)的是

     ServiceMethod serviceMethod = loadServiceMethod(method);
     OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
    return serviceMethod.callAdapter.adapt(okHttpCall);

ServiceMethod 實(shí)現(xiàn)的是對(duì)@POST("{user}/tt.php")Call<Person> postStudent(@Body Student student,@Path("user") String user)的解析岛杀。也可以說Retrofit最核心的功能是如何解析我們自定義的方法
重要的事情說三遍
Retrofit最核心的功能是如何解析我們自定義的方法
Retrofit最核心的功能是如何解析我們自定義的方法
Retrofit最核心的功能是如何解析我們自定義的方法

ServiceMethod 對(duì)自定義的方法的解析

public ServiceMethod build() {
  callAdapter = createCallAdapter();
  responseType = callAdapter.responseType();
  responseConverter = createResponseConverter();
  for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation);
  }
  int parameterCount = parameterAnnotationsArray.length;
  parameterHandlers = new ParameterHandler<?>[parameterCount];
  for (int p = 0; p < parameterCount; p++) {
    Type parameterType = parameterTypes[p];
    Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }
  return new ServiceMethod<>(this);
}

0. Factory 和Converter以及CallAdapter

private List<Converter.Factory> converterFactories = new ArrayList<>();
private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
無論是Converter.Factory還是CallAdapter.Factory都是提供一個(gè)規(guī)則阔拳,一個(gè)生產(chǎn)Converter或是CallAdapter的規(guī)則。
比如CallAdapter.Factory的規(guī)則是 public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,Retrofit retrofit);根據(jù)returnType的類型去創(chuàng)建不同的Adapter例如ExecutorCallAdapterFactory中就看是不是Call.class类嗤,用戶也可以根據(jù)自己的需求去定義get糊肠,來實(shí)現(xiàn)CallAdapter的自定義。
Converter或是CallAdapter提供一個(gè)處理數(shù)據(jù)的規(guī)則遗锣。
類似我們的每條HTTP就是企業(yè)不同的工作任務(wù) CallAdapter.Factory相當(dāng)于HR罪针,CallAdapter相當(dāng)于員工。不同的http任務(wù)有不同type的技能黄伊,HR根據(jù)不同type找能處理這種type任務(wù)的員工泪酱。企業(yè)通過work指令讓具體的CallAdapter(員工)根據(jù)自己的技能處理任務(wù)。adapterFactories 是表示有一群HR还最,有些HR能找到會(huì)計(jì)算機(jī)的墓阀,有些HR能找到廚師,所以在企業(yè)發(fā)布任務(wù)的時(shí)候回問每個(gè)HR拓轻,如果HR(小明)能找到就把CallAdapter生成斯撮,如果不能就返回null(告訴不能)企業(yè)在找下一個(gè)HR(小芳)

1.CallAdapter加載和構(gòu)建

CallAdapter是通過CallAdapter.Factory創(chuàng)建的。
我們分析callAdapter = createCallAdapter();獲得的是ExecutorCallAdapterFactory創(chuàng)建的CallAdapter
1扶叉、CallAdapter的作用
先說XXXCallAdapterFactory的作用勿锅,個(gè)人感覺提供一個(gè)拓展的方法。我們OKhttp提供的是ExecutorCallAdapterFactory(默認(rèn)的)

Call<Person> postStudent(@Body Student student);

對(duì)于RxJava提供的是

 Observable<Person> postStudent(@Body Student student);

同樣我們通過addCallAdapterFactory(RxJavaCallAdapterFactory.create())
所以這個(gè)addCallAdapter將控制器(Call<XXX>或是Observable<XXX>)變的可以靈活定義了枣氧。同時(shí)溢十,我們XXCallAdapterFactory提供了一個(gè)Get方法下面是ExecutorCallAdapterFactory的get方法的實(shí)現(xiàn)。ExecutorCallAdapterFactory只處理Call的达吞,其他類型不處理张弛。
也就是說我們可以同時(shí)提供多個(gè)CallAdapter每個(gè)都處理自己類型的returnType,這就是為什么我們adapterFactories 是用一個(gè)List進(jìn)行保存。


ExecutorCallAdapterFactory.java

2吞鸭、ExecutorCallAdapterFactory是如何獲得的寺董。


private final List<CallAdapter.Factory> adapterFactories;

adapterFactories在Retrofit build的時(shí)候已經(jīng)默認(rèn)添加了。


對(duì)于converterFactories過程類似就不在說明了刻剥。
對(duì)Converter.Factory中的兩個(gè)方法說明
1.requestBodyConverter 是在Post中@Body時(shí)被調(diào)用的遮咖。位置在parseParameter() parseParameter是被ServiceMethod.Builder.build()過程中調(diào)用的。
2.responseBodyConverter是在ServiceMethod.Builder.build()中造虏。

//對(duì)ResponseBody的轉(zhuǎn)換規(guī)則
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
  return null;
}
  //在post方法中我們要傳RequestBody的轉(zhuǎn)換規(guī)則
 public Converter<?, RequestBody> requestBodyConverter(Type type,
    Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
  return null;
}

第二緯度看問題

每個(gè)部分注解都是哪些方法解析的

 @POST("{user}/tt.php")
 Call<Person> postStudent(@Body Student student, @Path("user") String user);
@POST(請(qǐng)求方法)的解析過程

關(guān)于字段的使用可以參考Retrofit網(wǎng)絡(luò)請(qǐng)求參數(shù)注解
在構(gòu)建ServiceMethod.build()的時(shí)候被解析出來盯滚。
注意:
1.這是{user}在這里只被保存起來,不會(huì)被替換
2.在http://xxx/api/News?newsId=1其中newsId=1也沒有被拼接起來酗电。
也就是說請(qǐng)求的method是已經(jīng)確定了。但是完整的url并沒有被組裝起來

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

private void parseMethodAnnotation(Annotation annotation) {
  if (annotation instanceof DELETE) {
    parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
  } else if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
  } else if (annotation instanceof HEAD) {
    parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
    if (!Void.class.equals(responseType)) {
      throw methodError("HEAD method must use Void as response type.");
    }
  } else if (annotation instanceof PATCH) {
    parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
  } else if (annotation instanceof POST) {
    parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
  } else if (annotation instanceof PUT) {
    parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
  } else if (annotation instanceof OPTIONS) {
    parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
  } else if (annotation instanceof HTTP) {
    HTTP http = (HTTP) annotation;
    parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
  } else if (annotation instanceof retrofit2.http.Headers) {
    String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
    if (headersToParse.length == 0) {
      throw methodError("@Headers annotation is empty.");
    }
    headers = parseHeaders(headersToParse);
  } else if (annotation instanceof Multipart) {
    if (isFormEncoded) {
      throw methodError("Only one encoding annotation is allowed.");
    }
    isMultipart = true;
  } else if (annotation instanceof FormUrlEncoded) {
    if (isMultipart) {
      throw methodError("Only one encoding annotation is allowed.");
    }
    isFormEncoded = true;
  }
}
完整URL的組裝過程

這個(gè)過程比較復(fù)雜
主要是在ServiceMethod build()過程調(diào)用了parseParameterAnnotation
這里對(duì)ParameterHandler做一個(gè)解釋:提供一個(gè)abstract void apply(RequestBuilder builder, T value)内列,意思是apply的作用是將T中的數(shù)據(jù)按照需求構(gòu)建我們的RequestBuilder 撵术,就以@Url為例生成的是ParameterHandler實(shí)例是ParameterHandler.RelativeUrl 其中apply的實(shí)現(xiàn)是

 @Override void apply(RequestBuilder builder, Object value) {
  builder.setRelativeUrl(value);//將值直接設(shè)置為RelativeUrl。RelativeUrl就是相對(duì)路徑话瞧,例如“api/getimage/xxxpng”
}

關(guān)于@Url的使用請(qǐng)參考Retrofit2-如何在請(qǐng)求時(shí)使用動(dòng)態(tài)URL
關(guān)于其他類型(@Path等)的就不一一舉例了嫩与。
ParameterHandler的另一個(gè)解釋是保存了類型的解析方法。但是不進(jìn)行解析交排。
這些類型包括(@Url划滋,@Path,@Query埃篓,@QueryMap处坪,@Header,@HeaderMap架专,@Field同窘,@FieldMap,@Part)
解析是在OkHttpCall 中 createRawCall()過程中Request request = serviceMethod.toRequest(args);調(diào)用了 handlers[p].apply(requestBuilder, args[p]);

/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
    contentType, hasBody, isFormEncoded, isMultipart);

@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

int argumentCount = args != null ? args.length : 0;
if (argumentCount != handlers.length) {
  throw new IllegalArgumentException("Argument count (" + argumentCount
      + ") doesn't match expected count (" + handlers.length + ")");
}

for (int p = 0; p < argumentCount; p++) {
  handlers[p].apply(requestBuilder, args[p]);
}

return requestBuilder.build();

}
同時(shí)經(jīng)過這兩步OKhttp需要發(fā)送請(qǐng)求設(shè)置參數(shù)的過程就全部完成了部脚。接下來就是解析Response了
在說解析Response前想邦,我個(gè)人的一個(gè)感覺∥酰框架做了許多的容器丧没。用來保存需要處理的東西。然后在不同的步驟進(jìn)行處理锡移。比如toRequest的步驟其實(shí)在ServiceMethod build()是可以完全處理完成的呕童。作者這樣寫可以讓處理過程推遲到必要的時(shí)候?qū)θ绻艞壢蝿?wù)就減少工作量。

總結(jié)

對(duì)于@POST("{user}/tt.php")Call<Person> postStudent(@Body Student student,@Path("user") String user)的解析大體是兩個(gè)部分
1 ServiceMethod Builder.build()
2 OkHttpCall中createRawCall()調(diào)用serviceMethod.toRequest(args);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末淆珊,一起剝皮案震驚了整個(gè)濱河市拉庵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖钞支,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茫蛹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烁挟,警方通過查閱死者的電腦和手機(jī)婴洼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撼嗓,“玉大人柬采,你說我怎么就攤上這事∏揖” “怎么了粉捻?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)斑芜。 經(jīng)常有香客問我肩刃,道長(zhǎng),這世上最難降的妖魔是什么杏头? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任盈包,我火速辦了婚禮,結(jié)果婚禮上醇王,老公的妹妹穿的比我還像新娘呢燥。我一直安慰自己,他們只是感情好寓娩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布叛氨。 她就那樣靜靜地躺著,像睡著了一般棘伴。 火紅的嫁衣襯著肌膚如雪力试。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天排嫌,我揣著相機(jī)與錄音畸裳,去河邊找鬼。 笑死淳地,一個(gè)胖子當(dāng)著我的面吹牛怖糊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颇象,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼伍伤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了遣钳?” 一聲冷哼從身側(cè)響起扰魂,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后劝评,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姐直,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年蒋畜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了声畏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姻成,死狀恐怖插龄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情科展,我是刑警寧澤均牢,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站才睹,受9級(jí)特大地震影響徘跪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜砂竖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹃答。 院中可真熱鬧乎澄,春花似錦、人聲如沸测摔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锋八。三九已至浙于,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挟纱,已是汗流浹背羞酗。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留紊服,地道東北人檀轨。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像欺嗤,于是被迫代替她去往敵國(guó)和親参萄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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

  • 前言 很早之前就開始接觸Retrofit,自己也寫了一個(gè)項(xiàng)目煎饼,但是一直沒有深入研究過源碼讹挎。 正文 版本 項(xiàng)目結(jié)構(gòu) ...
    javalong閱讀 3,909評(píng)論 6 12
  • 本文將順著構(gòu)建請(qǐng)求對(duì)象->構(gòu)建請(qǐng)求接口->發(fā)起同步/異步請(qǐng)求的流程,分析Retrofit是如何實(shí)現(xiàn)的。 開始之前筒溃,...
    zhuhf閱讀 1,614評(píng)論 0 10
  • 將一個(gè)Java接口翻譯成一個(gè)Http請(qǐng)求马篮,然后用OkHttp去發(fā)送這個(gè)請(qǐng)求 一 入口類 Retrofit 成員變...
    菜鳥湯先生閱讀 495評(píng)論 0 0
  • Retrofit是Square公司出品的一款產(chǎn)品,主要完成封裝符合RESTful風(fēng)格 的 HTTP的網(wǎng)絡(luò)框架铡羡。Re...
    嘎啦果安卓獸閱讀 803評(píng)論 0 0
  • 簡(jiǎn)介 剛接觸Retrofit的時(shí)候积蔚,就寫了一篇簡(jiǎn)單的使用介紹:Retrofit 2.0基本使用方法,算是對(duì)Retr...
    Whyn閱讀 2,844評(píng)論 4 24