開始慢慢地把各種筆記從硬盤里搬運出來了锣杂,第一篇就先好好回顧下平日低頭不見抬頭見的Retrofit同志碴萧。
Retrofit同OkHttp一樣都是square公司(然后查了一下發(fā)現(xiàn)Picasso和Dragger也是他們的項目,向大佬低頭)的開源網(wǎng)絡(luò)框架,大概因為這個原因,它們也有相似的Builder鏈式調(diào)用和相似的請求流程。當(dāng)然Retrofit實際可以算作是OkHttp的加強版啰脚,其不僅有各種方便的注解簡化代碼,更有其他開源庫的支持(看看漫山遍野的Retrofit+RxJava使用教程)实夹,下面簡單地記錄一下當(dāng)時學(xué)習(xí)過的一些點橄浓。
添加依賴
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.okhttp3:okhttp:3.7.0'
//compile 'com.squareup.retrofit2:converter-gson:2.2.0'
//compile 'com.squareup.retrofit2:adapter-rxjava:2.2.0'
//compile 'io.reactivex:rxjava:1.1.7' //這里如果也用rxjava 2.0以上版本會和上面的adapter-rxjaava出現(xiàn)不同版本庫沖突問題。
//compile 'io.reactivex:rxandroid:1.2.1'
基本用法
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://lazxy.github.io/") //注意這里一定要以“/”結(jié)尾
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//和RxJava一起用
.addConverterFactory(GsonConverterFactory.create())//和Gson一起用
.build();
實際上從這個Builder鏈就能大致看出Retrofit和OkHttp的關(guān)系了收擦,Rerofit需要通過OkHttpClient來創(chuàng)建對象贮配,其實也就是等于在它之上做了一層封裝,除了OkHttp本身的各種屬性之外塞赂,又給附上了適配器和對象轉(zhuǎn)換泪勒。然而光有Retrofit對象并沒有什么用,通道是搭好了宴猾,但請求對象呢圆存?在OkHttp里,需要另外創(chuàng)建一個Request對象仇哆,在里面注入Url沦辙、請求頭和各種Http請求方法,然而在Retrofit里讹剔,這一步直接換成了高大上的注解實現(xiàn):
interface DemoService {
@GET("demo/{id}")
Call<ResponseBody> test(@Path("id") int id);
}
...
Call<ResponseBody> call = retrofit.create(DemoService.class).test(1);
call.enqueue(new Callback(){...})
這里直接通過向retrofit的create(Class class)方法傳入接口類字節(jié)碼實現(xiàn)反射調(diào)用創(chuàng)建了對應(yīng)的接口對象油讯,再通過該對象獲取到了Call對象,也就是請求的載體延欠,然后和OkHttp一樣陌兑,用一個enqueue方法把請求加入隊列,就完成了請求的調(diào)用過程由捎。其中請求的Http請求類型通過注解@GET注入兔综,類似的還有下列注解:
請求類型 | 說明 |
---|---|
GET | 通過請求獲得響應(yīng),最多攜帶2048字節(jié)信息 |
POST | 發(fā)送數(shù)據(jù)給服務(wù)器端,數(shù)據(jù)長度無限制软驰,但非冪等涧窒,反復(fù)請求會造成數(shù)據(jù)的反復(fù)創(chuàng)建。 |
PUT | 更新或創(chuàng)建服務(wù)器端的數(shù)據(jù)锭亏,與POST的區(qū)別在于PUT的地址是精確到單個資源的纠吴,而POST只能作用于資源集合層面。(即前者能夠針對單一文件進行操作慧瘤,但后者只能在目錄下進行創(chuàng)建操作) |
DELETE | 刪除服務(wù)器端數(shù)據(jù)呜象。 |
HEAD | 同GET相同,但響應(yīng)體為空碑隆,多用于測試連接性和信息更新。 |
OPTIONS | 獲取服務(wù)器端支持的請求方法蹬音,或者用于檢查服務(wù)器的性能 |
PATCH | 更新服務(wù)器端數(shù)據(jù)的局部上煤,可以是一個或多個字段,與PUT不同著淆,PATCH操作是非冪等的劫狠,因為局部更新需要服務(wù)器程序配合,可能會造成資源改變永部。 |
HTTP | 通用型注解独泞,可以用來表示上面七種請求類型。 |
上面幾種請求就是基本的幾種Http請求苔埋,注解內(nèi)的值表示retrofit創(chuàng)建時賦予的主路徑(baseUrl)下的子路徑(當(dāng)注解值僅為路徑名時實際路徑為baseUrl+注解值懦砂;而當(dāng)注解值以“/”開頭時實際路徑為baseUrl實際路徑為baseUrl的域名+注解值,忽略baseUrl中已有的路徑)组橄,但當(dāng)注解值為全路徑且與baseUrl指向的域不同時荞膘,以注解值為準。中間動態(tài)參數(shù)用{param}
標注玉工,在注解方法的參數(shù)列表中需要出現(xiàn)一一對應(yīng)的@Path("param")注解來注入具體值羽资。另附@HTTP注解的例子:
@HTTP(method = "get", path = "demo/{id}", hasBody = false) (替代上面的get)
另外還有以下幾個相關(guān)注解:
注解名 | 說明 |
---|---|
FormUrlEncoded | 表示請求體是一個Form表單,實際作用方式為設(shè)置請求頭的Content-Type:application/x-www-form-urlencoded |
Multipart | 表示請求體支持分段傳輸遵班,常用于文件上傳屠升,實際為設(shè)置請求頭Content-Type:multipart/form-data |
Streaming | 表示響應(yīng)體數(shù)據(jù)以流的形式返回,而不進入內(nèi)存狭郑,在返回大容量數(shù)據(jù)時會用到 |
Headers | 用于添加請求頭腹暖,其注解參數(shù)即為要添加的請求頭鍵值對,可一次添加多個頭 |
好了愿阐,上面提到的注解都是用于注解函數(shù)的微服,而對于函數(shù)中的各個參數(shù),則采用下列注解:
注解名 | 說明 |
---|---|
Header | 用于注解需要添加的頭,注解參數(shù)為鍵以蕴,被注解的函數(shù)參數(shù)為值糙麦。 |
Body | 用于非表單請求體,即自定義對象丛肮,在發(fā)起請求的過程中會被相應(yīng)的Convert序列化為預(yù)設(shè)的格式(如Json)赡磅,并且其結(jié)果直接被設(shè)置為Request的請求體。 |
Field | 用于注解單個表單字段宝与,使用時需要有FormUrlEncoded注解或者相應(yīng)頭表示其請求體為表單格式焚廊。 |
FieldMap | 用于注解多個表單字段的集合,只接受Map<String,String>類型习劫,其他類型會被強制調(diào)用toString()方法咆瘟,使用時需要有FormUrlEncoded注解或者相應(yīng)頭表示其請求體為表單格式。 |
Part | 用于注解單個分段表單字段诽里,支持類型為RequestBody袒餐、MultiPartBody.Part(內(nèi)部表示)與其他任意類型(也是通過Convert轉(zhuǎn)換),使用時需要有Multipart注解或者相應(yīng)頭表示其請求體為分段表單格式。 |
PartMap | 用于注解多個分段表單字段谤狡,只接受Map<String,RequestBody>類型灸眼,非RequestBody類型會通過Converter轉(zhuǎn)換,使用時需要有Multipart注解或者相應(yīng)頭表示其請求體為分段表單格式墓懂。 |
Path | 用于注解Url路徑相關(guān)參數(shù)焰宣,其參數(shù)與上述Http請求方法中的{param} 中參數(shù)一一對應(yīng)。 |
Query | 注解Url的附帶參數(shù)值捕仔,其注解參數(shù)為鍵匕积,被注解函數(shù)為值,注解結(jié)果會顯示在最終請求的的路徑上逻澳,形如?name=Lazxy闸天。其實這個結(jié)果與Field最終表現(xiàn)出來的結(jié)果是相同的,但區(qū)別在于后者作用對象是請求體而非Url斜做。 |
QueryMap | 同F(xiàn)ieldMap,區(qū)別也同樣在于作用點為URL |
Url | 用于注解Url路徑苞氮,當(dāng)該注解存在時,Http方法會直接以該注解值為Url |
Path瓤逼、Query笼吟、Field幾類注解沒什么好講的,其內(nèi)部處理其實就是簡單的在請求體或者直接在URL中加入key=value形式的值霸旗,這里著重講一下Part贷帮。
上面說到了Part用于表示分段表單字段,然而這個分段是怎么實現(xiàn)的呢诱告?其實際請求內(nèi)容如下:
...
Content-Type: multipart/form-data; boundary=${bound} //請求頭里的值撵枢,@Multipart注解的功勞,這里的boundary用于下面分段的分割標識
//一個空行,表示請求頭結(jié)束了锄禽,下面是請求體內(nèi)容
--${bound}
Content-Disposition: form-data; name="file_name" //規(guī)定格式及請求字段
Content-Type: text/plain; charset=UTF-8 //該段請求體的類型以及其編碼格式
Content-Transfer-Encoding: 8bit //解碼轉(zhuǎn)化方式
//一個空行潜必,表示前面的信息段結(jié)束,下面一行是文件內(nèi)容
avatar.jpg //Filename的內(nèi)容
--${bound}
Content-Disposition: form-data; name="avatar"; filename="avatar.png"http://name為請求字段沃但,filename為建議服務(wù)器端保存的文件名
Content-Type: image/png
而Part注解的注解參數(shù)正是Content-Disposition:form-data;
后面的部分磁滚,例如當(dāng)需要傳輸一個文件時,它的參數(shù)可以這樣寫
Call<ResponseBody> uploadFile(@Part("file\";filename=\"file.txt"\"") RequestBody file);
//這里的三個轉(zhuǎn)義字符是為了將其原本的name="${param}"格式強行拓展為name="${param1}";filename="${param2}"
而RequestBody的構(gòu)造則寫在調(diào)用這個請求的地方:
RequestBody fileRequest = RequestBody.create(MediaType.parse("text/plain"),new File("0/storage/file.txt"));//這里規(guī)定了文件類型宵晚,也就是上面的Type-Content
retrofit.create(Demo.class).uploadFile(fileRequest);
而當(dāng)需要上傳多個文件或者附加字段時垂攘,用PartMap就行,其中key為name淤刃,value為包裝著文件的RequestBody或者基本類型(包括String晒他,畢竟Converter默認都有String轉(zhuǎn)化的方法)。
PS:Path推薦只用在與路徑有關(guān)時逸贾,包括數(shù)據(jù)的部分最好用Query解決仪芒,而數(shù)據(jù)量較大或者需要不可見時建議用Filed提交。另外Query耕陷、Field和Part都支持數(shù)組或者實現(xiàn)了Iterable等可迭代對象導(dǎo)入。
關(guān)于CallAdapter和Converter
? 在最基本的使用中据沈,我們定義的接口及其內(nèi)被注解的方法返回值類型為Call<ResponeBody>哟沫,而在正常的使用中,我們更希望能夠返回一個我們自定義的對象類型從而更容易地將其打包或解析為請求體锌介,這時就需要用到Retrofit的Converter機制了嗜诀。Retrofit能夠通過addConverterFactory方法來添加相應(yīng)的Converter工廠類,并在需要解析時(包括對@Body注解對象轉(zhuǎn)換為ResponseBody和ResponseBody轉(zhuǎn)換為相應(yīng)對象)創(chuàng)建相應(yīng)的實現(xiàn)了Converter<F,T>接口的類孔祸,來進行對象類型轉(zhuǎn)換隆敢,從而得到Call<Object>(這樣便沒有辦法獲得響應(yīng)頭或者狀態(tài)碼)、Call<Result<Object>>(這里是retrofit2.adapter.rxjava.Result)或者Call<Response<Object>>(這里是retrofit2.response)對象崔慧。
? Converter<F,T>實現(xiàn)類的結(jié)構(gòu)也很簡單拂蝎,主要是重寫一個convert方法完成由F對象到T對象的轉(zhuǎn)換。而Converter.Factory接口中定義了兩個responseBodyConverter惶室,分別用于ResponseBody對目標類的轉(zhuǎn)換以及目標類對ResponseBody的轉(zhuǎn)換温自,返回值分別為Converter<ResponseBody,?>和Converter<?,ResponseBody>;以及一個stringConverter皇钞,用于@Field悼泌、@Path等注解參數(shù)到String的轉(zhuǎn)換,返回值自然是一個Converter<?,String>(當(dāng)上述三個方法返回null時則代表無法解析相應(yīng)類型)夹界。所以Converter的工作原理就很清楚了馆里,當(dāng)需要類型轉(zhuǎn)換時,Converter.Factory會調(diào)用相應(yīng)的工廠方法,創(chuàng)建對應(yīng)轉(zhuǎn)換方向的Converter對象鸠踪,Converter對象再調(diào)用自身的convert方法完成實際轉(zhuǎn)換工作丙者。
需要注意的是Converter的添加是有優(yōu)先級的,當(dāng)多個Converter都能對同一對象進行轉(zhuǎn)換時慢哈,會優(yōu)先調(diào)用先添加的Converter蔓钟。而GsonConveter是不區(qū)分對象類型的,故當(dāng)需要特別地解析某個對象的卵贱,需要將其Converter先于GsonConverter添加滥沫,否則會報類型不匹配的錯誤。
? 事實上键俱,除了Call<T>中的T可以被代換兰绣,Call本身也是可以被替換的,這也是Retrofit與RxJava這一對能夠處處展現(xiàn)其相性的前提编振。對于響應(yīng)載體的替換需要用到CallAdapter缀辩,其實現(xiàn)與Converter相似,也是需要實現(xiàn)CallAdapter<T>踪央,并且實現(xiàn)CallAdapter.Factory工廠類臀玄,最后用addAdapterFactory方法添加,這樣一來自定義接口中就可以返回自定義響應(yīng)載體對象了畅蹂。
? CallAdapter<T>的接口又定義了些什么呢健无?它首先有兩個方法,responseType和adapt液斜,這兩個方法很好理解累贤,前者用于返回響應(yīng)體類型,即上面Converter定義的對象類型少漆;而后者用于返回CallAdpater用于代理Call的類型臼膏,即其泛型T,它接受一個Call參數(shù)示损,相當(dāng)于上面的ResponseBody渗磅,是原始響應(yīng)載體。當(dāng)然前面已經(jīng)提到CallAdapter同樣有一個內(nèi)部工廠類检访,其中有g(shù)et夺溢、getParameterUpperBound和getRawType三個方法。get自然是用于取得一個對象烛谊,其返回值便是需要返回的響應(yīng)載體類型风响,如果無法進行合適的轉(zhuǎn)換則返回null;getParameterUpperBound方法用于獲取響應(yīng)體類型丹禀,與responseType的返回值相同状勤;getRawType返回的是T的Class對象鞋怀。CallAdapter類的創(chuàng)建過程也大致可以歸結(jié)為:生成響應(yīng)載體時,調(diào)用CallAdapter.Factory的get方法持搜,傳入需要的類型參數(shù)密似,如Observable<Entity>,首先通過getRawType確認是否為Observable葫盼,再通過getParameterUpperBound獲取其泛型類型Entity残腌,在構(gòu)造CallAdapter實現(xiàn)類時傳入,從而使responseType方法能獲得正確的泛型類型贫导,最后通過adapt返回目標響應(yīng)載體對象抛猫。
最后
本來是想Retrofit和Gson一起列在這篇里的,不過貌似放在一起并不是很合適孩灯,Gson就下一次再搬嘍闺金,大概還有封裝好了之后就再也沒好好看過的 RxJava?
參考:你真的會用Retrofit2嗎?Retrofit2完全教程
如果上面的內(nèi)容有任何錯誤或不足峰档,歡迎各種交流指導(dǎo)败匹,鞠躬。
個人Github主頁:Lazxy