Android 手把手教你使用Retrofit2

lixuqu 7年前發布 | 27K 次閱讀 Retrofit Android開發 移動開發

Android 手把手教你使用Retrofit2

前言:

1.我想通過本文,讓大家能更快的上手Retrofit2,跟上時代的步伐。

2.本文主要是寫給初級、中級開發人員閱讀,所以在這邊不會做太多的解釋說明,只針對一些坑以及使用方法進行說明。

3.本文只涉及到Retrofit2,并未與RxJava一起使用,RxJava+Retrofit2技術將會在下篇文章中呈現。

4.本人也是剛開始接觸Retrofit2,所以可能文章中會有一些錯誤或者不足,希望大家多多留言探討,共同進步

開啟Retrofit2之旅

添加依賴

以下是我用到的一些依賴

compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    //日志攔截器
    compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
    compile 'io.reactivex:rxjava:1.2.4'
    compile 'io.reactivex:rxandroid:1.2.1'
    compile 'org.ligboy.retrofit2:converter-fastjson-android:2.1.0'

注解

retrofit通過使用注解來簡化請求,大體分為以下幾類:

1.用于標注請求方式的注解

2.用于標記請求頭的注解

3.用于標記請求參數的注解

4.用于標記請求和響應格式的注解

請求方法注解

注解 說明
@GET get請求
@POST post請求
@PUT put請求
@DELETE delete請求
@PATCH patch請求,該請求是對put請求的補充,用于更新局部資源
@HEAD head請求
@OPTIONS option請求
@HTTP 通用注解,可以替換以上所有的注解,其擁有三個屬性:method,path,hasBody

請求頭注解

注解 說明
@Headers 用于添加固定請求頭,可以同時添加多個。通過該注解添加的請求頭不會相互覆蓋,而是共同存在
@Header 作為方法的參數傳入,用于添加不固定值的Header,該注解會更新已有的請求頭

請求參數注解

名稱 說明
@Body 多用于post請求發送非表單數據,比如想要以post方式傳遞json格式數據
@Filed 多用于post請求中表單字段,Filed和FieldMap需要FormUrlEncoded結合使用
@FiledMap 和@Filed作用一致,用于不確定表單參數
@Part 用于表單字段,Part和PartMap與Multipart注解結合使用,適合文件上傳的情況
@PartMap 用于表單字段,默認接受的類型是Map<String,REquestBody>,可用于實現多文件上傳
@Path 用于url中的占位符
@Query 用于Get中指定參數
@QueryMap 和Query使用類似
@Url 指定請求路徑

請求和響應格式注解

名稱 說明
@FormUrlEncoded 表示請求發送編碼表單數據,每個鍵值對需要使用@Field注解
@Multipart 表示請求發送multipart數據,需要配合使用@Part
@Streaming 表示響應用字節流的形式返回.如果沒使用該注解,默認會把數據全部載入到內存中.該注解在在下載大文件的特別有用

使用

這里不對注解進行過多的說明了,只展示幾個用法

/**

  • Created by caihan on 2017/1/11. *
  • @GET 表明這是get請求
  • @POST 表明這是post請求
  • @PUT 表明這是put請求
  • @DELETE 表明這是delete請求
  • @PATCH 表明這是一個patch請求,該請求是對put請求的補充,用于更新局部資源
  • @HEAD 表明這是一個head請求
  • @OPTIONS 表明這是一個option請求
  • @HTTP 通用注解, 可以替換以上所有的注解,其擁有三個屬性:method,path,hasBody
  • @Headers 用于添加固定請求頭,可以同時添加多個。通過該注解添加的請求頭不會相互覆蓋,而是共同存在
  • @Header 作為方法的參數傳入,用于添加不固定值的Header,該注解會更新已有的請求頭
  • @Body 多用于post請求發送非表單數據, 比如想要以post方式傳遞json格式數據
  • @Filed 多用于post請求中表單字段, Filed和FieldMap需要FormUrlEncoded結合使用
  • @FiledMap 和@Filed作用一致,用于不確定表單參數
  • @Part 用于表單字段, Part和PartMap與Multipart注解結合使用, 適合文件上傳的情況
  • @PartMap 用于表單字段, 默認接受的類型是Map<String,REquestBody>,可用于實現多文件上傳
  • <p>
  • Part標志上文的內容可以是富媒體形勢,比如上傳一張圖片,上傳一段音樂,即它多用于字節流傳輸。
  • 而Filed則相對簡單些,通常是字符串鍵值對。
  • </p>
  • Part標志上文的內容可以是富媒體形勢,比如上傳一張圖片,上傳一段音樂,即它多用于字節流傳輸。
  • 而Filed則相對簡單些,通常是字符串鍵值對。
  • @Path 用于url中的占位符,{占位符}和PATH只用在URL的path部分,url中的參數使用Query和QueryMap代替,保證接口定義的簡潔
  • @Query 用于Get中指定參數
  • @QueryMap 和Query使用類似
  • @Url 指定請求路徑 */ public interface MyRetrofit2Service {

    //使用@Headers添加多個請求頭 @Headers({

         "User-Agent:android",
         "apikey:123456789",
    

    }) @POST() Call<HttpResult<News>> post(@Url String url, @QueryMap Map<String, String> map);

    @GET("mobile/active") Call<HttpResult<News>> get(@Header("token") String token, @Query("id") int activeId);

    @GET("active/list") Call<HttpResult<News>> ActiveList();

    @POST("api/news/newsList") Call<HttpResult<News>> post2(@QueryMap Map<String, String> map);

    /**

    • 很多情況下,我們需要上傳json格式的數據。比如當我們注冊新用戶的時候,因為用戶注冊時的數據相對較多,
    • 并可能以后會變化,這時候,服務端可能要求我們上傳json格式的數據。此時就要@Body注解來實現。
    • 直接傳入實體,它會自行轉化成Json *
    • @param url
    • @param post
    • @return */ @POST("api/{url}/newsList") Call<HttpResult<News>> login(@Path("url") String url, @Body News post);

      /**

    • 單張圖片上傳
    • retrofit 2.0的上傳和以前略有不同,需要借助@Multipart注解、@Part和MultipartBody實現。 *
    • @param url
    • @param file
    • @return */ @Multipart @POST("{url}") Call<HttpResult<News>> upload(@Path("url") String url, @Part MultipartBody.Part file);

      /**

    • 多張圖片上傳 *
    • @param map
    • @return */ @Multipart @POST("upload/upload") Call<HttpResult<News>> upload(@PartMap Map<String, MultipartBody.Part> map);

      /**

    • 圖文混傳 *
    • @param post
    • @param map
    • @return */ @Multipart @POST("") Call<HttpResult<News>> register(@Body News post, @PartMap Map<String, MultipartBody.Part> map);

      /**

    • 文件下載 *
    • @param fileUrl
    • @return */ @Streaming @GET Call<HttpResult<News>> downloadPicture(@Url String fileUrl);

      /**

    • 這里需要注意的是如果下載的文件較大,比如在10m以上,那么強烈建議你使用@Streaming進行注解,否則將會出現IO異常. *
    • @param fileUrl
    • @return */ @Streaming @GET Observable<HttpResult<News>> downloadPicture2(@Url String fileUrl);

      @POST() @FormUrlEncoded Observable<HttpResult<News>> executePost(@FieldMap Map<String, Object> maps); }</code></pre>

      踩坑

      1.url被轉義

      http://api.mydemo.com/api%2Fnews%2FnewsList?

      罪魁禍首@Url與@Path注解,我們開發過程中,肯定會需要動態的修改請求地址

      兩種動態修改方式如下:

      @POST()
      Call<HttpResult<News>> post(@Url String url, @QueryMap Map<String, String> map);

      @POST("api/{url}/newsList") Call<HttpResult<News>> login(@Path("url") String url, @Body News post);</code></pre>

      第一種是直接使用@Url,它相當于直接替換了@POST()里面的請求地址

      第二種是使用@Path("url"),它只替換了@POST("api/{url}/newsList")中的{url}

      如果你用下面這樣寫的話,就會出現url被轉義

      @POST("{url}")
      Call<HttpResult<News>> post(@Path("url") String url);

      你如果執意要用@Path,也不是不可以,需要這樣寫

      @POST("{url}")
      Call<HttpResult<News>> post(@Path(value = "url", encoded = true) String url);

      OkHttpClient

      攔截器

      addNetworkInterceptor添加的是網絡攔截器Network Interfacetor它會在request和response時分別被調用一次;

      addInterceptor添加的是應用攔截器Application Interceptor他只會在response被調用一次。

      1.日志攔截器

      使用 addNetworkInterceptor() 方法添加到 OkHttpClient

      日志攔截器我這有兩種創建方式:

      一種是使用HttpLoggingInterceptor,需要使用到依賴

      compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
      /**

    • 創建日志攔截器 *
    • @return */ public static HttpLoggingInterceptor getHttpLoggingInterceptor() { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(

           new HttpLoggingInterceptor.Logger() {
      
               @Override
               public void log(String message) {
                   Log.e("OkHttp", "log = " + message);
               }
      
           });
      

      loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return loggingInterceptor; }</code></pre>

      另一種是

      /**

    • 創建日志攔截器 *
    • @return */ private class LogInterceptor implements Interceptor { @Override public okhttp3.Response intercept(Chain chain) throws IOException {

       Request request = chain.request();
      
       long t1 = System.nanoTime();
       Log.d("OkHttp", "HttpHelper1" + String.format("Sending request %s on %s%n%s",
               request.url(), chain.connection(), request.headers()));
      
       okhttp3.Response response = chain.proceed(request);
       long t2 = System.nanoTime();
      
       Log.d("OkHttp", "HttpHelper2" + String.format("Received response for %s in %.1fms%n%s",
               response.request().url(), (t2 - t1) / 1e6d, response.headers()));
       return response;
      

      } }</code></pre>

      2.請求頭攔截器

      使用 addInterceptor() 方法添加到 OkHttpClient

      我的理解是,請求頭攔截器是為了讓服務端能更好的識別該請求,服務器那邊通過請求頭判斷該請求是否為有效請求等...

      /**

    • 攔截器Interceptors
    • 使用addHeader()不會覆蓋之前設置的header,若使用header()則會覆蓋之前的header *
    • @return */ public static Interceptor getRequestHeader() { Interceptor headerInterceptor = new Interceptor() {

       @Override
       public okhttp3.Response intercept(Chain chain) throws IOException {
           Request originalRequest = chain.request();
           Request.Builder builder = originalRequest.newBuilder();
           builder.addHeader("version", "1");
           builder.addHeader("time", System.currentTimeMillis() + "");
      
           Request.Builder requestBuilder = builder.method(originalRequest.method(), originalRequest.body());
           Request request = requestBuilder.build();
           return chain.proceed(request);
       }
      
      

      }; return headerInterceptor; }</code></pre>

      3.統一請求攔截器

      使用 addInterceptor() 方法添加到 OkHttpClient

      統一請求攔截器的功能跟請求頭攔截器相類似

      /**

    • 攔截器Interceptors
    • 統一的請求參數 */ private Interceptor commonParamsInterceptor() { Interceptor commonParams = new Interceptor() {

       @Override
       public okhttp3.Response intercept(Chain chain) throws IOException {
           Request originRequest = chain.request();
           Request request;
           HttpUrl httpUrl = originRequest.url().newBuilder().
                   addQueryParameter("paltform", "android").
                   addQueryParameter("version", "1.0.0").build();
           request = originRequest.newBuilder().url(httpUrl).build();
           return chain.proceed(request);
       }
      

      };

      return commonParams; }</code></pre>

      4.時間攔截器

      使用 addInterceptor() 方法添加到 OkHttpClient

      從響應中獲取服務器返回的時間

      /**

    • 攔截器Interceptors
    • 通過響應攔截器實現了從響應中獲取服務器返回的time *
    • @return */ public static Interceptor getResponseHeader() { Interceptor interceptor = new Interceptor() {

       @Override
       public okhttp3.Response intercept(Chain chain) throws IOException {
           okhttp3.Response response = chain.proceed(chain.request());
           String timestamp = response.header("time");
           if (timestamp != null) {
               //獲取到響應header中的time
           }
           return response;
       }
      

      }; return interceptor; }</code></pre>

      緩存

      使用okhttp緩存的話,先要創建Cache,然后在創建緩存攔截器

      //創建Cache
       File httpCacheDirectory = new File(MyApp.getContext().getCacheDir(), "OkHttpCache");
       Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
       httpClientBuilder.cache(cache);
       //設置緩存
       httpClientBuilder.addNetworkInterceptor(getCacheInterceptor2());
       httpClientBuilder.addInterceptor(getCacheInterceptor2());

      緩存攔截器

      緩存時間自己根據情況設定

      /**

    • 在無網絡的情況下讀取緩存,有網絡的情況下根據緩存的過期時間重新請求, *
    • @return */ public Interceptor getCacheInterceptor2() { Interceptor commonParams = new Interceptor() {

       @Override
       public okhttp3.Response intercept(Chain chain) throws IOException {
           Request request = chain.request();
           if (!NetworkUtils.isConnected()) {
               //無網絡下強制使用緩存,無論緩存是否過期,此時該請求實際上不會被發送出去。
               request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE)
                       .build();
           }
      
           okhttp3.Response response = chain.proceed(request);
           if (NetworkUtils.isConnected()) {//有網絡情況下,根據請求接口的設置,配置緩存。
               //這樣在下次請求時,根據緩存決定是否真正發出請求。
               String cacheControl = request.cacheControl().toString();
               //當然如果你想在有網絡的情況下都直接走網絡,那么只需要
               //將其超時時間這是為0即可:String cacheControl="Cache-Control:public,max-age=0"
               int maxAge = 60 * 60; // read from cache for 1 minute
               return response.newBuilder()
      

      // .header("Cache-Control", cacheControl)

                       .header("Cache-Control", "public, max-age=" + maxAge)
                       .removeHeader("Pragma")
                       .build();
           } else {
               //無網絡
               int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
               return response.newBuilder()
      

      // .header("Cache-Control", "public,only-if-cached,max-stale=360000")

                       .header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale)
                       .removeHeader("Pragma")
                       .build();
           }
      
       }
      

      }; return commonParams; }</code></pre>

      自定義CookieJar

      httpClientBuilder.cookieJar(new CookieJar() {

       final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();
      
       @Override
       public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
           cookieStore.put(url, cookies);//保存cookie
           //也可以使用SP保存
       }
      
       @Override
       public List<Cookie> loadForRequest(HttpUrl url) {
           List<Cookie> cookies = cookieStore.get(url);//取出cookie
           return cookies != null ? cookies : new ArrayList<Cookie>();
       }
      

      });</code></pre>

      啟動Retrofit2

      到了這里,基本上準備工作都做好了,可以啟動Retrofit2了

      retrofit = new Retrofit.Builder()

           .client(httpClientBuilder.build())
           .baseUrl(BASE_URL)
           .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
           .addConverterFactory(FastJsonConverterFactory.create())
           .build();
      

      mApi = retrofit.create(MyRetrofit2Service.class);</code></pre>

      界面上通過getEnqueue()方法調用

      /**

    • 異步調用 */ public void getEnqueue() { Call<HttpResult<News>> call = mApi.post(NEWS_URI, params); call.enqueue(new Callback<HttpResult<News>>() {

       @Override
       public void onResponse(Call<HttpResult<News>> call, Response<HttpResult<News>> response) {
           //處理請求成功
           Log.e("OkHttp", "處理成功請求 response = " + response.body().toString());
       }
      
       @Override
       public void onFailure(Call<HttpResult<News>> call, Throwable t) {
           //處理請求失敗
           Log.e("OkHttp", "處理失敗請求");
       }
      

      }); // cancelCall(call); }</code></pre>

      結束

      相信到這邊應該能滿足Demo的要求了吧,至少用是可以用了,不過在實際開發中,這還是太糙了點,哎..沒時間打磨,慢慢來吧,有興趣的可以留言討論下優化方案,大家拓展下思維也是不錯的嘛

       

      來自:http://www.jianshu.com/p/73216939806a

       

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