你真的會用Retrofit2嗎?Retrofit2徹底教程

本文注目錄:java

  • 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,有興趣的能夠去試試。git

接口列表:github

地址 請求方法 參數 說明
/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同理。json

前面寫了你應該知道的HTTP基礎知識 介紹了HTTP的相關知識,不知道那些想了解Retrofit的同鞋是否是去看了Retrofit的官方教程,曾經我在你真的會用Gson嗎?Gson使用指南(四) 中說當你瞭解了註解、反射、泛型、HTTP的內容只須要看一篇Retrofit的代碼示例就能夠輕鬆玩轉Retrofit,不知道你玩轉了沒?
固然註解、反射、泛型的內容尚未寫,Retrofit的內容卻先來了!畢竟看懂Retrofit也只須要會使就行,你準備好了嗎?數組

一、Retrofit入門

Retrofit 其實至關簡單,簡單到源碼只有37個文件,其中22個文件是註解還都和HTTP有關,真正暴露給用戶的類並很少,因此我看了一遍 官方教程 大多數情景就能夠無障礙使用,若是你尚未看過,能夠先去看看,雖然是英文,但代碼纔是最好的教程不是麼?固然本篇文章會介紹得詳細一點,不能寫一篇水文,畢竟我給它命名爲《你真的會用Retrofit2嗎?Retrofit2徹底教程》。服務器

1.一、建立Retrofit實例

Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .build();

建立Retrofit實例時須要經過Retrofit.Builder,並調用baseUrl方法設置URL。
注: Retrofit2 的baseUlr 必須以 /(斜線) 結束,否則會拋出一個IllegalArgumentException,因此若是你看到別的教程沒有以 / 結束,那麼多半是直接從Retrofit 1.X 照搬過來的。ide

1.二、接口定義

以獲取指定id的Blog爲例:post

public interface BlogService { @GET("blog/{id}") Call<ResponseBody> getBlog(@Path("id") int id); }

注意,這裏是interface不是class,因此咱們是沒法直接調用該方法,咱們須要用Retrofit建立一個BlogService的代理對象。測試

BlogService service = retrofit.create(BlogService.class);

拿到代理對象以後,就能夠調用該方法啦。gradle

1.三、接口調用

Call<ResponseBody> call = service.getBlog(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

二、Retrofit註解詳解

上面提到Retrofit 共22個註解,這節就專門介紹這22個註解,爲幫助你們更好理解我將這22個註解分爲三類,並用表格的形式展示出來,表格上說得並不完整,具體的見源碼上的例子註釋。

第一類:HTTP請求方法


HTTP請求方法註解


以上表格中的除HTTP之外都對應了HTTP標準中的請求方法,而HTTP註解則能夠代替以上方法中的任意一個註解,有3個屬性:methodpath,hasBody,下面是用HTTP註解實現上面 Example01.java 的例子。

public interface BlogService { /** * method 表示請求的方法,區分大小寫 * path表示路徑 * hasBody表示是否有請求體 */ @HTTP(method = "GET", path = "blog/{id}", hasBody = false) Call<ResponseBody> getBlog(@Path("id") int id); }

注:method 的值 retrofit 不會作處理,因此要自行保證其準確性,以前使用小寫也能夠是由於示例源碼中的服務器不區分大小寫,因此但願你們注意,感謝 @言過祺實 發現該問題。
示例源碼見 Example02.java

第二類:標記類


標記類註解


示例源碼見 Example03.java

第三類:參數類


參數類註解

注1:{佔位符}和PATH儘可能只用在URL的path部分,url中的參數使用QueryQueryMap 代替,保證接口定義的簡潔
注2:QueryFieldPart這三者都支持數組和實現了Iterable接口的類型,如ListSet等,方便向後臺傳遞數組。

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

三、Gson與Converter

在默認狀況下Retrofit只支持將HTTP的響應體轉換換爲ResponseBody,
這也是什麼我在前面的例子接口的返回值都是 Call<ResponseBody>
但若是響應體只是支持轉換爲ResponseBody的話何須要引用泛型呢,
返回值直接用一個Call就好了嘛,既然支持泛型,那說明泛型參數能夠是其它類型的,
Converter就是Retrofit爲咱們提供用於將ResponseBody轉換爲咱們想要的類型,
有了Converter以後咱們就能夠寫把咱們的第一個例子的接口寫成這個樣子了:

public interface BlogService { @GET("blog/{id}") Call<Result<Blog>> getBlog(@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使用指南》 系列。

四、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和響應碼的,若是咱們須要這二者,提供兩種方案:
一、用Observable<Response<T>>``Observable<T> ,這裏的Responseretrofit2.Response
二、用Observable<Result<T>> 代替Observable<T>,這裏的Result是指retrofit2.adapter.rxjava.Result,這個Result中包含了Response的實例

五、自定義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則分別對應ResponseBodyString,咱們定義一個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

六、自定義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();

注: addCallAdapterFactoryaddConverterFactory同理,也有前後順序。

示例源碼見 Example10.java

七、其它說明

7.1 Retrofit.Builder

前面用到了 Retrofit.Builder 中的baseUrladdCallAdapterFactoryaddConverterFactorybuild方法,還有callbackExecutorcallFactoryclientvalidateEagerly這四個方法沒有用到,這裏簡單的介紹一下。

方法 用途
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組合規則

BaseUrl 和URL有關的註解中提供的值 最後結果
http://localhost:4567/path/to/other/ /post http://localhost:4567/post
http://localhost:4567/path/to/other/ post http://localhost:4567/path/to/other/post
http://localhost:4567/path/to/other/ https://github.com/ikidou https://github.com/ikidou

從上面不能難看出如下規則:

  • 若是你在註解中提供的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的內容就到這裏啦,下次再見。

相關文章
相關標籤/搜索