讀retrofit源碼

? 雖然項(xiàng)目中經(jīng)常用到retrofit,但我一直也沒有深入了解過其內(nèi)部實(shí)現(xiàn)霞幅。直到有一次述么,項(xiàng)目中需要取到http url中的path對(duì)應(yīng)的值剃根,結(jié)合其他數(shù)據(jù)先舷,生成接口驗(yàn)簽的簽名艰管,一開始我無從下手,還是跟后端java的同事一起調(diào)試蒋川,看到retrofit使用的是動(dòng)態(tài)代理牲芋,然后通過retrofit 2.5版本新增的Invocation類,最終完成了path數(shù)據(jù)獲取。

? 經(jīng)過這件事,我必須抽時(shí)間學(xué)習(xí)一下retrofit的源碼街图,因?yàn)閞etrofit是支持java和android平臺(tái)浇衬,其使用的動(dòng)態(tài)代理技術(shù),我作為android開發(fā)餐济,也沒有接觸耘擂,這也讓我覺得,拓展技術(shù)面可以讓你了解到很多全新的事物絮姆,也許你的整個(gè)技術(shù)架構(gòu)都會(huì)有新的認(rèn)識(shí)醉冤。

? 下面還是說正事,我是使用android studio打開retrofit的源碼篙悯,版本是2.9.0蚁阳。

? 那么,我們先從Retrofit類開始鸽照,我們使用的時(shí)候螺捐,也是需要先創(chuàng)建Retrofit的實(shí)例,創(chuàng)建方法是通過builder模式進(jìn)行的矮燎,那我們看一下其內(nèi)部Builder類定血。

? Builder類公開構(gòu)造器方法是無參,另外你也可以用Retrofit的newBuilder方法诞外,這會(huì)調(diào)用另一個(gè)內(nèi)部構(gòu)造器澜沟,接收一個(gè)retrofit實(shí)例作為參數(shù);構(gòu)造方法里峡谊,首先關(guān)注platform字段的實(shí)例化茫虽,Platform是對(duì)運(yùn)行平臺(tái)的抽象,判斷運(yùn)行在android還是jvm上既们,并且由于java8引入了interface default method,而retrofit動(dòng)態(tài)代理的目標(biāo)也是interface濒析,因此,Platform中封裝了對(duì)default method的處理贤壁。另外在看builder有參構(gòu)造方法悼枢,或者build方法時(shí),我們可以看到脾拆,其中調(diào)用了platform的defaultConverterFactories和defaultCallAdapterFactories馒索,這兩個(gè)方法中,如果平臺(tái)支持java8名船,那么就會(huì)包含OptionalConverterFactory和CompletableFutureCallAdapterFactory绰上,這兩個(gè)都是根據(jù)java8的新特性,用于支持Optional和CompletableFuture渠驼。同時(shí)蜈块,retrofit內(nèi)置的java8的adapter和converter庫也就過期了。

? 另外我們需要注意,如果是Android平臺(tái)百揭,則會(huì)實(shí)現(xiàn)一個(gè)MainThreadExecutor作為defaultCallbackExecutor爽哎,其實(shí)現(xiàn)了execute方法,使用handler執(zhí)行runnable,實(shí)現(xiàn)切換到主線程的功能器一。而這個(gè)callbackExecutor,會(huì)作為參數(shù)傳入DefaultCallAdapterFactory课锌,而此DefaultCallAdapterFactory會(huì)加入callAdapterFactory的集合中,最后這個(gè)集合會(huì)作為參數(shù)在構(gòu)造retrofit實(shí)例時(shí)使用祈秕,具體怎么用我們后面說渺贤。

? 構(gòu)造完Retrofit類實(shí)例之后,我們重點(diǎn)看一下其create方法请毛,其方法簽名是: <T> T create(final Class<T> service)志鞍,這也就是動(dòng)態(tài)代理的核心部分,根據(jù)定義的interface構(gòu)造API endpoint的implementation,這里我都沿用了create代碼中注釋部分的詞匯方仿,翻譯不對(duì)容易引起誤會(huì)固棚。create方法中,首先執(zhí)行validateServiceInterface方法兼丰,此方法首先檢查傳入的class對(duì)象玻孟,沒有類型參數(shù)唆缴,然后判斷validateEagerly為true的話鳍征,就遍歷Service類中定義的方法,針對(duì)不是default method也不是static method的方法面徽,執(zhí)行l(wèi)oadServiceMethod艳丛。

? validateServiceInterface方法調(diào)用完成后,就調(diào)用Proxy.newProxyInstance創(chuàng)建對(duì)應(yīng)接口的代理類的實(shí)例趟紊,其重點(diǎn)是第三個(gè)入?yún)⑹荌nvocationHandler氮双,代碼中匿名內(nèi)部類實(shí)現(xiàn)了這個(gè)接口,實(shí)現(xiàn)其invoke方法霎匈,這里判斷是default method 則調(diào)用platform的invokeDefaultMethod方法戴差,否則執(zhí)行l(wèi)oadServiceMethod獲取ServiceMethod對(duì)象后調(diào)用其invoke方法。

? 接下來我們重點(diǎn)看一下loadServiceMethod方法铛嘱,在此方法中暖释,我們可以看到Retrofit中定義的serviceMethodCache字段,這是一個(gè)ConcurrentHashMap墨吓,里面存儲(chǔ)了根據(jù)method解析出來的ServiceMethod,而ServiceMethod實(shí)例是通過其靜態(tài)方法parseAnnotations生成的球匕,在這個(gè)方法中,先是通過RequestFactory.parseAnnotations得到RequestFactory的實(shí)例帖烘,調(diào)用這個(gè)方法亮曹,會(huì)遍歷解析method的annotation,得到是否包含body等信息,然后解析參數(shù)注解的數(shù)組,得到ParameterHandler<照卦?>的實(shí)例式矫,這一步是調(diào)用parseParameter方法實(shí)現(xiàn)的,其內(nèi)部建立參數(shù)注解役耕,然后調(diào)用parseParameterAnnotation得到對(duì)應(yīng)的ParameterHandler,ParameterHandler是個(gè)抽象類衷佃,需要實(shí)現(xiàn)抽象方法apply(RequestBuilder builder, @Nullable T value),知道這點(diǎn)蹄葱,我們繼續(xù)回到parseParameterAnnotation氏义,這個(gè)方法非常長,其邏輯就是條件判斷參數(shù)annotation注解是什么:


if(annation instance Url){

    ParameterHandler.RelativeUrl

}else if(annotation instanceof Path){

    ParameterHandler.Path

}else if(annotation instanceof Query){

    ParameterHandler.Query

}else if(annotation instanceof QueryName){

    ParameterHandler.QueryName

}else if(annotation instanceof QueryMap){

    ParameterHandler.QueryMap

}else if(annotation instanceof Header){

    ParameterHandler.Header

}else if(annotation instanceof HeaderMap){

    ParameterHandler.HeaderMap

}else if(annotation instanceof Field){

    ParameterHandler.Field

}else if(annotation instanceof FieldMap){

    ParameterHandler.FieldMap

}else if(annotation instanceof Part){

    if(part.value.isEmpty()){

        //原始數(shù)據(jù)即文件

ParameterHandler.RawPart.INSTANCE

    }else{

        ParameterHandler.Part

    }

}else if(annotation instanceof PartMap){

    ParameterHandler.PartMap

}else if(annotation instanceof Body){

    ParameterHandler.Body

}else if(annotation instanceof Tag){

    ParameterHandler.Tag

}

? 上面的是偽代碼图云,如果參數(shù)是數(shù)組或集合對(duì)象惯悠,則會(huì)返回用上面的返回值生成的ParameterHandler.array或ParameterHandler.iterable。我們可以看到竣况,每個(gè)參數(shù)注解克婶,都有對(duì)應(yīng)的ParameterHandler實(shí)現(xiàn)類,而其apply方法實(shí)現(xiàn)的就是將數(shù)據(jù)填入RequestBuilder類實(shí)例中丹泉。

? 注意Part注解情萤,這里會(huì)判斷value是否是空的,如果是空的摹恨,則注解的類必須是MultipartBody.Part筋岛,或者List<MultipartBody.Part>,MultipartBody.Part數(shù)組晒哄,以支持上次單個(gè)或多個(gè)文件;

? 另外睁宰,對(duì)于ParameterHandler.Part,ParameterHandler.PartMap和ParameterHandler.Body的構(gòu)造方法寝凌,需要調(diào)用requestBodyConverter傳入Converter<?, RequestBody>,而其他ParameterHandler實(shí)現(xiàn)類柒傻,如果需要converter,則都是調(diào)用stringConverter得到Converter<?, String>,這兩個(gè)方法较木,都是在Converter接口中需要實(shí)現(xiàn)的红符,不同的是,即使不實(shí)現(xiàn)Converter<?, String>伐债,retrofit也會(huì)調(diào)用(Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE得到默認(rèn)的Converter<?,String>,其實(shí)現(xiàn)就是簡單的調(diào)用入?yún)alue的toString方法预侯,這里我們就可以知道Converter的requestBodyConverter是怎么被使用的。

? 另外實(shí)際代碼中泳赋,有很多判斷邏輯雌桑,檢查數(shù)據(jù)正確性,比如Field注解中先會(huì)判斷isFormEncoded字段是否為true,而這個(gè)內(nèi)部字段祖今,在之前解析方法注解的FormUrlEncoded注解時(shí)會(huì)設(shè)置為true校坑。

? 然后將retrofit ,method,requestBody作為參數(shù)拣技,調(diào)用HttpServiceMethod.parseAnnotations,得到HttpServiceMethod實(shí)例耍目,這個(gè)也就是要保存的ServiceMethod的具體實(shí)現(xiàn)類膏斤,通過注釋我們知道HttpServiceMethod.parseAnnotations方法中使用到了反射,為了不每次都執(zhí)行此方法邪驮,就需要通過serviceMethodCache緩存莫辨,以提升性能。

? 下面我們看一下HttpServiceMethod.parseAnnotations毅访。方法中首先判斷isKotlinSuspendFunction是否為true,此屬性來自requestionFactory,我們?cè)诮馕龇椒ǖ膮?shù)時(shí)沮榜,如果是協(xié)程方法,雖然在kotlin代碼中看不到喻粹,但從實(shí)際java字節(jié)碼中可以看到蟆融,方法最后一個(gè)參數(shù)的類型是Continuation,由此便能判斷是否是協(xié)程方法守呜,如果是協(xié)程方法并且返回Response<T>型酥,那代碼中會(huì)解析出實(shí)際的responseType,使用其構(gòu)造adapterType;而如果不是協(xié)程方法查乒,則調(diào)用method.getGenericReturnType()得到adpaterType弥喉。這個(gè)adapterType實(shí)際就是返回參數(shù)類型,通過此參數(shù),我們可以構(gòu)造CallAdapter玛迄,并繼續(xù)構(gòu)造responseConverter由境,然后將這兩個(gè)參數(shù),以及requestFactory,callFactory(從retrofit實(shí)例中得到)憔晒,如果不是協(xié)程方法藻肄,返回CallAdapted,如果是并且方法返回參數(shù)是Response拒担,則返回SuspendForResponse,否則返回SuspendForBody攻询,這里的三個(gè)類从撼,都是HttpServiceMethod的子類,HttpServiceMethod實(shí)現(xiàn)的invoke方法钧栖,就是使用入?yún)ⅲ–allAdapter低零,responseConverter,requestFactory,callFactory)拯杠,構(gòu)造OkHttpCall實(shí)例掏婶,然后調(diào)用adapt抽象方法,而我們剛看到的三個(gè)子類潭陪,就是實(shí)現(xiàn)adapt方法雄妥。CallAdapted類中最蕾,adapt方法實(shí)現(xiàn)很簡單,直接callAdapter.adapt(call)得到我們需要的api方法的返回值老厌,而其他兩個(gè)方法瘟则,也是先調(diào)用callAdapter.adapt(call),然后使用協(xié)程枝秤,這塊牽涉到kotlin協(xié)程知識(shí)醋拧,筆者對(duì)協(xié)程了解甚少,有興趣的讀者可以自己去了解淀弹。同時(shí)我們也了解到CallAdapter是如何是被使用的丹壕。

? 在構(gòu)造CallAdapter時(shí),我們提到了DefaultCallAdapterFactory薇溃,這里就會(huì)執(zhí)行DefaultCallAdapterFactory的get方法雀费,構(gòu)造一個(gè)CallAdapter的匿名內(nèi)部類實(shí)現(xiàn),最終使得在調(diào)用Call.enqueue方法時(shí)痊焊,會(huì)調(diào)用callbackExecutor的execute,在其內(nèi)部runnable中調(diào)用callback.onResponse或者callback.onFailure兵怯,這樣enqueue方法在android端,callback的回調(diào)方法就會(huì)在主線程中執(zhí)行昆箕。

? 我們查看一下rxjava2的CallAdapter似扔,最后發(fā)現(xiàn)會(huì)執(zhí)行call.execute得到實(shí)際的response,那我們看一下就看一下retrofit中Call的實(shí)現(xiàn)類OkHttpCall,既然提到了execute方法垄惧,那我們就查看一下刁愿。

? execute方法先是調(diào)用getRawCall,得到okhttp3.Call到逊,之前我們說的Call類铣口,都是retrofit源碼中的Call,這兩者都是接口,并且內(nèi)部的方法簽名也基本一致觉壶,唯一的區(qū)別是retrofit的Call類聲明中包含泛型參數(shù)脑题,同時(shí)Response<T> execute()和enqueue(Callback<T> callback)也都是泛型方法。

? 說完Call铜靶,我們接著看getRawCall方法叔遂,其先檢查OkhttpCall緩存的rawCall是否存在,存在則直接返回此實(shí)例争剿,否則調(diào)用createRawCall方法已艰,此方法通過requestFactory.create得到request實(shí)例,而構(gòu)造此實(shí)例的方法是先使用之前解析并存儲(chǔ)在requestFactory中有關(guān)method的各種屬性構(gòu)造RequestBuilder,然后調(diào)用ParameterHandler的apply蚕苇,將之前解析參數(shù)注解時(shí)得到的屬性設(shè)置到requestBuilder中哩掺,這里我們就可以看到ParameterHandler是怎么被使用的,以及你設(shè)置的注解是怎么生效的涩笤,完成后調(diào)用requestBuilder的build方法嚼吞,得到request,然后調(diào)用callFactory.newCall盒件,傳入request,就得到了Okhttp3.Call,至于這個(gè)callFactory,那就是構(gòu)建retrofit實(shí)例時(shí)設(shè)置的。

? 得到Okhttp3.Call實(shí)例誊薄,調(diào)用其execute方法后,得到response后呢蔫,會(huì)調(diào)用parseResponse方法解析Okhttp.Response的retrofit源碼中的Response<T>(這兩者的區(qū)別切心,與之前提到的Call是類似的)。parseResponse方法中先取出body,然后根據(jù)http code進(jìn)行不同的處理:


if(code < 200 || code >= 300){

返回Response.error

}

if(code == 204 || code == 205){

    success,返回不帶body的response,

}

調(diào)用responseConverter.convert得到ResponseBody轉(zhuǎn)換類型的實(shí)例片吊,然后返回帶這個(gè)body的response

? 上面我們就可以看到responseBodyConverter是怎么被使用的绽昏。結(jié)合之前關(guān)于requestBodyConverter以及CallAdapter的介紹,我們可以了解到我們提供的CallAdapter是如何把默認(rèn)返回的Call轉(zhuǎn)換成我們自定義的類型俏脊,以及Converter是如何轉(zhuǎn)換RequestBody和ResponseBody為我們自定義的類型全谤。

? 之前我提到通過Invocation類獲取注解path數(shù)據(jù),那現(xiàn)在我們來看一下retrofit是什么時(shí)候添加Invocation的爷贫。我們回到RequestFactory的create方法創(chuàng)建request時(shí)认然,查看方法最后一行:


    return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();

我們可以看到,在構(gòu)造request時(shí)漫萄,通過tag方法添加了Invocation的實(shí)例卷员,并且傳入了method對(duì)象,以及參數(shù)列表腾务,這樣我們就可以在Interceptor中獲取需要的數(shù)據(jù)毕骡。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市岩瘦,隨后出現(xiàn)的幾起案子未巫,更是在濱河造成了極大的恐慌,老刑警劉巖启昧,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叙凡,死亡現(xiàn)場離奇詭異,居然都是意外死亡箫津,警方通過查閱死者的電腦和手機(jī)狭姨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苏遥,“玉大人,你說我怎么就攤上這事赡模√锾浚” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵漓柑,是天一觀的道長教硫。 經(jīng)常有香客問我叨吮,道長,這世上最難降的妖魔是什么瞬矩? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任茶鉴,我火速辦了婚禮,結(jié)果婚禮上景用,老公的妹妹穿的比我還像新娘涵叮。我一直安慰自己,他們只是感情好伞插,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布割粮。 她就那樣靜靜地躺著,像睡著了一般媚污。 火紅的嫁衣襯著肌膚如雪舀瓢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天耗美,我揣著相機(jī)與錄音京髓,去河邊找鬼。 笑死商架,一個(gè)胖子當(dāng)著我的面吹牛堰怨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甸私,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诚些,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了皇型?” 一聲冷哼從身側(cè)響起诬烹,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弃鸦,沒想到半個(gè)月后绞吁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唬格,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年家破,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片购岗。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汰聋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喊积,到底是詐尸還是另有隱情烹困,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布乾吻,位于F島的核電站髓梅,受9級(jí)特大地震影響拟蜻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枯饿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一酝锅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奢方,春花似錦搔扁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至愉老,卻和暖如春场绿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫉入。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工焰盗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咒林。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓熬拒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垫竞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子澎粟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354