轉自:http://www.jianshu.com/p/308f3c54abdd
作者: @怪盜kidou
如需轉載需在明顯位置保留作者信息及原文鏈接
Retrofit版本: 2.0.2
本文注目錄:
- Retrofit入門
- Retrofit註解詳解
- Gson與Converter
- RxJava與CallAdapter
- 自定義Converter
- 自定義CallAdapter
- 其它說明
前言
本文中的Retrofit均指代Retrofit2.0。
本文涉及到的代碼以及測試使用的接口可在Github上找到。
測試接口服務器在 server 項目下,直接運行
RESTServer.main()
即可啓動測試服務器,所面代碼示例均使用該接口(接口地址
http://localhost:4567/ ).
當然你也可以自己藉助
json-server 或 最新開源的Parse 搭建一個REST API,不過都需要安裝Node.js,有興趣的可以去試試。
接口列表:
地址 | 請求方法 | 參數 | 說明 |
---|---|---|---|
/blog | GET | page={page},sort=asc或desc | 分頁獲取Blog列表,每頁10條 |
/blog/{id} | GET | id | 獲取指定ID的Blog |
/blog | POST | {"author":"","title":"","content":""} | 創建一個新Blog |
/blog/{id} | PUT | {"author":"","title":"","content":""} 中至少一個 | 修改Blog |
/blog/{id} | DELETE | id | 刪除一個Blog |
/form | POST | 任意,最終以Json Object形式返回 | 用於測試Form表單,支持文件上傳 |
/headers | GET | showAll=true或false,默認false | 返回自定義請求頭,all=true是顯示全部 |
注:以上的接口的{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有關,真正暴露給用戶的類並不多,所以我看了一遍 官方教程 大多數情景就可以無障礙使用,如果你還沒有看過,可以先去看看,雖然是英文,但代碼纔是最好的教程不是麼?當然本篇文章會介紹得詳細一點,不能寫一篇水文,畢竟我給它命名爲《你真的會用Retrofit2嗎?Retrofit2完全教程》。
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);
}
示例源碼見 Example02.java
第二類:標記類
示例源碼見
Example03.java
第三類:參數類
注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 示例源碼見
Example04.java
Query、QueryMap、Url 示例源碼見
Example05.java
3、Gson與Converter
在默認情況下Retrofit只支持將HTTP的響應體轉換換爲ResponseBody
,
這也是什麼我在前面的例子接口的返回值都是 Call<ResponseBody>
,
但如果響應體只是支持轉換爲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();
示例源碼見 Example06.java
這樣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}
示例源碼見 Example07.java
如果你對Gson不熟悉可以參考我寫的《你真的會用Gson嗎?Gson使用指南》 系列。
4、RxJava與CallAdapter
說到Retrofit就不得說到另一個火到不行的庫RxJava
,網上已經不少文章講如何與Retrofit結合,但這裏還是會有一個RxJava的例子,不過這裏主要目的是介紹使用CallAdapter
所帶來的效果。
第3節介紹的Converter
是對於Call<T>
中T
的轉換,而CallAdapter
則可以對Call
轉換,這樣的話Call<T>
中的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}
示例源碼見 Example08.java
「20160608補充」:像上面的這種情況最後我們無法獲取到返回的Header和響應碼的,如果我們需要這兩者,提供兩種方案:
1、用Observable<Response<T>>``Observable<T>
,這裏的Response
指retrofit2.Response
2、用Observable<Result<T>>
代替Observable<T>
,這裏的Result
是指retrofit2.adapter.rxjava.Result
,這個Result中包含了Response
的實例
5、自定義Converter
本節的內容是教大家實現在一簡易的Converter,這裏以返回格式爲Call<String>
爲例。
在此之前先了解一下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<ResponseBody>
轉換爲 Call<String>
那麼對應的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<String>
還是Observable<String>
有沒有很簡單?如果你有其它的需求處理的就自己實現吧。
示例源碼見 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<String>
爲例。
在此我們需要定義一個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<T>
到
CustomCall<T>
的轉換,這裏需要注意的是最後的泛型,是我們要返回的類型。
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
這四個方法沒有用到,這裏簡單的介紹一下。
方法 | 用途 |
---|---|
callbackExecutor(Executor) | 指定Call.enqueue 時使用的Executor ,所以該設置只對返回值爲Call 的方法有效 |
callFactory(Factory) | 設置一個自定義的okhttp3.Call.Factory ,那什麼是Factory呢?OkHttpClient 就實現了okhttp3.Call.Factory 接口,下面的client(OkHttpClient) 最終也是調用了該方法,也就是說兩者不能共用 |
client(OkHttpClient) | 設置自定義的OkHttpClient ,以前的Retrofit版本中不同的Retrofit 對象共用同OkHttpClient ,在2.0各對象各自持有不同的OkHttpClient 實例,所以當你需要共用OkHttpClient 或需要自定義時則可以使用該方法,如:處理Cookie、使用stetho
調式等 |
validateEagerly(boolean) | 是否在調用create(Class) 時檢測接口定義是否正確,而不是在調用方法才檢測,適合在開發、測試時使用 |
7.2 Retrofit的Url組合規則
從上面不能難看出以下規則:
- 如果你在註解中提供的url是完整的url,則url將作爲請求的url。
- 如果你在註解中提供的url是不完整的url,且不以 / 開頭,則請求的url爲baseUrl+註解中提供的值
- 如果你在註解中提供的url是不完整的url,且以 / 開頭,則請求的url爲baseUrl的主機部分+註解中提供的值
7.3 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 |
7.4 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 |
7.5 關於源碼
看到這兒可能有小夥伴要問爲什麼源碼沒有把類拆分到單獨的文件,命名也不能體現其用途,這裏主要是因爲方便大家看源碼,而不是將注意力放在反覆跳轉上,另一方面也是因爲同一個例子中不可避免的使用其它小節要介紹的內容,所以就直接用了ExampleXX
的形式,不過在項目中千萬不要使用這種方式,一定要好好命名,做到見名知意。
結語
其它本博客的內容早就已經完成好了,但由於當時HTTP、反射、註解的博客一篇也沒有寫,所以一直沒有發,期間也有不少的博主寫了Retrofit2的博文,不過呢沒有自定義相關的內容也沒有對各個註解進行詳解,所以我還是決定發出來幫助一下那此對Retrofit2無從下手同鞋。
這次Retrofit2的內容就到這裏啦,下次再見。
原文鏈接:http://www.jianshu.com/p/308f3c54abdd
著作權歸作者所有,轉載請聯繫作者獲得授權,並標註“簡書作者”。