Retrofit2完全教程

御女三千 8年前發布 | 9K 次閱讀 Retrofit Android開發 移動開發

本文中的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和響應碼的,如果我們需要這兩者,提供兩種方案:

  1. 用Observable >``Observable ,這里的Response指retrofit2.Response
  2. 用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/

 

 本文由用戶 御女三千 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!