什么是 Retrofit ?
Retrofit是Square開發(fā)的一個Android和Java的REST客戶端庫。這個庫非常簡單并且具有很多特性,相比其他的網(wǎng)絡庫,更容易讓初學者快速掌握。
Retrofit配置
在 app/build.gradle 添加依賴
在這里我們最好查看一下retrofit的官網(wǎng)添加最新依賴。
compile 'com.squareup.retrofit2:retrofit:2.2.0'
創(chuàng)建retrofit實例
String baseUrl = "http://192.168.1.8:8080/RetrofitService/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.build();
創(chuàng)建Retrofit實例時需要通過Retrofit.Builder,并調(diào)用baseUrl方法設置URL。
注: Retrofit2 的baseUlr 必須以 /(斜線) 結(jié)束,不然會拋出一個IllegalArgumentException,所以如果你看到別的教程沒有以 / 結(jié)束。
接口定義
以獲取指定id的News為例:
public interface NewsService {
//普通call方式
@GET("NewsServlet")
Call<ResponseBody> getNews(@Query("id") int id);
}
注意,這里是interface不是class,所以我們是無法直接調(diào)用該方法,我們需要用Retrofit創(chuàng)建一個NewsService的代理對象。
NewsService newsService = createRetrofit().create(NewsService.class);
拿到代理對象之后,就可以調(diào)用該方法啦。
接口調(diào)用
Call<ResponseBody> answers = newsService.getNews(1);
answers.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
Retrofit注解詳解
上面提到Retrofit 共22個注解,這節(jié)就專門介紹這22個注解,為幫助大家更好理解我將這22個注解分為三類,并用表格的形式展現(xiàn)出來,表格上說得并不完整,具體的見源碼上的例子注釋
第一類:HTTP請求方法

以上表格中的除HTTP以外都對應了HTTP標準中的請求方法,而HTTP注解則可以代替以上方法中的任意一個注解,有3個屬性:method、path、hasBody下面是用HTTP注解實現(xiàn)上面的例子。
/**
* method 表示請求的方法,區(qū)分大小寫
* path表示路徑
* hasBody表示是否有請求體
*/
@HTTP(method = "GET", path = "NewsServlet", hasBody = false)
Call<ResponseBody> getNews1(@Query("id") int id);
注:method 的值 retrofit 不會做處理,所以要自行保證其準確性,之前使用小寫也可以是因為示例源碼中的服務器不區(qū)分大小寫,所以希望大家注意。
第二類:標記類

FormUrlEncoded注解接口的2種寫法
/**
* {@link FormUrlEncoded} 表明是一個表單格式的請求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示將后面的 <code>String name</code> 中name的取值作為 username 的值
*/
@POST("/NewsServlet")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("name") String name, @Field("age") int age);
/**
* Map的key作為表單的鍵
*/
@POST("/NewsServlet")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
分別對應調(diào)用的2種方法
第一種:
NewsService newsService = createRetrofit().create(NewsService.class);
Call<ResponseBody> answers = newsService.testFormUrlEncoded1("txy", 18);
第二種:
NewsService newsService = createRetrofit().create(NewsService.class);
HashMap<String, Object> map = new HashMap<>();
map.put("name", "txy");
map.put("age", 18);
Call<ResponseBody> answers = newsService.testFormUrlEncoded2(map);
Multipart注解接口的3種寫法,Multipart一般是上傳文件的時候使用
/**
* {@link Part} 后面支持三種類型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意類型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它類型都必須帶上表單字段({@link okhttp3.MultipartBody.Part} 中已經(jīng)包含了表單字段的信息),
*/
@POST("NewsServlet")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
/**
* PartMap 注解支持一個Map作為參數(shù),支持 {@link RequestBody } 類型,
* 如果有其它的類型,會被{@link retrofit2.Converter}轉(zhuǎn)換,如后面會介紹的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
* 所以{@link MultipartBody.Part} 就不適用了,所以文件只能用<b> @Part MultipartBody.Part </b>
*/
@POST("NewsServlet")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
@POST("NewsServlet")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
對應的3種調(diào)用方式
第一種:
NewsService newsService = createRetrofit().create(NewsService.class);
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "txy");
RequestBody age = RequestBody.create(textType, "18");
//構(gòu)建要上傳的文件
File file = new File(Environment.getExternalStorageDirectory(), "paoche.jpg");
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);
MultipartBody.Part filePart =
MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
Call<ResponseBody> answers = newsService.testFileUpload1(name, age, filePart);
第二種:
NewsService newsService = createRetrofit().create(NewsService.class);
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "txy");
RequestBody age = RequestBody.create(textType, "18");
Map<String, RequestBody> fileUpload2Args = new HashMap<>();
fileUpload2Args.put("name", name);
fileUpload2Args.put("age", age);
//構(gòu)建要上傳的文件
File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);
MultipartBody.Part filePart =
MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
Call<ResponseBody> answers = newsService.testFileUpload2(fileUpload2Args, filePart);
第二種:
NewsService newsService = createRetrofit().create(NewsService.class);
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "txy");
RequestBody age = RequestBody.create(textType, "18");
Map<String, RequestBody> fileUpload3Args = new HashMap<>();
fileUpload3Args.put("name", name);
fileUpload3Args.put("age", age);
//構(gòu)建要上傳的文件
File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);
fileUpload3Args.put("fileUploader\"; filename=\"paoche3.jpg",requestFile);
Call<ResponseBody> answers = newsService.testFileUpload3(fileUpload3Args);
第三類:參數(shù)類

注1:{占位符}和PATH盡量只用在URL的path部分,url中的參數(shù)使用Query和QueryMap 代替,保證接口定義的簡潔
注2: Query、Field和Part這三者都支持數(shù)組和實現(xiàn)了Iterable接口的類型,如List,Set等,方便向后臺傳遞數(shù)組。
Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//結(jié)果:ids[]=0&ids[]=1&ids[]=2
Field、FieldMap、Part和PartMap 前面的例子已經(jīng)講過。
下面說一下Header和Headers的二種用法、靜態(tài)和動態(tài)我自己理解的,看下接口怎么寫
第一種靜態(tài)方式:
//靜態(tài)添加Header
@Headers("Cache-Control: max-age=640000")
@GET("NewsServlet")
Call<ResponseBody> testHeader1();
//靜態(tài)添加多個Header
@Headers({"X-Foo: Bar","X-Ping: Pong"})
@GET("NewsServlet")
Call<ResponseBody> testHeader2();
第二種動態(tài)方式:
//動態(tài)添加Header
@GET("NewsServlet")
Call<ResponseBody> testHeader3(@Header("Cache-Control") int header, @Query("name") String name);
//動態(tài)添加Header
@GET("NewsServlet")
Call<ResponseBody> testHeader4(@HeaderMap Map<String, String> headers, @Query("name") String name);
Gson與Converter
在默認情況下Retrofit只支持將HTTP的響應體轉(zhuǎn)換換為ResponseBody,這也是什么我在前面的例子接口的返回值都是 Call<ResponseBody>,但如果響應體只是支持轉(zhuǎn)換為ResponseBody的話何必要引用泛型呢,返回值直接用一個Call就行了嘛,既然支持泛型,那說明泛型參數(shù)可以是其它類型的,而Converter就是Retrofit為我們提供用于ResponseBody轉(zhuǎn)換為我們想要的類型,有了Converter之后我們就可以寫把我們的第一個例子的接口寫成這個樣子了:
public interface NewsService {
//普通call方式
@GET("NewsServlet")
Call<News> getNews(@Query("id") int id);
}
當然只改變泛型的類型是不行的,我們在創(chuàng)建Retrofit時需要明確告知用于將ResponseBody轉(zhuǎn)換我們泛型中的類型時需要使用的Converter
引入Gson支持:
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
通過GsonConverterFactory為Retrofit添加Gson支持:
Gson gson = new GsonBuilder()
//配置你的Gson
.setDateFormat("yyyy-MM-dd hh:mm:ss")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//可以接收自定義的Gson,當然也可以不傳
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
說道Gson我們就先聊一下有些Http服務返回一個固定格式的數(shù)據(jù)的問題。 例如:
{
"code": 0,
"message": "成功",
"data": {}
}
或:
{
"code": 0,
"message": "成功",
"data": []
}
不一樣的地方就是data是一個對象或?qū)ο髷?shù)組,這種情況下咱們怎么統(tǒng)一處理,我們可以創(chuàng)建一個BaseModel類
public class BaseModel<T> {
public int resultCode;
public String resultMessage;
public T data;
}
以demo返回的2種數(shù)據(jù)為例
data是普通對象
{"code":200,"data":{"comments":[{"content":"我是內(nèi)容"}],"date":"20170603","id":1,"likes":"我喜歡新聞","title":"我是標題","views":"我是views"},"message":"獲取成功!"}
以第一個例子為例咱們看下怎么修改,Call的泛型填寫B(tài)aseModel<News>即可
public interface NewsService {
//普通call方式
@GET("NewsServlet")
Call<BaseModel<News>> testConverter1(@Query("id") int id);
}
data是普通對象數(shù)組
{"code":200,"data":[{"comments":[{"content":"我是內(nèi)容"}],"date":"20170603","id":1,"likes":"我喜歡新聞","title":"我是標題","views":"我是views"}],"message":"獲取成功!"}
Call的泛型填寫B(tài)aseModel<List<News>>即可
public interface NewsService {
//普通call方式
@POST("NewsServlet")
Call<BaseModel<List<News>>> testConverter1();
}
結(jié)合我們現(xiàn)在用的只傳JSON的請求,請求header的content-type是 application/json,也就是@Body的用法寫一個列子:
@POST("NewsServlet")
Call<BaseModel<List<News>>> testConverte3(@Body News news);
被@Body注解的的Blog將會被Gson轉(zhuǎn)換成RequestBody發(fā)送到服務器。
下面咱們看下接口怎么調(diào)用
NewsService newsService = createRetrofit().create(NewsService.class);
News news = new News();
news.likes = "likes";
news.date = "20170619";
news.title = "Converter用法3";
news.id = 1;
news.views = "views";
Call<BaseModel<List<News>>> call = newsService.testConverter3(news);
服務器收到的數(shù)據(jù)就是如下這樣
{"comments":[],"date":"20170619","id":1,"likes":"likes","title":"Converter用法3","views":"views"}
RxJava與CallAdapter
說到Retrofit就不得說到另一個火到不行的庫RxJava,網(wǎng)上已經(jīng)不少文章講如何與Retrofit結(jié)合,但這里還是會有一個RxJava的例子,不過這里主要目的是介紹使用CallAdapter所帶來的效果。
注意:下面說的RxJava是指的RxJava2.0
第3節(jié)介紹的Converter是對于Call<T>中T的轉(zhuǎn)換,而CallAdapter則可以對Call轉(zhuǎn)換,這樣的話Call<T>中的Call也是可以被替換的,而返回值的類型就決定你后續(xù)的處理程序邏輯,同樣Retrofit提供了多個CallAdapter,這里以RxJava的為例,用Observable代替Call:
引入RxJava支持:
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
// 針對rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
通過RxJavaCallAdapterFactory為Retrofit添加RxJava支持:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//可以接收自定義的Gson,當然也可以不傳
.addConverterFactory(GsonConverterFactory.create(gson))
// 針對rxjava2.x
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
接口設計:
public interface NewsService {
@GET("NewsServlet")
Observable<BaseModel<List<News>>> testCallAdapter();
}
使用:
NewsService newsService = createRetrofit().create(NewsService.class);
Observable<BaseModel<List<News>>> observable = newsService.testCallAdapter();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<BaseModel<List<News>>>() {
@Override
public void onSubscribe(Disposable d) {
//運行在io線程
}
@Override
public void onNext(BaseModel<List<News>> value) {
Log("RXJava用法 onNext:" + FastJsonUtils.toJson(value));
}
@Override
public void onError(Throwable e) {
Log("RXJava用法 onError:" + e.getMessage());
}
@Override
public void onComplete() {
Log("RXJava用法 onComplete");
}
});
結(jié)果:
{"code":200,"data":[{"comments":[{"content":"我是內(nèi)容"}],"date":"20170603","id":1,"likes":"我喜歡新聞","title":"我是標題","views":"我是views"}],"success":true}
有的時候我們需要獲取原始的json的字符串,GSON是滿足不了我們的,下面我們來看下怎么獲取原始的json串,我們需要借助另一個Converter,后面也會介紹都有哪些Converter
引入scalars支持:
compile 'com.squareup.retrofit2:converter-scalars:2.0.2'
通過ScalarsConverterFactory為Retrofit添加String支持:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//添加ScalarsConverterFactory支持
.addConverterFactory(ScalarsConverterFactory.create())
//可以接收自定義的Gson,當然也可以不傳
.addConverterFactory(GsonConverterFactory.create(gson))
// 針對rxjava2.x
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
注意: 如果需要既支持String又支持Gson需要先設置ScalarsConverterFactory 后設置 GsonConverterFactory 如果上所示。
接口
@GET("NewsServlet")
Observable<String> testScalars();
看下怎么調(diào)用
Observable<String> observable = newsService.testScalars();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String value) {
Log("Scalar用法 onNext value:" + value);
}
@Override
public void onError(Throwable e) {
Log("Scalar用法 onError " +e.getMessage());
}
@Override
public void onComplete() {
}
});
像上面的這2種情況最后我們無法獲取到返回的Header和響應碼的,如果我們需要這兩者,提供兩種方案:
1、用Observable<Response<T>> Observable<T> ,這里的Response指retrofit2.Response
2、用Observable<Result<T>>代替Observable<T>,這里的Result是指retrofit2.adapter.rxjava.Result,這個Result中包含了Response的實例
看下第一種接口怎么設計
@GET("NewsServlet")
Observable<Response<BaseModel<List<News>>>> testRxAndroid2();
可以理解在以前BaseModel<List<News>>的外面把包裝一層Response最終Response<BaseModel<List<News>>>,接下來看下怎么調(diào)用
Observable<Response<BaseModel<List<News>>>> observable = newsService.testRxAndroid2(); observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<Response<BaseModel<List<News>>>>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Response<BaseModel<List<News>>> value) {
Headers headers = value.headers();
Set<String> names = headers.names();
Log("RXJava用法2 onNext value:" + FastJsonUtils.toJson(value.body()));
Log("RXJava用法2 onNext headers:" + FastJsonUtils.toJson(names));
BaseModel<List<News>> body = value.body();
Log("RXJava用法2 onNext body:" + FastJsonUtils.toJson(body));
}
@Override
public void onError(Throwable e) {
Log("RXJava用法2 onError:"+e.getMessage());
}
@Override
public void onComplete() {
Log("RXJava用法2 onComplete");
}
});
通過Response的headers()方法獲取所有header
其它說明
Retrofit.Builder
前面用到了 Retrofit.Builder 中的baseUrl、addCallAdapterFactory、addConverterFactory、build方法,還有callbackExecutor、callFactory、client、validateEagerly這四個方法沒有用到,這里簡單的介紹一下。
| 方法 | 用途 | |
|---|---|---|
| callbackExecutor(Executor) | 指定Call.enqueue時使用的Executor,所以該設置只對返回值為Call的方法有效 |
|
| callFactory(Factory) | 設置一個自定義的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就實現(xiàn)了okhttp3.Call.Factory接口,下面的client(OkHttpClient)最終也是調(diào)用了該方法,也就是說兩者不能共用 |
|
| client(OkHttpClient) | 設置自定義的OkHttpClient,以前的Retrofit版本中不同的Retrofit對象共用同OkHttpClient,在2.0各對象各自持有不同的OkHttpClient實例,所以當你需要共用OkHttpClient或需要自定義時則可以使用該方法,如:處理Cookie、使用stetho 調(diào)式等 |
|
| validateEagerly(boolean) | 是否在調(diào)用create(Class)時檢測接口定義是否正確,而不是在調(diào)用方法才檢測,適合在開發(fā)、測試時使用 |
Retrofit的Url組合規(guī)則
從上面不能難看出以下規(guī)則:
如果你在注解中提供的url是完整的url,則url將作為請求的url。
如果你在注解中提供的url是不完整的url,且不以 / 開頭,則請求的url為baseUrl+注解中提供的值
如果你在注解中提供的url是不完整的url,且以 / 開頭,則請求的url為baseUrl的主機部分+注解中提供的值
Retrofit提供的Converter
| Converter | Gradle依賴 |
|---|---|
| Gson | com.squareup.retrofit2:converter-gson:2.0.2 |
| Jackson | com.squareup.retrofit2:converter-jackson:2.0.2 |
| Moshi | com.squareup.retrofit2:converter-moshi:2.0.2 |
| Protobuf | com.squareup.retrofit2:converter-protobuf:2.0.2 |
| Wire | com.squareup.retrofit2:converter-wire:2.0.2 |
| Simple XML | com.squareup.retrofit2:converter-simplexml:2.0.2 |
| Scalars | com.squareup.retrofit2:converter-scalars:2.0.2 |
Retrofit提供的CallAdapter
| CallAdapter | Gradle依賴 |
|---|---|
| guava | com.squareup.retrofit2:adapter-guava:2.0.2 |
| Java8 | com.squareup.retrofit2:adapter-java8:2.0.2 |
| rxjava | com.squareup.retrofit2:adapter-rxjava:2.0.2 |
結(jié)語
本篇博客已經(jīng)完成,后續(xù)我還會出進階版本,包括調(diào)用https請求、下載文件、報文加密等等。最后感謝 怪盜kidou 老鐵,本篇博客好多都是借鑒的這位兄弟的,添加了一些自己的理解。