相信作過Android網絡請求的同窗都繞不開Volley,Retrofit,OkHttp這幾座大山,至於他們的前世姻緣以及孰優孰劣,不在本博客的討論範圍。如題,這篇博客主要介紹一個小白(其實就是我本身)的Retrofit2進階之路,會結合一個開發實例介紹5節內容:html
Call<T>
響應結果的處理問題先來回顧一下Retrofit2
在項目中的完整使用流程:建立Bean類 --> 建立接口形式的http
請求方法 --> 經過Retrofit.builder()
建立接口對象並調用http
方法請求網絡數據 --> 在RxJava
的Observable
(被觀察者)中異步處理請求結果!java
那麼Retrofit2 Http 請求方法註解有那麼多字段,都表明什麼含義呢?添加請求頭或者大文件上傳的請求方法該怎麼寫呢?這將在第二節介紹。另外,Retrofit2基本用法的網絡響應結果是一個Call<T>
,那麼怎樣在Android中解析Call<T>
呢?將在第二節介紹。第三節根據Retrofit2使用流程介紹了一個實踐項目是怎樣使用Retrofit2+RxJava
作網絡請求。第四節和四五節是Retrofit實現一些複雜需求的必殺技,介紹了自定義OkHttp Interceptor實現日誌輸出,保存和添加Cookie;自定義ResponseConverter,自定義HTTP請求註解等內容。react
從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); }
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 }
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 請求:
addCallAdapterFactory(rxJavaCallAdapterFactory)
方法指定使用RxJava
做爲CallAdapter
,須要傳入一個RxJavaCallAdapterFactory
對象:CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create()
;addConverterFactory(gsonConverterFactory)
方法指定 Gson
做爲解析Json數據的Converter
:Converter.Factory gsonConverterFactory = GsonConverterFactory.create()
;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
方法請求網絡數據 --> 在RxJava
的Observable
中異步處理請求結果!
在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!
默認狀況下,Retrofit會把HTTP響應體反序列化到OkHttp的ResponseBody
中,加入Converter能夠將返回的數據直接格式化成你須要的樣子,Retrofit提供了以下6個Converter能夠直接使用,使用前須要加上相應的Gradle依賴:
在前面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
來實現的,重寫了responseBodyConverter
和 requestBodyConverter
這兩個方法,代碼只有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請求接口GuokrService
中getGuokrs()
方法返回值的泛型類型,若是返回值類型是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請求註解的作法!
有一個結論說的是在網絡上,只有 1% 的用戶貢獻了內容,10% 的用戶比較活躍,會評論和點贊,剩下的都是網絡透明人,他們只是默默地在看,既不貢獻內容,也不點贊。這篇文章但願能讓你成爲網絡上貢獻內容的 TOP 1%。若是暫時作不到,那就先點個贊吧,成爲活躍的 10%。
參考文獻