Retrofit2完全教程
本文中的Retrofit均指代Retrofit2.0。
本文涉及到的代碼以及測試使用的接口可在 Github 上找到。
測試接口服務器在 server 項目下,直接運行 RESTServer.main() 即可啟動測試服務器,所面代碼示例均使用該接口
當然你也可以自己借助 json-server 或 最新開源的Parse 搭建一個REST API,不過都需要安裝Node.js,有興趣的可以去試試。
接口列表:
注:以上的接口的{id}和{page}均代表一個純數字,/blog/{id} 可以用 /blog?id=XXX 代替,page同理。
前面寫了 你應該知道的HTTP基礎知識 介紹了HTTP的相關知識,不知道那些想了解Retrofit的同鞋是不是去看了 Retrofit的官方教程 ,曾經我在你真的會用Gson嗎? Gson使用指南(四) 中說當你了解了注解、反射、泛型、HTTP的內容只需要看一篇Retrofit的代碼示例就可以輕松玩轉Retrofit,不知道你玩轉了沒?
當然注解、反射、泛型的內容還沒有寫,Retrofit的內容卻先來了!畢竟看懂Retrofit也只需要會使就行,你準備好了嗎?
1、Retrofit入門
Retrofit 其實相當簡單,簡單到源碼只有37個文件,其中22個文件是注解還都和HTTP有關,真正暴露給用戶的類并不多,所以我看了一遍 官方教程 大多數情景就可以無障礙使用,如果你還沒有看過,可以先去看看,雖然是英文,但代碼才是最好的教程不是么?當然本篇文章會介紹得詳細一點,不能寫一篇水文。
1.1、創建Retrofit實例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
.build();
創建Retrofit實例時需要通過Retrofit.Builder,并調用baseUrl方法設置URL。
注: Retrofit2 的baseUlr 必須以 /(斜線) 結束,不然會拋出一個IllegalArgumentException,所以如果你看到別的教程沒有以 / 結束,那么多半是直接從Retrofit 1.X 照搬過來的。
1.2、接口定義
以獲取指定id的Blog為例:
public interface BlogService {
@GET("blog/{id}")
Call<ResponseBody> getFirstBlog(@Path("id") int id);
}
注意,這里是interface不是class,所以我們是無法直接調用該方法,我們需要用Retrofit創建一個BlogService的代理對象。
BlogService service = retrofit.create(BlogService.class);
拿到代理對象之后,就可以調用該方法啦。
1.3、接口調用
Call<ResponseBody> call = service.getFirstBlog(2);
// 用法和OkHttp的call如出一轍,
// 不同的是如果是Android系統回調方法執行在主線程
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
打印結果:
{
"code": 200,
"msg": "OK",
"data": {
"id": 2,
"date": "2016-04-15 03:17:50",
"author": "怪盜kidou",
"title": "Retrofit2 測試2",
"content": "這里是 Retrofit2 Demo 測試服務器2"
},
"count": 0,
"page": 0
}
示例源碼見 Example01.java
2、Retrofit注解詳解
上面提到Retrofit 共22個注解,這節就專門介紹這22個注解,為幫助大家更好理解我將這22個注解分為三類,并用表格的形式展現出來,表格上說得并不完整,具體的見源碼上的例子注釋。
第一類:HTTP請求方法
以上表格中的除HTTP以外都對應了HTTP標準中的請求方法,而HTTP注解則可以代替以上方法中的任意一個注解,有3個屬性:method、path,hasBody,下面是用HTTP注解實現上面 Example01.java 的例子。
public interface BlogService {
/**
* method 表示請的方法,不區分大小寫
* path表示路徑
* hasBody表示是否有請求體
*/
@HTTP(method = "get", path = "blog/{id}", hasBody = false)
Call<ResponseBody> getFirstBlog(@Path("id") int id);
}
第二類:標記類
第三類:參數類
注1:{占位符}和PATH盡量只用在URL的path部分,url中的參數使用Query和QueryMap 代替,保證接口定義的簡潔
注2:Query、Field和Part這三者都支持數組和實現了Iterable接口的類型,如List,Set等,方便向后臺傳遞數組。
Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//結果:ids[]=0&ids[]=1&ids[]=2
Path 示例源碼見 Example01.java
Field、FieldMap、Part和PartMap 示例源碼見 Example03.java
Header和Headers
Query、QueryMap、Url
3、Gson與Converter
在默認情況下Retrofit只支持將HTTP的響應體轉換換為ResponseBody,
這也是什么我在前面的例子接口的返回值都是 Call
,
但如果響應體只是支持轉換為ResponseBody的話何必要引用泛型呢,
返回值直接用一個Call就行了嘛,既然支持泛型,那說明泛型參數可以是其它類型的,
而Converter就是Retrofit為我們提供用于將ResponseBody轉換為我們想要的類型,
有了Converter之后我們就可以寫把我們的第一個例子的接口寫成這個樣子了:
public interface BlogService {
@GET("blog/{id}") //這里的{id} 表示是一個變量
Call<Result<Blog>> getFirstBlog(/** 這里的id表示的是上面的{id} */@Path("id") int id);
}
當然只改變泛型的類型是不行的,我們在創建Retrofit時需要明確告知用于將ResponseBody轉換我們泛型中的類型時需要使用的Converter
引入Gson支持:
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
通過GsonConverterFactory為Retrofit添加Gson支持:
Gson gson = new GsonBuilder()
//配置你的Gson
.setDateFormat("yyyy-MM-dd hh:mm:ss")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
//可以接收自定義的Gson,當然也可以不傳
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
這樣Retrofit就會使用Gson將ResponseBody轉換我們想要的類型。
這是時候我們終于可以演示如使創建一個Blog了!
@POST("blog")
Call<Result<Blog>> createBlog(@Body Blog blog);
被@Body注解的的Blog將會被Gson轉換成RequestBody發送到服務器。
BlogService service = retrofit.create(BlogService.class);
Blog blog = new Blog();
blog.content = "新建的Blog";
blog.title = "測試";
blog.author = "怪盜kidou";
Call<Result<Blog>> call = service.createBlog(blog);
結果:
Result{
code=200,
msg='OK',
data=Blog{
id=20,
date='2016-04-21 05:29:58',
author='怪盜kidou',
title='測試',
content='新建的Blog'
},
count=0,
page=0
}
4、RxJava與CallAdapter
說到Retrofit就不得說到另一個火到不行的庫RxJava,網上已經不少文章講如何與Retrofit結合,但這里還是會有一個RxJava的例子,不過這里主要目的是介紹使用CallAdapter所帶來的效果。
第3節介紹的Converter是對于Call 中T的轉換,而CallAdapter則可以對Call轉換,這樣的話Call 中的Call也是可以被替換的,而返回值的類型就決定你后續的處理程序邏輯,同樣Retrofit提供了多個CallAdapter,這里以RxJava的為例,用Observable代替Call:
引入RxJava支持:
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
通過RxJavaCallAdapterFactory為Retrofit添加RxJava支持:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
接口設計:
public interface BlogService {
@POST("/blog")
Observable<Result<List<Blog>>> getBlogs();
}
使用:
BlogService service = retrofit.create(BlogService.class);
service.getBlogs(1)
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<Result<List<Blog>>>() {
@Override
public void onCompleted() {
System.out.println("onCompleted");
}
@Override
public void onError(Throwable e) {
System.err.println("onError");
}
@Override
public void onNext(Result<List<Blog>> blogsResult) {
System.out.println(blogsResult);
}
});
結果:
Result{
code=200,
msg='OK',
data=[
Blog{
id=1,
date='2016-04-15 03:17:50',
author='怪盜kidou',
title='Retrofit2 測試1',
content='這里是 Retrofit2 Demo 測試服務器1'
},
.....
],
count=20,
page=1
}
「補充」:像上面的這種情況最后我們無法獲取到返回的Header和響應碼的,如果我們需要這兩者,提供兩種方案:
- 用Observable >``Observable ,這里的Response指retrofit2.Response
- 用Observable > 代替Observable ,這里的Result是指retrofit2.adapter.rxjava.Result,這個Result中包含了Response的實例
5、自定義Converter
本節的內容是教大家實現在一簡易的Converter,這里以返回格式為Call 為例。
在此之前先了解一下Converter接口及其作用:
public interface Converter<F, T> {
// 實現從 F(rom) 到 T(o)的轉換
T convert(F value) throws IOException;
// 用于向Retrofit提供相應Converter的工廠
abstract class Factory {
// 這里創建從ResponseBody其它類型的Converter,如果不能處理返回null
// 主要用于對響應體的處理
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
// 在這里創建 從自定類型到ResponseBody 的Converter,不能處理就返回null,
// 主要用于對Part、PartMap、Body注解的處理
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
// 這里用于對Field、FieldMap、Header、Path、Query、QueryMap注解的處理
// Retrfofit對于上面的幾個注解默認使用的是調用toString方法
public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
}
}
我們要想從Call 轉換為 Call 那么對應的F和T則分別對應ResponseBody和String,我們定義一個StringConverter并實現Converter接口。
public static class StringConverter implements Converter<ResponseBody, String> {
public static final StringConverter INSTANCE = new StringConverter();
@Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
}
我們需要一個Fractory來向Retrofit注冊StringConverter
public static class StringConverterFactory extends Converter.Factory {
public static final StringConverterFactory INSTANCE = new StringConverterFactory();
public static StringConverterFactory create() {
return INSTANCE;
}
// 我們只關實現從ResponseBody 到 String 的轉換,所以其它方法可不覆蓋
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == String.class) {
return StringConverter.INSTANCE;
}
//其它類型我們不處理,返回null就行
return null;
}
}
使用Retrofit.Builder.addConverterFactory向Retrofit注冊我們StringConverterFactory:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
// 如是有Gson這類的Converter 一定要放在其它前面
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
注:addConverterFactory是有先后順序的,如果有多個ConverterFactory都支持同一種類型,那么就是只有第一個才會被使用,而GsonConverterFactory是不判斷是否支持的,所以這里交換了順序還會有一個異常拋出,原因是類型不匹配。
只要返回值類型的泛型參數就會由我們的StringConverter處理,不管是Call 還是Observable
有沒有很簡單?如果你有其它的需求處理的就自己實現吧。
示例源碼見 Example09.java
6、自定義CallAdapter
本節將介紹如何自定一個CallAdapter,并驗證是否所有的String都會使用我們第5節中自定義的Converter。
先看一下CallAdapter接口定義及各方法的作用:
public interface CallAdapter<T> {
// 直正數據的類型 如Call<T> 中的 T
// 這個 T 會作為Converter.Factory.responseBodyConverter 的第一個參數
// 可以參照上面的自定義Converter
Type responseType();
<R> T adapt(Call<R> call);
// 用于向Retrofit提供CallAdapter的工廠類
abstract class Factory {
// 在這個方法中判斷是否是我們支持的類型,returnType 即Call<Requestbody>和`Observable<Requestbody>`
// RxJavaCallAdapterFactory 就是判斷returnType是不是Observable<?> 類型
// 不支持時返回null
public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
// 用于獲取泛型的參數 如 Call<Requestbody> 中 Requestbody
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
// 用于獲取泛型的原始類型 如 Call<Requestbody> 中的 Call
// 上面的get方法需要使用該方法。
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
}
}
了解了CallAdapter的結構和其作用之后,我們就可以開始自定義我們的CallAdapter了,本節以CustomCall 為例。
在此我們需要定義一個CustomCall,不過這里的CustomCall作為演示只是對Call的一個包裝,并沒有實際的用途。
public static class CustomCall<R> {
public final Call<R> call;
public CustomCall(Call<R> call) {
this.call = call;
}
public R get() throws IOException {
return call.execute().body();
}
}
有了CustomCall,我們還需要一個CustomCallAdapter來實現 Call 到 CustomCall 的轉換,這里需要注意的是最后的泛型,是我們要返回的類型。
public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> {
private final Type responseType;
// 下面的 responseType 方法需要數據的類型
CustomCallAdapter(Type responseType) {
this.responseType = responseType;
}
@Override
public Type responseType() {
return responseType;
}
@Override
public <R> CustomCall<R> adapt(Call<R> call) {
// 由 CustomCall 決定如何使用
return new CustomCall<>(call);
}
}
提供一個CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter:
public static class CustomCallAdapterFactory extends CallAdapter.Factory {
public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();
@Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
// 獲取原始類型
Class<?> rawType = getRawType(returnType);
// 返回值必須是CustomCall并且帶有泛型
if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {
Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
return new CustomCallAdapter(callReturnType);
}
return null;
}
}
使用addCallAdapterFactory向Retrofit注冊CustomCallAdapterFactory
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
.addConverterFactory(Example09.StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
.build();
注: addCallAdapterFactory與addConverterFactory同理,也有先后順序。
示例源碼見 Example10.java
7、其它說明
7.1 Retrofit.Builder
前面用到了 Retrofit.Builder 中的baseUrl、addCallAdapterFactory、addConverterFactory、build方法,還有callbackExecutor、callFactory、client、validateEagerly這四個方法沒有用到,這里簡單的介紹一下。
7.2 Retrofit的Url組合規則
從上面不能難看出以下規則:
-
如果你在注解中提供的url是完整的url,則url將作為請求的url。
-
如果你在注解中提供的url是不完整的url,且不以 / 開頭,則請求的url為baseUrl+注解中提供的值
-
如果你在注解中提供的url是不完整的url,且以 / 開頭,則請求的url為baseUrl的主機部分+注解中提供的值
7.3 Retrofit提供的Converter
7.4 Retrofit提供的CallAdapter:
來自:http://ocnyang.com/2016/10/10/Retrofit2/