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


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


0X00 寫在前面


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

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

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

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

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


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

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

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請求參數注解字段說明 這篇博客介紹的比較詳細可作參考:

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 網絡請求也是適用的。

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

如果你想異步的執行網絡請求,最簡單的就是在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


免責聲明!

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



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