Retrofit 入門 基本使用 總結 MD


Markdown版本筆記 我的GitHub首頁 我的博客 我的微信 我的郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

Retrofit

入門

Retrofit 源碼只有37個文件,其中22個文件是注解,還都和HTTP有關,真正暴露給用戶的類並不多,看一遍 官方教程 ,大多數情景就可以無障礙使用。

創建Retrofit實例

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

Retrofit2 的baseUlr 必須以 /結束,不然會拋出一個IllegalArgumentException。

接口定義

public interface BlogService {
    @GET("blog/{id}")/*這里的{id} 表示是一個變量*/
    Call<ResponseBody> getBlog(@Path("id") int id);
}

注意,這里是interface不是class,所以我們是無法直接調用該方法,我們需要用Retrofit創建一個BlogService的代理對象

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

接口調用
拿到代理對象之后,就可以調用該方法啦

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());
            System.out.println(response.code() + "  " + response.isSuccessful());//200  true
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        t.printStackTrace();
    }
});

Retrofit注解

Retrofit 共22個注解,為幫助大家更好理解,我將這22個注解分為三類,並用表格的形式展現出來。

8個HTTP請求方法注解

以上表格中的除HTTP以外都對應了HTTP標准中的請求方法,而HTTP注解則可以代替以上方法中的任意一個注解。
有3個屬性:

  • method 表示請求的方法,區分大小寫
  • path 表示路徑
  • hasBody 表示是否有請求體,默認為false,此時可以不寫此參數
public interface BlogService {
    @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getBlog(@Path("id") int id);
}

注:method 的值 retrofit 不會做處理,所以要自行保證其准確性,包括大小寫,示例源碼使用小寫也可以是因為示例源碼中的服務器不區分大小寫

3個標記類注解

11個參數類注解

注1:{占位符}和PATH盡量只用在URL的path部分,url中的參數使用Query和QueryMap代替,保證接口定義的簡潔
注2:Query、Field和Part這三者都支持數組和實現了Iterable接口的類型,如List,Set等,方便向后台傳遞數組。

Gson與Converter

在默認情況下Retrofit只支持將HTTP的響應體轉換換為ResponseBody,但如果響應體只是支持轉換為ResponseBody的話何必要引入泛型呢,返回值直接用一個Call就行了嘛。既然支持泛型,那說明泛型參數可以是其它類型的。

Converter就是Retrofit為我們提供用於將ResponseBody轉換為我們想要的類型,例如上面例子的接口可以寫成這個樣子:

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.1.0'

通過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/")
        .addConverterFactory(GsonConverterFactory.create(gson))//可以接收自定義的Gson,也可以不傳
        .build();

這樣Retrofit就會使用Gson將ResponseBody轉換成我們想要的類型。

Gson 反序列化案例

Call<Result<Blog>> call = retrofit.create(BlogService.class).getBlog(2);
call.enqueue(new Callback<Result<Blog>>() {
    @Override
    public void onResponse(Call<Result<Blog>> call, Response<Result<Blog>> response) {
        Result<Blog> result = response.body();//已經轉換為想要的類型了
        System.out.println(result);
    }

    @Override
    public void onFailure(Call<Result<Blog>> call, Throwable t) {
        t.printStackTrace();
    }
});

Gson 序列化案例
下面演示如使創建一個Blog!

@POST("blog")
Call<Result<Blog>> createBlog(@Body Blog blog);

@Body注解的的Blog將會被Gson轉換成RequestBody發送到服務器。

Blog blog = new Blog("author:包青天", "title:測試", "content:新建的Blog");
Call<Result<Blog>> call = retrofit.create(BlogService.class).createBlog(blog);

RxJava與CallAdapter

上一節介紹的Converter是對於Call<T>中T的轉換,而CallAdapter則可以對Call轉換,這樣的話返回值的類型就決定你后續的處理程序邏輯,同樣Retrofit提供了多個CallAdapter,這里以RxJava的為例,用Observable代替Call。

引入RxJava支持:

compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
//compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'//針對rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)

通過RxJavaCallAdapterFactory為Retrofit添加RxJava支持:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://localhost:4567/")
    .addConverterFactory(GsonConverterFactory.create(gson))
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    //.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//針對rxjava2.x
    .build();

接口設計:

public interface BlogService {
    @GET("/blog")
    Observable<Result<List<Blog>>> getBlogs(@Query("page") int page);
    //如果需要Header的值,可以把返回值替換為 Observable<Response<Result<List<Blog>>>>
}

使用:

retrofit.create(BlogService.class)
  .getBlogs(1)
  .subscribeOn(Schedulers.io())
  .subscribe(blogsResult -> System.out.println(blogsResult)); //Result<List<Blog>>

像上面的這種情況最后我們無法獲取到返回的Header和響應碼的,如果我們需要這兩者,提供兩種方案:

  • Observable<retrofit2.Response<T>>代替 Observable<T>
  • Observable<retrofit2.adapter.rxjava.Result<T>>代替 Observable<T>,這個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則分別對應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;
        return null;//其它類型我們不處理,返回null就行
    }
}

使用Retrofit.Builder.addConverterFactory向Retrofit注冊我們StringConverterFactory:

Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      .addConverterFactory(StringConverterFactory.create())// 如是有Gson這類的Converter一定要放在其它前面
      .addConverterFactory(GsonConverterFactory.create())
      .build();

注:addConverterFactory是有先后順序的,如果有多個ConverterFactory都支持同一種類型,那么就是只有第一個才會被使用,而GsonConverterFactory是不判斷是否支持的,所以這里交換順序會有異常拋出,原因是類型不匹配。

只要返回值類型的泛型參數就會由我們的StringConverter處理,不管是Call 還是Observable

自定義CallAdapter

本節將介紹如何自定一個CallAdapter,並驗證是否所有的String都會使用我們上一節中自定義的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) {
    return new CustomCall<>(call);// 由 CustomCall 決定如何使用
  }
}

提供一個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同理,也有先后順序。

其它說明

Retrofit.Builder的其他方法

Retrofit.Builder中的所有方法:

baseUrl(String baseUrl) 和 baseUrl(okhttp3.HttpUrl baseUrl)
addConverterFactory(retrofit2.Converter.Factory factory)//對Call<T>中T的進行類型轉換
addCallAdapterFactory(retrofit2.CallAdapter.Factory factory)//對Call進行轉換
callbackExecutor(java.util.concurrent.Executor executor)
callFactory(okhttp3.Call.Factory factory)
client(OkHttpClient client)
validateEagerly(boolean validateEagerly)
  • callbackExecutor(Executor):指定Call.enqueue時使用的Executor,所以該設置只對返回值為Call的方法有效
  • callFactory(Factory):設置一個自定義的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就實現了此接口。如果你需要對okhttpclient進行詳細的設置,需要構建OkHttpClient對象,然后通過callFactory方法傳入,否則new一個默認的OkHttpClient。下面的client方法最終也是調用了該方法,所有兩者不能共用
  • client(OkHttpClient):設置自定義的OkHttpClient,以前的Retrofit版本中,不同的Retrofit對象共同OkHttpClient,在2.0后,各對象各自持有不同的OkHttpClient實例,所以當你需要共用OkHttpClient或需要自定義時則可以使用該方法,如:處理Cookie、使用stetho調式等
  • validateEagerly(boolean):是否在調用create(Class)時檢測接口定義是否正確,而不是在調用方法才檢測。適合在開發、測試時使用

很多時候,比如你使用Retrofit需要統一的log管理,給每個請求添加統一的header等,這些都應該通過OkHttpClient去操作。你可以單獨寫一個OkHttpClient的單例生成類,在這個里面完成你所需的所有的配置,然后通過Retrofit.Builder的callFactory方法設置給retrofit。

Retrofit的Url組合規則

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

  • 如果你在注解中提供的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

如何運行項目

測試接口服務器在 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是顯示全部

2017-9-8

附件列表

     


    免責聲明!

    本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



     
    粵ICP備18138465號   © 2018-2025 CODEPRJ.COM