Retrofit 2使用要點梳理:小白進階回憶錄

本博客爲做者原創,如需轉載請註明原博客出處:http://www.cnblogs.com/wondertwo/p/5838528.html(博客園)/http://www.jianshu.com/p/dd2804030b89(簡書)


0X00 寫在前面


相信作過Android網絡請求的同窗都繞不開Volley,Retrofit,OkHttp這幾座大山,至於他們的前世姻緣以及孰優孰劣,不在本博客的討論範圍。如題,這篇博客主要介紹一個小白(其實就是我本身)的Retrofit2進階之路,會結合一個開發實例介紹5節內容:html

  • Retrofit2 HTTP請求方法註解的字段說明
  • Call<T>響應結果的處理問題
  • Retrofit2+RxJava實現開發效率最大化
  • 自定義OkHttp Interceptor實現日誌輸出,保存和添加Cookie
  • 自定義ResponseConverter,自定義HTTP請求註解

先來回顧一下Retrofit2在項目中的完整使用流程:建立Bean類 --> 建立接口形式的http請求方法 --> 經過Retrofit.builder()建立接口對象並調用http方法請求網絡數據 --> 在RxJavaObservable(被觀察者)中異步處理請求結果!java

那麼Retrofit2 Http 請求方法註解有那麼多字段,都表明什麼含義呢?添加請求頭或者大文件上傳的請求方法該怎麼寫呢?這將在第二節介紹。另外,Retrofit2基本用法的網絡響應結果是一個Call<T> ,那麼怎樣在Android中解析Call<T> 呢?將在第二節介紹。第三節根據Retrofit2使用流程介紹了一個實踐項目是怎樣使用Retrofit2+RxJava 作網絡請求。第四節和四五節是Retrofit實現一些複雜需求的必殺技,介紹了自定義OkHttp Interceptor實現日誌輸出,保存和添加Cookie;自定義ResponseConverter,自定義HTTP請求註解等內容。react

0X01 Retrofit2 HTTP請求方法註解的字段說明


從Retrofit2的官方文檔來看,Retrofit2 進行網絡請求的URL分爲兩部分:BaseURL和relativeURL。BaseURL須要以/ 結尾, 通常不須要變化直接定義便可,固然在特殊的狀況下,好比後一次網絡訪問URL須要從前一次訪問結果中獲取相關參數,那麼就須要動態的操做URL,這種用法會在第五節進行介紹;relativeURL與每次請求的參數相關,因此每一個request 方法都須要 http annotation 來提供請求的relativeURL,Retrofit2內置的註解有五個:GET, POST, PUT, DELETE, and HEAD. 這些註解在使用時涉及到哪些相關的字段呢?我從參考文獻的博客中引用了一張圖:android

能夠看到,有URL請求參數,Query參數這些簡單網絡請求參數;同時還支持用@Header添加請求頭;POST請求中經常使用@FormUrlEncoded提交表單,並用@Field定義表單域;@MultiPart文件上傳並用@Part定義請求體。來看一個具體的例子(摘自Retrofit2官方文檔):git

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

Retrofit2把網絡請求定義成接口的形式,如上是一個GET請求,@Path表示一個佔位符,@Path中的變量必須與@GET變量中{} 中間的部分一致。下面是一個POST請求,@FormUrlEncoded用於提交一個表單,@Field定義了表單的name和value。更多詳細的用法詳見Retrofit2官方文檔API Declaration ,另外Retrofit請求參數註解字段說明 這篇博客介紹的比較詳細可做參考:github

public interface GitHubService {
    @FormUrlEncoded
    @POST("user/edit")
    Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
}

0X02 Call<T> 響應結果的處理


細心的你有木有發現,發現官方文檔中給出的請求方法示例,返回結果都是 Call<List<User>> 這種形式?沒錯!這就是Retrofit2最原始的網絡請求用法,官方文檔上介紹的很簡潔,能夠在 call<T> 響應對象上作異步或者同步的操做,每一個 call<T> 對象只能用一次,要想屢次使用能夠調用 clone() 方法來克隆出多個 call 對象以供更多操做使用。由於Retrofit2 是一個類型安全的Java和Android網絡請求庫,因此以上的操做對 Java 網絡請求也是適用的。json

針對JVM而言,網絡請求和結果處理會放在同一個線程中執行,那麼在Android中,咱們怎樣處理請求結果對象 call 呢?官方文檔也給出了答案,咱們都知道Android中網絡請求這類耗時操做都是放在工做線程(即worker thread)來執行的,而後在主線程(也即 UI thread)處理網絡請求結果,天然Retrofit2也不例外,因爲Retrofit2拋棄了飽受詬病的Apache HttpClient底層只依賴OkHttp3.0,網絡訪問層的操做都會交由OkHttp來完成,而OkHttp不只擁有自動維護的socket鏈接池,減小握手次數,並且還擁有隊列線程池,能夠輕鬆寫併發,同時還支持socket自動選擇最好路線,並支持自動重連,OkHttp的優勢遠不止於此,因此Retrofit2選擇用OkHttp做爲網絡請求執行器是一個再明智不過的決定。api

若是你想異步的執行網絡請求,最簡單的就是在Activity或者Fragment中View控件的監聽器中進行網絡訪問,並經過call.enqueue()處理請求結果,並更新UI,下面是一個小demo:緩存

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    GitHubService gitHubService = GitHubService.retrofit.create(GitHubService.class);
    final Call<List<Contributor>> call =
            gitHubService.repoContributors("square", "retrofit");

    call.enqueue(new Callback<List<Contributor>>() {
        @Override
        public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
            final TextView textView = (TextView) findViewById(R.id.textView);
            textView.setText(response.body().toString());
        }
        @Override
        public void onFailure(Call<List<Contributor>> call, Throwable t) {
            final TextView textView = (TextView) findViewById(R.id.textView);
            textView.setText("Something went wrong: " + t.getMessage());
        }
    });
  }
});

若是你須要在工做線程中執行網絡請求,而不是在一個Activity或者一個Fragment中去執行,那麼也就意味着,你能夠在同一個線程中同步的去執行網絡請求,使用call.execute()方法來處理請求結果便可,代碼以下:安全

try {
  Response<User> response = call.execute();
} catch (IOException e ){
   // handle error
}

0X03 Retrofit2+RxJava實現開發效率最大化


Retorfit是支持RxJava,Guava,Java8 等等一系列擴展的,關於RxJava這個網紅我就不作介紹了,RactiveX項目對 JVM 的擴展,你能夠把它當作一個超級強大的異步事件處理庫,可他的NB之處遠不止於此,至少作Android的都應該聽過他的鼎鼎大名,不熟悉的能夠去看看RxJava Wiki!!而這裏Retrofit2+RxJava組合就能夠實現開發效率的大幅提高,至於怎樣提高的?對比一下你之前寫的網絡請求的代碼量就知道了!結合一個實踐項目的源碼來分析,這裏是請求果殼網最新的100條文章數據,返回結果爲Json,首先build.gradle 添加依賴:

compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid
    compile 'io.reactivex:rxjava:1.1.0' // 推薦同時添加RxJava
    compile 'com.squareup.retrofit2:retrofit:2.1.0' // Retrofit網絡處理
    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' // Retrofit的rx解析庫
    compile 'com.squareup.retrofit2:converter-gson:2.1.0' // Retrofit的gson庫

    compile 'com.squareup.okhttp3:okhttp:3.2.0' // OkHttp3

第一步,定義服務器Json數據對應的POJO類,這裏咱們能夠偷一下懶能夠直接經過jsonschema2pojo 這個網站自動生成POJO類,就不用咱們手動去寫了,而後copy到項目目錄的bean包下。接着即是定義HTTP請求方法了,以接口的形式定義,以下:

// 服務器數據對應的實體類
public class Guokr {
    // 定義序列化後的名字
    public @SerializedName("ok") Boolean response_ok;
    // 定義序列化後的名字
    public @SerializedName("result") List<GuokrResult> response_results;

    public static class GuokrResult {
        public int id;
        public String title;

        public String headline_img_tb; // 用於文章列表頁小圖
        public String headline_img; // 用於文章內容頁大圖

        public String link;
        public String author;
        public String summary;
    }
}

// HTTP請求方法
public interface GuokrService {

    @GET("handpick/article.json")
    Observable<Guokr> getGuokrs(@Query("retrieve_type") String type,
                                @Query("category") String category,
                                @Query("limit") int limit,
                                @Query("ad") int ad);

}

其中 Observable<Guokr> 是RxJava中的被觀察者,對應請求結果Call<T>。只是由於Retrofit提供了很是強大的CallAdapterFactory 完美兼容了RxJava 這個超級大網紅,才致使咱們日常看到的寫法是這樣的。第二步, 須要經過Retrofit.builder() 建立 GuokrService 接口對象,經過接口對象執行 getGuokrs 方法進行網絡訪問,代碼以下:

// 封裝 GuokrService 請求
    public static GuokrService getGuokrService() {
        if (guokrService == null) {
            Retrofit retrofit = new Retrofit.Builder()
                    .client(mClient)
                    .baseUrl("http://apis.guokr.com/")
                    .addCallAdapterFactory(rxJavaCallAdapterFactory)
                    .addConverterFactory(gsonConverterFactory)
                    .build();
            guokrService = retrofit.create(GuokrService.class);
        }
        return guokrService;
    }

// 默認加載最新的100條數據
subscription = RetrofitClient.getGuokrService().getGuokrs("by_since", "all", 100, 1)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<Guokr>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "--------completed-------");
            }

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, "--------error-------");
                Log.e(TAG, e.getMessage());
            }

            @Override
            public void onNext(Guokr guokr) {
                if (guokr.response_ok) {
                    List<Guokr.GuokrResult> guokrResults = guokr.response_results;
                    List<GuokrItem> guokrItems = new ArrayList<>(guokrResults.size());
                    for (Guokr.GuokrResult result : guokrResults) {
                        GuokrItem item = new GuokrItem();
                        item.headline_img_tb = result.headline_img_tb;
                        item.title = result.title;
                        item.id = result.id;
                        item.headline_img = result.headline_img;
                        item.summary = result.summary;
                        guokrItems.add(item);
                    }
                    mAdapter.addAll(guokrItems);
                    mAdapter.notifyDataSetChanged();
                });

注意到封裝 GuokrService 請求:

  1. addCallAdapterFactory(rxJavaCallAdapterFactory) 方法指定使用RxJava 做爲CallAdapter ,須要傳入一個RxJavaCallAdapterFactory對象:CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create()
  2. addConverterFactory(gsonConverterFactory) 方法指定 Gson 做爲解析Json數據的ConverterConverter.Factory gsonConverterFactory = GsonConverterFactory.create()
  3. client(mClient)方法指定網絡執行器爲OkHttp 以下建立一個默認的OkHttp對象傳入便可:OkHttpClient mClient = new OkHttpClient()

而加載網絡數據這個鏈式調用就是RxJava最大的特點,用在這裏邏輯就是,被觀察者Observable<Guokr>訂閱觀察者Observer<Guokr>,當服務器一有response,觀察者就會當即處理response result。由於RxJava最大的亮點就是異步,能夠很方便的切換當前任務所在的線程,並能對事件流進行各類Map變換,好比壓合、轉換、緩存等操做。這裏是最基本的用法,被觀察者直接把事件流訂閱到觀察者,中間沒有作轉換處理。

到此網絡訪問就完成了,是否是很簡潔?簡潔就對了,那是由於太多東西Retrofit2和RxJava甚至是OkHttp都幫咱們作好了!再回顧一下整個網絡訪問流程:建立Bean類 --> 建立接口形式的http 請求方法 --> 經過Retrofit.builder() 建立接口對象並調用http 方法請求網絡數據 --> 在RxJavaObservable 中異步處理請求結果!

0X04 自定義OkHttp Interceptor實現日誌輸出,保存和添加Cookie


在Retrofit2作網絡請求的第二步,咱們須要經過Retrofit.builder()方法來建立Retrofit對象,其中client(mClient)這個方法指定一個OkHttpClient客戶端做爲請求的執行器,須要傳入一個OkHttpClient對象做爲參數,那麼在這裏,咱們就能夠進行一些OkHttp相關的操做,好比自定義Interceptor,經過自定義Interceptor能夠實現網絡請求日誌的分級輸出,能夠實現保存和添加Cookie這些功能,固然,這些功能的實現都是基於OkHttp,因此要對OkHttp有必定的瞭解才能靈活運用。

Retrofit使用指南-->OkHttp配合Retrofit使用 這篇博客在OkHttp配合Retrofit使用這一節,關於OkHttpClient添加HttpLoggingInterceptor 進行日誌輸出,以及如何設置SslSocketFactory作了詳細的說明,有興趣的同窗能夠參考!值得注意的是,若是後一次請求的URL,須要從前一次請求結果數據中獲取,這時候就須要動態的改變BaseURL,也可經過自定義Interceptor 來實現這一需求,在BaseURL改變時,只須要setHost()就可讓下次請求的BaseURL改變,代碼以下:

public class DynamicBaseUrlInterceptor implements Interceptor {
    private volatile String host;

    public void setHost(String host) {
        this.host = host;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        if (!TextUtils.isEmpty(host)) {
            HttpUrl newUrl = originalRequest.url().newBuilder()
                    .host(host)
                    .build();
            originalRequest = originalRequest.newBuilder()
                    .url(newUrl)
                    .build();
        }

        return chain.proceed(originalRequest);
    }
}

那麼怎樣在經過OkHttp保存和添加Cookie呢?其實實現原理和上面添加日誌攔截器差很少,只是添加的Intercepter不一樣而已,其實就是自定義了一個Interceptor接口實現類,接收和保存返回結果中的Cookie,或者添加Cookie,最後,在建立OkHttp實例的時候,傳入以上Interceptor實現類的對象便可。Retrofit使用OkHttp保存和添加cookie這篇博客講的很好,能夠做爲參考!

簡而言之,以上這Retorfit2些高級運用都是基於定製化OkHttp來實現的,若是想玩得很溜就必須對OkHttp瞭解一二,推薦看這篇博客OkHttp3源碼分析綜述!最起碼須要弄清楚OkHttpClient自定義Interceptor這一塊內容,推薦看OkHttp Github Wiki --> Interceptors

0X05 自定義ResponseConverter,自定義HTTP請求註解


默認狀況下,Retrofit會把HTTP響應體反序列化到OkHttp的ResponseBody中,加入Converter能夠將返回的數據直接格式化成你須要的樣子,Retrofit提供了以下6個Converter能夠直接使用,使用前須要加上相應的Gradle依賴:

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

在前面Retrofit2+RxJava實例中,咱們指定GsonConverterFactory做爲解析Json數據的Converter,當面對更復雜的需求時,仍然能夠經過繼承Converter.Factory 來自定義Converter,只須要重寫如下這兩個方法便可:

@Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
        //your own implements
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
     Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
       //your own implements
  }

咱們不妨來看看GsonConverterFactory 源碼,果真GsonConverterFactory 也是繼承Converter.Factory 來實現的,重寫了responseBodyConverterrequestBodyConverter 這兩個方法,代碼只有70多行仍是很簡潔的,以下:

public final class GsonConverterFactory extends Converter.Factory {
  /**
   * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
   * decoding from JSON (when no charset is specified by a header) will use UTF-8.
   */
  public static GsonConverterFactory create() {
    return create(new Gson());
  }

  /**
   * Create an instance using {@code gson} for conversion. Encoding to JSON and
   * decoding from JSON (when no charset is specified by a header) will use UTF-8.
   */
  public static GsonConverterFactory create(Gson gson) {
    return new GsonConverterFactory(gson);
  }

  private final Gson gson;

  private GsonConverterFactory(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    this.gson = gson;
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);
  }
}

這裏須要詳細解釋一下TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)) 中的TypeAdapter<?>TypeAdapte是Gson提供的自定義Json解析器,Type就是HTTP請求接口GuokrServicegetGuokrs()方法返回值的泛型類型,若是返回值類型是Call<T>,那麼這裏的Type就是泛型類型 T ,若是返回值類型是Observable<List<Guokr>> ,那麼Type就是List<Guokr>;關於Gson的詳細用法能夠參考:你真的會用Gson嗎?Gson使用指南(四)

咱們看到responseBodyConverter 方法返回的是一個GsonResponseBodyConverter 對象,跟進去看一下GsonResponseBodyConverter 源碼,也很簡單,源碼 以下:

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
}

咱們看到GsonResponseBodyConverter<T> 實現了Converter<ResponseBody, T>,重寫了convert(ResponseBody value) 方法,這就給咱們提供了一個思路:自定義Converter關鍵一步就是要實現Converter<ResponseBody, T> 接口而且重寫convert(ResponseBody value) 方法,具體重寫的代碼我就不貼出來了,能夠參考如何使用Retrofit請求非Restful API 這篇博客自定義Converter的作法!

另外,若是需求更復雜,須要咱們自定義HTTP請求方法的註解,又該怎麼作呢?咱們還注意到GsonConverterFactory 類的重寫方法responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) 中的Annotation[] methodAnnotations 這個參數,對的,或許你已經猜到了,這就是咱們在HTTP請求接口方法中定義的註解,先看 @GET 註解的源碼,以下:

/** Make a GET request. */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  String value() default "";
}

那咱們自定義註解的思路也就有了,模仿上面 @GET 註解寫一個 @WONDERTWO 註解便可。這裏我點到即止,主要是提供一種思路,具體實現仍然能夠參考上面提到的 如何使用Retrofit請求非Restful API 這篇博客自定義HTTP請求註解的作法!

0X06 寫在後面


有一個結論說的是在網絡上,只有 1% 的用戶貢獻了內容,10% 的用戶比較活躍,會評論和點贊,剩下的都是網絡透明人,他們只是默默地在看,既不貢獻內容,也不點贊。這篇文章但願能讓你成爲網絡上貢獻內容的 TOP 1%。若是暫時作不到,那就先點個贊吧,成爲活躍的 10%。

參考文獻

  1. Retrofit官方文檔
  2. Getting started with Retrofit 2
  3. Consuming APIs with Retrofit
  4. Retrofit 2.0: The biggest update yet on the best HTTP Client Library for Android
  5. Retrofit使用指南
  6. Square全家桶正傳——Retrofit使用及配合RxJava實現最大效率開發
  7. Retrofit使用OkHttp保存和添加cookie
  8. 如何使用Retrofit請求非Restful API
  9. Retrofit請求參數註解字段說明
  10. OkHttp-->Interceptors
相關文章
相關標籤/搜索