Retrofit is a type-safe HTTP client for Android and Java.
Retrofit是面向Android和Java平臺(tái)的一個(gè)類(lèi)型安全的HTTP客戶(hù)端毯盈。
<br />
本文將圍繞Retrofit的注解、CallAdapter(配合RxJava)恨胚、Converter進(jìn)行介紹撞羽;
首先在你的工程添加以下依賴(lài):
final OKHTTP_VERSION = '3.4.1'
final RETROFIT_VERSION = '2.1.0'
compile "com.squareup.okhttp3:okhttp:$OKHTTP_VERSION"
compile "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION"
compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
版本號(hào)如有更新的可以更改到最新
接下來(lái)演示如何請(qǐng)求接口:
假設(shè)我們現(xiàn)在需要請(qǐng)求一個(gè)這樣的接口: https://api.github.com/users/yuhengye
首先創(chuàng)建一個(gè)TestService接口
import retrofit2.Call;
import retrofit2.http.GET;
public interface TestService {
//定義一條接口請(qǐng)求
@GET("users/yuhengye")
Call<String> getUserInfo();
}
先不關(guān)心為何創(chuàng)建一個(gè)這樣的接口
接下來(lái)創(chuàng)建一個(gè)RetrofitManager.class類(lèi)
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitManager {
private static Retrofit mRetrofit;
private static TestService mTestService;
private static OkHttpClient mOkHttpClient;
public static OkHttpClient getOkHttpClient(){
if(mOkHttpClient == null) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
}
return mOkHttpClient;
}
public static Retrofit getRetrofit(){
if(mRetrofit == null) {
mRetrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(getOkHttpClient())
.build();
}
return mRetrofit;
}
public static TestService getTestService(){
if(mTestService == null) {
mTestService = getRetrofit().create(TestService.class);
}
return mTestService;
}
}
接下來(lái)看下如何在代碼里請(qǐng)求接口:
1.異步執(zhí)行:
Call<String> call = RetrofitManager.getTestService().getUserInfo();
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
//獲得結(jié)果
String result = response.body();
}
@Override
public void onFailure(Call<String> call, Throwable t) {
}
});
2.同步執(zhí)行
Call<String> call = RetrofitManager.getTestService().getUserInfo();
try {
Response<String> response = call.execute();
//獲得結(jié)果
String result = response.body();
}catch (IOException ioException){
ioException.printStackTrace();
}
上面例子中的Call<String>里申明的call是用于操控網(wǎng)絡(luò)請(qǐng)求的李皇,而申明的泛型String類(lèi)型這是我們想要獲得請(qǐng)求結(jié)果時(shí)轉(zhuǎn)換成的類(lèi)型伺糠,下文會(huì)解釋是怎么轉(zhuǎn)換的枕荞;
在Android里不能在主線程執(zhí)行請(qǐng)求網(wǎng)絡(luò)的操作造烁,所以使用同步請(qǐng)求是需要另起線程,所以一般都是異步請(qǐng)求汽摹;
如果你像以上所述操作后李丰,那么你已經(jīng)成功使用Retrofit請(qǐng)求網(wǎng)絡(luò)。
接下來(lái)看下如何定義一條接口請(qǐng)求:
//定義一條接口請(qǐng)求
//等價(jià)于https://api.github.com/users/yuhengye
@GET("users/yuhengye")
Call<String> getUserInfo();
可以看到方法頭部使用了注解逼泣,官方文檔是這么解釋的:
Every method must have an HTTP annotation that provides the request method and relative URL. There are five built-in annotations: GET, POST, PUT, DELETE, and HEAD. The relative URL of the resource is specified in the annotation.
<br />
每個(gè)方法都必須有一個(gè)HTTP注釋提供的請(qǐng)求方法和相對(duì)URL趴泌。有五個(gè)內(nèi)置注釋?zhuān)篏ET,POST拉庶,PUT嗜憔,DELETE和HEAD。資源的相對(duì)URL在注釋中指定氏仗。
<br />
也就是說(shuō)如果你想用GET請(qǐng)求就可以在方法頭部加@GET
注釋?zhuān)褂肞OST請(qǐng)求就使用@POST
即可吉捶,在注釋里申明的相對(duì)URL會(huì)和在生成Retrofit實(shí)例時(shí)使用的baseUrl()
方法里傳的值結(jié)合;
Retrofit提供不同HTTP請(qǐng)求方法的注解:
GET
,POST
,PUT
,DELETE
,HEAD
,OPTIONS
,PATCH
,HTTP
;
Retrofit還提供了以下注解:
Path
,Header
,HeaderMap
,Headers
,Query
,QueryMap
,FormUrlEncoded
,Field
,FieldMap
,Multipart
,Part
,PartMap
,Body
,Streaming
,Url
;
值得一提的是,Retrofit里提供的注解命名規(guī)范是這樣的皆尔,如果是HTTP請(qǐng)求的方法呐舔,則一律是大寫(xiě),其他注解才使用駝峰式慷蠕;
接下來(lái)解釋下這些注解如何使用珊拼,為了縮短篇幅,以下在調(diào)用接口時(shí)流炕,就不寫(xiě)獲取接口實(shí)例的方法了澎现,比如調(diào)用getUserInfo()
相當(dāng)于RetrofitManager.getTestService().getUserInfo()
,并且申明的接口方法都是在TestService接口下浪感;
Path
@GET("users/{username}")
Call<String> getUserInfo(@Path("username") String name);
//等價(jià)于https://api.github.com/users/yuhengye
getUserInfo("yuhengye");
可以看出Path
是用于替換相對(duì)路徑url里的值昔头;
Header,HeaderMap,Headers
//在方法里聲明多個(gè)參數(shù)時(shí),可以多種注解組合使用
@GET("users/{username}")
Call<String> getUserInfo(@Header("Accept-Language") String lang, @Path("username") String name);
//Request Header
//Accept-Language:en-US
getUserInfo("en-US", "yuhengye");
---------------divider---------------
@GET("users/yuhengye")
Call<String> getUserInfo(@Header("Accept-Language") List<String> langs);
List<String> langs = new ArrayList<>();
langs.add("en-US");
langs.add("zh-CN");
//Request Header
//Accept-Language:en-US,zh-CN
getUserInfo(langs);
---------------divider---------------
@GET("users/yuhengye")
Call<String> getUserInfo(@HeaderMap<String,String> headers);
Map<String,String> headers = new HashMap<>();
header.put("Accept", "text/plain");
header.put("Accept-Charset", "utf-8");
//Request Header
//Accept:text/plain
//Accept-Charset:utf-8
getUserInfo(headers);
---------------divider---------------
@Headers("Accept-Language: en-US")
@GET("users/yuhengye")
Call<String> getUserInfo();
@Headers({
"Accept-Language: en-US,zh-CN",
"Cache-Control: max-age=640000"
})
@GET("users/yuhengye")
Call<String> getUserInfo2();
Header
是用于給HTTP的請(qǐng)求頭增加信息的影兽,HeaderMap
則是當(dāng)有多個(gè)Header和數(shù)量不固定的Header準(zhǔn)備的揭斧;下面介紹Query
和QueryMap
,Field
和FieldMap
時(shí)也是一樣的道理峻堰;而Headers
是當(dāng)你的接口的Header是固定的時(shí)候才用到讹开,并且支持多個(gè)(數(shù)組形式);
Query,QueryMap
@GET("search/repositories")
Call<String> searchRepo(@Query("q") String keywords, @Query("sort") String sort);
//等價(jià)于https://api.github.com/search/repositories?q=retrofit&sort=stars
searchRepo("retrofit", "stars");
---------------divider---------------
@GET("search/repositories?order=desc")
Call<String> searchRepoOrderByDesc(@QueryMap Map<String,String> params);
Map<String,String> params = new HashMap<>();
header.put("q", "retrofit");
//等價(jià)于https://api.github.com/search/repositories?order=desc&q=retrofit
searchRepo(params);
Query
和QueryMap
主要是在GET請(qǐng)求時(shí)捐名,給你的URL傳參旦万,當(dāng)然URL的參數(shù)也可以固定在相對(duì)路徑里面;
FormUrlEncoded,Field,FieldMap
@FormUrlEncoded
@POST(“https://api.weibo.com/oauth2/access_token”)
Call<String> oauthToken(@Field("access_token") String code, @Field("client_secret") String client_secret);
//POST表單數(shù)據(jù)
oauthToken("codeValue", "secretValue");
這里沒(méi)有使用到FieldMap
镶蹋,相信你也知道它的用處是什么了成艘,當(dāng)你需要使用到表單的方式請(qǐng)求時(shí)赏半,只需使用@FormUrlEncoded
注解就可以了,實(shí)際上跟你替換成@Headers("Content-Type: application/x-www-form-urlencoded")
同理淆两,只是框架已經(jīng)幫我們實(shí)現(xiàn)了断箫,Field
和FieldMap
跟Query
和QueryMap
類(lèi)似,只不過(guò)前者是在表單提交時(shí)使用的注解秋冰,后者是URL傳參時(shí)使用的注解仲义。
在上面例子中,@Post
中的URL不再是相對(duì)路徑剑勾,而是絕對(duì)路徑埃撵,當(dāng)你使用絕對(duì)路徑時(shí),Retrofit就會(huì)忽略掉你實(shí)例化它時(shí)通過(guò)baseUrl()
方法傳給它的host(接口前綴地址)虽另;因?yàn)閷?shí)際開(kāi)發(fā)中暂刘,有可能只有幾個(gè)接口的host是跟你設(shè)置的host不一樣的,為了避免實(shí)例化多個(gè)Retrofit捂刺,你只需傳入絕對(duì)路徑即可鸳惯;
Body
@POST("users/new")
Call<String> createUser(@Body RequestBody user);
---------------divider---------------
@POST("users/new")
Call<String> createUser(@Body User user);
當(dāng)發(fā)起POST請(qǐng)求時(shí),使用@Body
把參數(shù)直接放到你的request的body里面叠萍,如果在實(shí)例化Retrofit時(shí),沒(méi)有指定converter(下文會(huì)解釋這是什么), 那@Body
注解后面的參數(shù)類(lèi)型必須是RequestBody類(lèi)型绪商;
Multipart,Part,PartMap
@Multipart
@POST(“https://api.weibo.com/2/statuses/upload_pic.json”)
Call<String> uploadPicture(
@Part("access_token") RequestBody token,
@Part MultipartBody.Part file);
RequestBody requestFile = RequestBody.create(MediaType.parse("application/otcet-stream"), new File(s));
MultipartBody.Part body = MultipartBody.Part.createFormData("pic", "share.png", requestFile);
uploadPicture(RequestBody.create(MediaType.parse("multipart/form-data"), "tokenValue"), body);
以下是POST請(qǐng)求時(shí)苛谷,multipart
的body內(nèi)容
//部分頭信息
Content-Type: multipart/form-data; boundary=d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Length: 32461
//body內(nèi)容開(kāi)始
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Disposition: form-data; name="access_token"
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 32
2.00tJ6X3GiGSdcC5f7433b5a40iAPJA
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Disposition: form-data; name="pic"; filename="share.png"
Content-Type: application/otcet-stream
Content-Length: 31813
file bytes
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9--
//body內(nèi)容結(jié)束
通常我們上傳文件時(shí),大多數(shù)使用multipart
格郁,下面簡(jiǎn)單描述下multipart
的傳輸過(guò)程:
每一個(gè)部分都是以--加boundary(分隔符)開(kāi)始腹殿,然后是該部分內(nèi)容的描述信息,然后一個(gè)回車(chē)例书,然后是描述信息的具體內(nèi)容锣尉;如果傳送的內(nèi)容是一個(gè)文件的話,那么還會(huì)包含文件名信息决采,以及文件內(nèi)容的類(lèi)型自沧。上面請(qǐng)求的第二個(gè)部分就是是一個(gè)文件體的結(jié)構(gòu),最后會(huì)以--boundary符--結(jié)尾树瞭,表示請(qǐng)求體結(jié)束拇厢。
當(dāng)使用@Multipart
時(shí),相當(dāng)于@Headers("Content-Type: multipart/form-data; boundary=${bound}")
晒喷,并且框架幫你把boundary(分隔符)的值設(shè)定好了;
@Part
和@PartMap
是配合@Multipart
使用的孝偎,使用@PartMap
參數(shù)類(lèi)型必須是Map,key就相當(dāng)于partName, value所接受的參數(shù)類(lèi)型跟@Part
一樣凉敲,但建議不要是MultipartBody.Part(因?yàn)镸ultipartBody.Part本身已經(jīng)包含partName)衣盾,以下是@Part
接受不同參數(shù)時(shí)的情況:
1.當(dāng)方法里傳參類(lèi)型是MultipartBody.Part時(shí)寺旺,不要這樣(@Part("partName") MultipartBody.Part part
申明,partName會(huì)被忽略势决,因?yàn)樵跇?gòu)建MultipartBody.Part時(shí)阻塑,已經(jīng)包含了part的name在里面;
2.當(dāng)方法里傳參類(lèi)型是RequestBody時(shí)徽龟,會(huì)把RequestBody的ContentType的值加到body里面去叮姑,比如像上面例子這樣申明Call<String> uploadPicture(@Part("access_token") RequestBody token);
那么調(diào)用該方法uploadPicture(RequestBody.create(MediaType.parse("multipart/form-data"), "tokenValue"))
時(shí)插入body里面的內(nèi)容是這樣的:
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Disposition: form-data; name="access_token"
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 32
2.00tJ6X3GiGSdcC5f7433b5a40iAPJA
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9--
3.當(dāng)方法里傳參類(lèi)型是其他對(duì)象類(lèi)型時(shí),將會(huì)通過(guò)實(shí)例化Retrofit時(shí)指定的conveter(下文會(huì)解釋這是什么)來(lái)轉(zhuǎn)換成RequestBody据悔,然后跟上面第二點(diǎn)所述處理;
Streaming
@Streaming
@GET("video/test.mp4")
Response getVideo();
Response response = getVideo();
InputStream videoDataStream = response.getBody().in();
使用@Streaming
時(shí)返回值必須是Response传透,包含了HTTP請(qǐng)求最初的body內(nèi)容,未經(jīng)任何轉(zhuǎn)換极颓;一般是獲取比較大的數(shù)據(jù)流時(shí)使用朱盐,或者下載文件時(shí)使用;
Url
//等價(jià)于https://api.github.com/users/yuhengye
@GET("users/yuhengye")
Call<String> getUserInfo();
---------------divider---------------
@GET
Call<String> getUserInfo(@Url String url);
//等價(jià)于https://api.github.com/users/yuhengye
getUserInfo("users/yuhengye);
從上面可以看出,如果方法里申明帶@Url
的參數(shù)菠隆,則在方法頭部申明HTTP請(qǐng)求方式時(shí)兵琳,可以不再申明相對(duì)路徑地址;
實(shí)例化Retrofit時(shí)調(diào)用baseUrl(api)
方法傳進(jìn)去的api地址應(yīng)該以/
結(jié)尾骇径,如果傳的是https://api.github.com/search
躯肌,Retrofit會(huì)以/
最后出現(xiàn)的位置作為結(jié)尾當(dāng)成https://api.github.com/
處理,值得一提的是破衔,在申明請(qǐng)求接口頭部定義url的時(shí)候清女,建議統(tǒng)一用相對(duì)路徑(前面不包括/
),類(lèi)似@GET("users/yuhengye")
而不是@GET("/users/yuhengye")
晰筛,因?yàn)檫@兩者處理上也有所不同:
BaseUrl:https://api.github.com/search/
Endpoint:/users/yuhengye
Result:https://api.github.com/users/yuhengye
BaseUrl:https://api.github.com/search/
Endpoint:users/yuhengye
Result:https://api.github.com/search/users/yuhengye
CallAdapter
上文在申明接口的方法時(shí)嫡丙,返回值統(tǒng)一都是Call<String>
類(lèi)型,因?yàn)槟J(rèn)情況下返回值是只支持Call
類(lèi)型的读第,這個(gè)類(lèi)包含了最基本的網(wǎng)絡(luò)請(qǐng)求操作曙博,相信你看過(guò)很多文章是XXX App,使用了以下框架:Retrofit + RxJava + ...
怜瞒,如果你沒(méi)用過(guò)RxJava
父泳,可以跳過(guò)此段介紹,下面簡(jiǎn)單介紹RxJava
結(jié)合Retrofit
請(qǐng)求網(wǎng)絡(luò)盼砍。
首先加入以下依賴(lài):
compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION"
//以下是下面例子中使用RxJava的版本
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
在實(shí)例化Retrofit時(shí)尘吗,改成這樣:
public static Retrofit getRetrofit(){
if(mRetrofit == null) {
mRetrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(getOkHttpClient())
.build();
}
return mRetrofit;
}
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
這個(gè)是我們?cè)黾拥恼{(diào)用,以后在申明接口的方法返回值時(shí)浇坐,就可以這樣:
@GET("users/yuhengye")
Observable<String> getUserInfo();
RetrofitManager
.getTestService()
.getUserInfo()//返回一個(gè)Observable<String>對(duì)象
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String result) {
}
});
Converter(轉(zhuǎn)換器)
上文中多次提到過(guò)converter的概念睬捶,現(xiàn)在簡(jiǎn)單介紹下:
Retrofit框架默認(rèn)是使用OKHTTP去請(qǐng)求網(wǎng)絡(luò)的,而在OKHTTP中網(wǎng)絡(luò)請(qǐng)求默認(rèn)都是返回一個(gè)ResponseBody
類(lèi)型的值近刘,為了免去網(wǎng)絡(luò)請(qǐng)求中重復(fù)的轉(zhuǎn)換操作擒贸,在實(shí)例化Retrofit時(shí)可以通過(guò)addConverterFactory(GsonConverterFactory.create())
方法增加一個(gè)轉(zhuǎn)換工廠臀晃,Retrofit內(nèi)置的轉(zhuǎn)換工廠是只支持ResponseBody
,Void
類(lèi)型介劫,值得一提的是徽惋,在接口申明的返回值里如果不關(guān)心返回結(jié)果,必須這樣申明Call<Void>
座韵,不能沒(méi)有泛型Call
這樣申明险绘;
文章開(kāi)始介紹要引入的依賴(lài)時(shí),有包括這個(gè)compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
誉碴, 這個(gè)庫(kù)的的名字是converter-gson
宦棺,那么肯定是跟gson相關(guān)的了,回想一下黔帕,剛才我們申明接口方法時(shí)的返回值代咸,統(tǒng)一都是Call<String>
,之所以可以申明String類(lèi)型成黄,是因?yàn)槲覀兺ㄟ^(guò)這個(gè)依賴(lài)庫(kù)提供的Gson轉(zhuǎn)換工廠把結(jié)果轉(zhuǎn)換成了String類(lèi)型呐芥,然后請(qǐng)求返回結(jié)果后得到一個(gè)Response<String>
類(lèi)型的實(shí)例response
,Response
申明的泛型類(lèi)型是跟Call
申明的泛型類(lèi)型一致的奋岁,而String result = response.body()
就是獲得申明泛型類(lèi)型的值思瘟,通常來(lái)說(shuō)我們是知道請(qǐng)求接口會(huì)返回的數(shù)據(jù)格式,如果是json
格式闻伶,每次都是通過(guò)獲得String
類(lèi)型潮太,再轉(zhuǎn)換成對(duì)應(yīng)的實(shí)體類(lèi),那么會(huì)做很多重復(fù)的工作虾攻;所以上文在接口申明方法時(shí)的泛型類(lèi)型都可以替換成對(duì)應(yīng)的實(shí)體類(lèi),比如這樣:
//GitHubUser就是通過(guò)gson轉(zhuǎn)換成對(duì)應(yīng)的實(shí)體類(lèi);
@GET("users/yuhengye")
Call<GitHubUser> getUserInfo();
當(dāng)然Converter還不止是在請(qǐng)求結(jié)果返回時(shí)轉(zhuǎn)換結(jié)果更鲁,還包括發(fā)起請(qǐng)求時(shí)的一些數(shù)據(jù)轉(zhuǎn)換等霎箍;以上所提到的@HeaderMap
、@QueryMap
澡为、FieldMap
后面申明的參數(shù)類(lèi)型必須是Map漂坏,但是key和value沒(méi)有限定必須是String類(lèi)型,這3個(gè)注解和@Path
媒至、@Header
顶别、@Query
、@Field
默認(rèn)最終都會(huì)調(diào)用內(nèi)置的converter調(diào)用String.valueOf(object)
轉(zhuǎn)成String類(lèi)型拒啰,也就是調(diào)用Object的toString()
方法驯绎;
我們也可以自定義自己的Converter工廠,本文就不再敘述了谋旦,后面會(huì)再寫(xiě)一遍關(guān)于Retrofit的源碼和原理探索還有進(jìn)階使用剩失;
相關(guān)文章:
Retrofit源碼解析
參考文檔:
Retrofit官方文檔
http://square.github.io/retrofit/
HTTP協(xié)議之multipart/form-data請(qǐng)求分析
http://blog.csdn.net/five3/article/details/7181521/