Retrofit

2020-08-10 17:45:47

簡介

Retrofit是一個Restful設計風格的Http網路請求框架,基於OkHttp

功能

  • 基於OkHttp、遵循restful api設計風格
  • 通過註解設定網路請求參數
  • 支援同步、非同步的網路請求
  • 支援多種數據的解析和序列化格式(Gson、Json、XML、Protobuf)
  • 提供了對RxJava的支援

優點

功能強大、簡潔易用、可延伸性好

應用場景

任何網路請求的場景都應該優先選擇,特別是後臺API遵循restful API設計風格或專案中使用到RxJava時

小結

  • 準確來說,Retrofit是一個RESTful的Http網路請求框架的封裝

  • 原因:網路請求的工作本質上是Okhttp完成,而Retrofit僅負責啊網路請求介面的封裝

  • 在这里插入图片描述

  • App應用程式通過Retrofit請求網路,實際上是使用Retrofit介面層封裝請求參數、Header、Url等資訊,之後由OkHttp完成後續的請求操作

  • 在伺服器端返回數據之後,Okhttp將原始的結果交給Retrofit,Retrofit根據使用者的需求對結果進行解析

使用

  • 引入Retrofit庫的依賴
  • 建立接收伺服器返回數據的類
  • 建立用於描述網路請求的介面 :Retrofit將http請求抽象成java介面。用動態代理動態將該介面的註解翻譯成一個Http請求,最後再執行Http請求
  • 建立Retrofit範例
  • 建立網路請求介面範例並設定網路請求參數
  • 發送網路請求(非同步/同步)

封裝了數據轉換、執行緒切換的操作

  • 處理伺服器返回的數據

註解型別

在这里插入图片描述

註解說明

在这里插入图片描述

Retrofit把網路請求的Url分成了兩部分設定:

// 第1部分:在網路請求介面的註解設定
 @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
Call<Translation>  getCall();

// 第2部分:在建立Retrofit範例時通過.baseUrl()設定
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") //設定網路請求的Url地址
                .addConverterFactory(GsonConverterFactory.create()) //設定數據解析器
                .build();

// 從上面看出:一個請求的URL可以通過 替換塊 和 請求方法的參數 來進行動態的URL更新。
// 替換塊是由 被{}包裹起來的字串構成
// 即:Retrofit支援動態改變網路請求根目錄

網路請求的完整Url : 在建立Retrofit範例時通過.baseUrl()設定 + 網路請求介面的註解設定

標記

在这里插入图片描述

@FormUrlEncoded

  • 作用:表示發送form-encoded的數據

每個鍵值對需要用@Filed來註解鍵名,隨後的物件需要提供值

@Multipart

  • 作用:表示發送form-encoded數據(適用於有檔案上傳的場景)

每個鍵值對需要用@Part來註解鍵名,隨後的物件需要提供值

具體使用如下:

public interface GetRequest_Interface {
        /**
         *表明是一個表單格式的請求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示將後面的 <code>String name</code> 中name的取值作爲 username 的值
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
         
        /**
         * {@link Part} 後面支援三種類型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意型別
         * 除 {@link okhttp3.MultipartBody.Part} 以外,其它型別都必須帶上表單欄位({@link okhttp3.MultipartBody.Part} 中已經包含了表單欄位的資訊),
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

}

// 具體使用
       GetRequest_Interface service = retrofit.create(GetRequest_Interface.class);
        // @FormUrlEncoded 
        Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
        
        //  @Multipart
        RequestBody name = RequestBody.create(textType, "Carson");
        RequestBody age = RequestBody.create(textType, "24");

        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
        Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);

網路請求參數

在这里插入图片描述

詳細說明

a. @Header、@Headers

  • 作用:新增請求頭/新增不固定的請求頭
  • 具體使用如下:
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()

// 以上的效果是一致的。
// 區別在於使用場景和使用方式
// 1. 使用場景:@Header用於新增不固定的請求頭,@Headers用於新增固定的請求頭
// 2. 使用方式:@Header作用於方法的參數;@Headers作用於方法

b. @Body

  • 作用:以Post方式傳遞自定義數據型別給伺服器
  • 特別注意:如果提交的是一個Map,那麼作用相當於@Field

Map經過FormBody.Builder類處理成符合OkHttp格式的表單,如:

FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");

c. @Filed、@FiledMap

  • 作用:發送Post請求時提交請求的表單欄位
  • 具體使用:與@FormUrlEncoded註解配合使用
public interface GetRequest_Interface {
        /**
         *表明是一個表單格式的請求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示將後面的 <code>String name</code> 中name的取值作爲 username 的值
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);

/**
         * Map的key作爲表單的鍵
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);

}

// 具體使用
         // @Field
        Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);

        // @FieldMap
        // 實現的效果與上面相同,但要傳入Map
        Map<String, Object> map = new HashMap<>();
        map.put("username", "Carson");
        map.put("age", 24);
        Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);

d. @Query、@QueryMap

  • 作用
    用於@GET方法的查詢參數(Query = Url中’?'後面的key-value)

如:url = http://www.println.net/?cate=android 其中,Query = cate

   @GET("/")    
   Call<String> cate(@Query("cate") String cate);
}

// 其使用方式同 @Field與@FieldMap,這裏不作過多描述

f. @Path

  • 作用
    URL地址的預設值

  • 具體使用

public interface GetRequest_Interface {

        @GET("users/{user}/repos")
        Call<ResponseBody>  getBlog(@Path("user") String user );
        // 存取的API是:https://api.github.com/users/{user}/repos
        // 在發起請求時, {user} 會被替換爲方法的第一個參數 user(被@Path註解作用)
    }

g. @Url

  • 作用
    直接傳入一個請求的Url變數用於URL設定

  • 具體使用

public interface GetRequest_Interface {

        @GET
        Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
       // 當有URL註解時,@GET傳入的URL就可以省略
       // 當GET、POST...HTTP等方法中沒有設定Url時,則必須使用 {@link Url}提供

}

原理

網路請求的過程

在这里插入图片描述

Retrofit與普通網路請求的比較

在这里插入图片描述

具體過程如下:

1.通過解析 網路請求介面的註解 設定網路請求參數
2.通過 動態代理 生成網路請求物件
3.通過 網路請求適配器 將網路請求物件進行平臺適配
4.通過 網路請求執行器 發送網路請求
5.通過 數據轉換器 解析伺服器返回的數據
6.通過回撥執行器切換執行緒(子執行緒 -> 主執行緒)
7.使用者在主執行緒處理返回結果
在这里插入图片描述

Retrofit類

<-- Retrofit類 -->
 public final class Retrofit {
  
  private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
  // 網路請求設定物件(對網路請求介面中方法註解進行解析後得到的物件)
  // 作用:儲存網路請求相關的設定,如網路請求的方法、數據轉換器、網路請求適配器、網路請求工廠、基地址等
  
  private final HttpUrl baseUrl;
  // 網路請求的url地址

  private final okhttp3.Call.Factory callFactory;
  // 網路請求器的工廠
  // 作用:生產網路請求器(Call)
  // Retrofit是預設使用okhttp
  
   private final List<CallAdapter.Factory> adapterFactories;
  // 網路請求適配器工廠的集合
  // 作用:放置網路請求適配器工廠
  // 網路請求適配器工廠作用:生產網路請求適配器(CallAdapter)
  // 下面 下麪會詳細說明


  private final List<Converter.Factory> converterFactories;
  // 數據轉換器工廠的集合
  // 作用:放置數據轉換器工廠
  // 數據轉換器工廠作用:生產數據轉換器(converter)

  private final Executor callbackExecutor;
  // 回撥方法執行器

private final boolean validateEagerly; 
// 標誌位
// 作用:是否提前對業務介面中的註解進行驗證轉換的標誌位


<-- Retrofit類別建構函式 -->
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,  
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,  
      Executor callbackExecutor, boolean validateEagerly) {  
    this.callFactory = callFactory;  
    this.baseUrl = baseUrl;  
    this.converterFactories = unmodifiableList(converterFactories); 
    this.adapterFactories = unmodifiableList(adapterFactories);   
    // unmodifiableList(list)近似於UnmodifiableList<E>(list)
    // 作用:建立的新物件能夠對list數據進行存取,但不可通過該物件對list集閤中的元素進行修改
    this.callbackExecutor = callbackExecutor;  
    this.validateEagerly = validateEagerly;  
  ...
  // 僅貼出關鍵程式碼
}

成功建立一個Retrofit物件的標準:設定好Retrofit類裡的成員變數

  • serviceMethod : 包含所有網路請求資訊的物件
  • baseUrl : 網路請求的url地址
  • callFactory : 網路請求工廠
  • adapterFactories : 網路請求適配器工廠的集合
  • converterFactories : 數據轉換工廠的集合
  • callbackExecutor : 回撥方法執行器

CallAdapter

  • 定義 : 網路請求執行器(Call)的適配器

1.Call在Retrofit裡預設是OkHttpCall
2.在Retrofit中提供了四種CallAdapterFactory:ExecutorCallAdapterFactory(預設)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory

  • 作用:將預設的網路請求執行器(OkHttpCall)轉換成適合被不同平臺來呼叫的網路請求執行器形式

1.例如:一開始Retrofit只打算利用OkHttpCall通過ExecutorCallbackCall切換執行緒,但是後來發現使用RxJava更加方便(不需要Handler切換執行緒)。想要實現Rxjava的方式,就要使用RxJavaCallAdapterFactoryCallAdapter將OkHttpCall轉換成RxJava(Scheduler);
2.Retrofit還支援java8、Guava平臺

  • 好處 : 用最小的代價相容更多平臺,即能適配更多的使用場景

Builder類

<-- Builder類-->
public static final class Builder {
    private Platform platform;
    private okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private List<Converter.Factory> converterFactories = new ArrayList<>();
    private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
    private Executor callbackExecutor;
    private boolean validateEagerly;

// 從上面可以發現, Builder類的成員變數與Retrofit類的成員變數是對應的
// 所以Retrofit類的成員變數基本上是通過Builder類進行設定
// 開始看步驟1

<-- 步驟1 -->
// Builder的構造方法(無參)
 public Builder() {
      this(Platform.get());
// 用this呼叫自己的有參構造方法public Builder(Platform platform) ->>步驟5(看完步驟2、3、4再看)
// 並通過呼叫Platform.get()傳入了Platform物件
// 繼續看Platform.get()方法 ->>步驟2
// 記得最後繼續看步驟5的Builder有參構造方法
    }
...
}

<-- 步驟2 -->
class Platform {

  private static final Platform PLATFORM = findPlatform();
  // 將findPlatform()賦給靜態變數

  static Platform get() {
    return PLATFORM;    
    // 返回靜態變數PLATFORM,即findPlatform() ->>步驟3
  }

<-- 步驟3 -->
private static Platform findPlatform() {
    try {

      Class.forName("android.os.Build");
      // Class.forName(xxx.xx.xx)的作用:要求JVM查詢並載入指定的類(即JVM會執行該類的靜態程式碼段)
      if (Build.VERSION.SDK_INT != 0) {
        return new Android(); 
        // 此處表示:如果是Android平臺,就建立並返回一個Android物件 ->>步驟4
      }
    } catch (ClassNotFoundException ignored) {
    }

    try {
      // 支援Java平臺
      Class.forName("java.util.Optional");
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }

    try {
      // 支援iOS平臺
      Class.forName("org.robovm.apple.foundation.NSObject");
      return new IOS();
    } catch (ClassNotFoundException ignored) {
    }

// 從上面看出:Retrofit2.0支援3個平臺:Android平臺、Java平臺、IOS平臺
// 最後返回一個Platform物件(指定了Android平臺)給Builder的有參構造方法public Builder(Platform platform)  --> 步驟5
// 說明Builder指定了執行平臺爲Android
    return new Platform();
  }
...
}

<-- 步驟4 -->
// 用於接收伺服器返回數據後進行執行緒切換在主執行緒顯示結果

static class Android extends Platform {

    @Override
      CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {

      return new ExecutorCallAdapterFactory(callbackExecutor);
    // 建立預設的網路請求適配器工廠
    // 該預設工廠生產的 adapter 會使得Call在非同步呼叫時在指定的 Executor 上執行回撥
    // 在Retrofit中提供了四種CallAdapterFactory: ExecutorCallAdapterFactory(預設)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory
    // 採用了策略模式
    
    }

    @Override 
      public Executor defaultCallbackExecutor() {
      // 返回一個預設的回撥方法執行器
      // 該執行器作用:切換執行緒(子->>主執行緒),並在主執行緒(UI執行緒)中執行回撥方法
      return new MainThreadExecutor();
    }

    static class MainThreadExecutor implements Executor {
   
      private final Handler handler = new Handler(Looper.getMainLooper());
      // 獲取與Android 主執行緒系結的Handler 

      @Override 
      public void execute(Runnable r) {
        
        
        handler.post(r);
        // 該Handler是上面獲取的與Android 主執行緒系結的Handler 
        // 在UI執行緒進行對網路請求返回數據處理等操作。
      }
    }

// 切換執行緒的流程:
// 1. 回撥ExecutorCallAdapterFactory生成了一個ExecutorCallbackCall物件
//2. 通過呼叫ExecutorCallbackCall.enqueue(CallBack)從而呼叫MainThreadExecutor的execute()通過handler切換到主執行緒
  }

// 下面 下麪繼續看步驟5的Builder有參構造方法
<-- 步驟5 -->
//  Builder類別建構函式2(有參)
  public  Builder(Platform platform) {

  // 接收Platform物件(Android平臺)
      this.platform = platform;

// 通過傳入BuiltInConverters()物件設定數據轉換器工廠(converterFactories)

// converterFactories是一個存放數據轉換器Converter.Factory的陣列
// 設定converterFactories即設定裏面的數據轉換器
      converterFactories.add(new BuiltInConverters());

// BuiltInConverters是一個內建的數據轉換器工廠(繼承Converter.Factory類)
// new BuiltInConverters()是爲了初始化數據轉換器
    }

Builder類設定了預設的屬性

  • 平臺型別物件 : Android
  • 網路請求適配器工廠 : CallAdapterFactory

CallAdapter用於對原始Call進行再次封裝,如Call到Observable

  • 數據轉換器工廠 : converterFactory
  • 回撥執行器 : callbackExecutor

baseUrl

<-- 步驟1 -->
public Builder baseUrl(String baseUrl) {

      // 把String型別的url參數轉化爲適合OKhttp的HttpUrl型別
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);     

    // 最終返迴帶httpUrl型別參數的baseUrl()
    // 下面 下麪繼續看baseUrl(httpUrl) ->> 步驟2
      return baseUrl(httpUrl);
    }


<-- 步驟2 -->
    public Builder baseUrl(HttpUrl baseUrl) {

      //把URL參數分割成幾個路徑碎片
      List<String> pathSegments = baseUrl.pathSegments();   

      // 檢測最後一個碎片來檢查URL參數是不是以"/"結尾
      // 不是就拋出異常    
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }     
      this.baseUrl = baseUrl;
      return this;
    }

baseUrl()用於設定Retrofit類的網路請求url地址。它可以將傳入的String型別的url轉換爲Okhttp的HttpUrl型別的url

GsonConverterFactory.create()

public final class GsonConverterFactory extends Converter.Factory {

<-- 步驟1 -->
  public static GsonConverterFactory create() {
    // 建立一個Gson物件
    return create(new Gson()); ->>步驟2
  }

<-- 步驟2 -->
  public static GsonConverterFactory create(Gson gson) {
    // 建立了一個含有Gson物件範例的GsonConverterFactory
    return new GsonConverterFactory(gson); ->>步驟3
  }

  private final Gson gson;

<-- 步驟3 -->
  private GsonConverterFactory(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    this.gson = gson;
  }


所以,GsonConverterFactory.create()是建立了一個含有Gson物件範例的GsonConverterFactory,並返回給addConverterFactory

addConverterFactory()


// 將上面建立的GsonConverterFactory放入到 converterFactories陣列
// 在第二步放入一個內建的數據轉換器工廠BuiltInConverters()後又放入了一個GsonConverterFactory
  public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }


1.Retrofit預設使用Gson進行解析
2.若使用其他解析方式(Json、XML、Protobuf),也可以通過自定義數據解析器來實現(必須繼承Converter.Factory)

非同步請求OkHttpCall.enqueue()

發送請求的過程

  • 對網路請求介面的方法中的每個參數利用對應ParameterHandler進行解析,再根據ServiceMethod物件建立一個OkHttp的Request物件

  • 使用OkHttp的Request發送網路請求

  • 對返回的數據使用之前設定的數據轉換器(GsonConverterFactory)解析返回的數據,最終得到一個Response物件

  • 進行執行緒切換從而在主執行緒處理返回的數據結果

若使用了RxJava,則直接回撥到主執行緒

非同步請求的過程跟同步請求類似,唯一不同的是:非同步請求會將回撥方法交給回撥執行器在指定的執行緒中執行(UI執行緒)

總結

Retrofit使用建造者模式通過Builder類建立了一個Retrofit範例,具體建立細節:

  • 平臺型別物件(plateform-Android)

  • 網路請求的url地址(baseUrl)

  • 網路請求工廠(callFactory)

預設使用OkHttpCall

  • 網路請求適配器工廠的集合(adapterFactories)

本質是設定了網路請求適配器工廠——預設是ExecutorCallAdapterFactory

  • 數據轉換器工廠的集合(converterFactories)

本質是設定了數據轉換器工廠

  • 回撥方法執行器(callbackExecutor)

預設回撥方法執行器作用是:切換執行緒(子執行緒 -> 主執行緒)