挖一挖Retrofit原始碼(一)

語言: CN / TW / HK

highlight: a11y-dark

攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第1天,點選檢視活動詳情

Retrofit原始碼系列文章: - 挖一挖Retrofit原始碼(一) - 挖一挖Retrofit原始碼(二) - Retrofit之CallAdapter解析 - Retrofit之自定義CallAdapter - Retrofit之Converter解析

簡介

Retrofit是一個基於OkHttp的網路請求框架,對OkHttp的請求和結果進行處理,Retrofit僅負責網路請求介面的封裝並自動生成實際網路請求的程式碼,而網路請求的工作本質上是OkHttp完成的。

應用發起網路請求後,實際上是使用 Retrofit 介面層封裝請求引數、Header、Url等資訊,之後由OkHttp完成後續的請求操作,OkHttp將伺服器返回的原始資料交給Retrofit,Retrofit根據使用者的需求對結果進行解析。

PS:本文基於Retrofit版本2.8.0

原始碼解析

1. 建立Retrofit例項

kotlin val retrofit = Retrofit.Builder() .baseUrl("https://test.com") .addConverterFactory(GsonConverterFactory.create()) .build() Retrofit例項是使用建造者模式通過Builder類建立的,並初始化一些配置項,下面將對建立Retrofit例項進行詳細分析。

1.1 Retrofit類

```java public final class Retrofit { private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();

final okhttp3.Call.Factory callFactory; //生產網路請求器(Call)的工廠,預設使用OkHttp final HttpUrl baseUrl; //url地址 final List converterFactories; //資料轉換器(converter)的工廠List final List callAdapterFactories; //生產網路請求介面卡(CallAdapter)的工廠List final @Nullable Executor callbackExecutor; //回撥方法執行器 final boolean validateEagerly; //是否提前對業務介面中的註解進行驗證轉換的標誌位

Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List converterFactories, List callAdapterFactories, @Nullable Executor callbackExecutor, boolean validateEagerly) { this.callFactory = callFactory; this.baseUrl = baseUrl; this.converterFactories = converterFactories; // Copy+unmodifiable at call site. this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site. this.callbackExecutor = callbackExecutor; this.validateEagerly = validateEagerly; } //省略其他程式碼 ...... } ``` 其中callFactory、converterFactories、callAdapterFactories體現了工廠模式,使用者不需要關心具體引數就可以例項化出所需要的類。

1.2 Builder類

```java public static final class Builder { private final Platform platform; private @Nullable okhttp3.Call.Factory callFactory; private @Nullable HttpUrl baseUrl; private final List converterFactories = new ArrayList<>(); private final List callAdapterFactories = new ArrayList<>(); private @Nullable Executor callbackExecutor; private boolean validateEagerly;

//①Builder的有參建構函式
Builder(Platform platform) {
  this.platform = platform;
}


//②Builder的無參建構函式
public Builder() {
  this(Platform.get());
}
......

} ``` 可以看出Builder類的成員變數和Retrofit類的是對應的,所以Retrofit類的成員變數基本上是通過Builder類來配置。

程式碼裡①接收了Platform物件並設進Builder類的Platform,而②則是用this呼叫了自己的有參建構函式①並通過呼叫Platform.get()傳入Platform物件,即Builder的建構函式中檢測了當前的執行平臺。

1.3 addConverterFactory方法

java public Builder addConverterFactory(Converter.Factory factory) { converterFactories.add(Objects.requireNonNull(factory, "factory == null")); return this; } 前面建立Retrofit例項的時候是把GsonConverterFactory.create()傳進了addConverterFactory方法,而GsonConverterFactory.create()實際上是建立並返回了一個含有Gson物件例項的GsonConverterFactory,即指定Retrofit使用Gson進行解析資料,也可以使用其他解析方式(如Json、XML或Protocobuf)或者可以使用自定義資料解析器(必須繼承Converter.Factory)。addCallAdapterFactory方法也是類似的,可以新增Retrofit內建的CallAdapterFactory,也可以使用自定義的介面卡。

1.4 build方法

在建立Retrofit.Builder物件並進行自定義配置後,我們就要呼叫build方法來構造出Retrofit物件了,下面來看看build()裡幹了什麼:

```java public Retrofit build() { //必須要配置baseUrl if (baseUrl == null) { throw new IllegalStateException("Base URL required."); }

  //配置網路請求執行器,若無指定則預設使用OkHttp
  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }


  //配置回撥方法執行器,Android平臺下預設為MainThreadExecutor
  //MainThreadExecutor的作用:切換執行緒(子->>主執行緒),並在主執行緒(UI執行緒)中執行回撥方法
  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }


  // Make a defensive copy of the adapters and add the default Call adapter.
  //配置網路請求介面卡工廠,將預設介面卡工廠DefaultCallAdapterFactory新增至List末尾
  List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
  callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));


  // Make a defensive copy of the converters.
  //配置資料轉換器工廠
  List<Converter.Factory> converterFactories = new ArrayList<>(
      1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());


  // Add the built-in converter factory first. This prevents overriding its behavior but also
  // ensures correct behavior when using converters that consume all types.
  //首先新增預設的BuiltInConverters,最後新增檢測平臺環境時預設返回的資料轉換器OptionalConverterFactory
  converterFactories.add(new BuiltInConverters());
  converterFactories.addAll(this.converterFactories);
  converterFactories.addAll(platform.defaultConverterFactories());


  return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
      unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}

```

可以看到在build()中做了:檢查配置、設定預設配置、建立Retrofit物件,而且在執行build()之前只有baseUrl()是必須呼叫來設定訪問地址的,其餘方法則是可選的,沒有指定就使用預設配置。另外:

  • callFactory負責建立HTTP請求,HTTP請求被抽象為了okhttp3.Call類,它表示一個已經準備好,可以隨時執行的HTTP請求
  • CallAdapter負責把伺服器的原始返回資料封裝成Retrofit的Call物件(注意和okhttp3.Call區分開來),通過Call發起HTTP請求,並把從伺服器拿到的資料(通過okhttp3.Call實現)轉換為介面方法宣告的返回型別T(通過Converter 實現)

1.5 總結

Retrofit建立例項時主要涉及了以下的配置項: - 網路請求地址baseUrl - 網路請求執行器工廠callFactory:預設使用OkHttpClient,通過OkHttp處理後續網路請求和響應 - 回撥方法執行器callbackExecutor:預設使用MainThreadExecutor - 網路請求介面卡工廠集合callAdapterFactories:預設新增DefaultCallAdapterFactory - 資料轉換器工廠集合converterFactories:預設新增BuiltInConverters和OptionalConverterFactory,BuiltInConverters直接取對應的值並未轉換,可以通過設定GsonConverterFactory來用Gson解析json資料

由於使用了建造者模式,所以建立Retrofit例項時並不需要關心配置細節。

2. 建立網路請求介面例項

```kotlin //定義網路請求的介面類 interface TestApi { @GET("/.../...") fun getDataA(): Call //①不使用協程

@GET("/.../...")
suspend fun getDataB(): DataBean //②使用協程

}

//建立網路請求介面例項 val testApi = retrofit.create(TestApi::class.java)

//呼叫介面方法① fun getDataA() { testApi.getDataA().enqueue(object : Callback { override fun onResponse( call: Call, response: Response ) { //成功邏輯 }

        override fun onFailure(call: Call<DataBean>, t: Throwable) {
            //失敗邏輯
        }
    })

} //呼叫介面方法② suspend fun getDataB() { try { testApi.getDataB() //成功邏輯 } catch (e: Exception) { //失敗邏輯 } } ```

可以看到請求接口裡的請求方法型別及請求url的後半部分是通過註解來標註的,而DataBean是該方法的返回型別(一個自定義的Data類)

另外,在沒有給Retrofit新增任何CallAdapterFactory的情況下:

  • 介面方法①的返回型別就必須是Call<?>,不能為其他型別;
  • 介面方法②由於用了suspend關鍵字,掛起了之後就可以無需使用Call型別返回結果,而是直接返回資料類物件。

介面建立完之後就是呼叫Retrofit的create方法來生成介面的動態代理物件,下面就來看看具體是怎麼實現動態代理的。

2.1 create方法

```java public T create(final Class service) { //①驗證介面合法性 validateServiceInterface(service); //②生成介面的動態代理 return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0];

      @Override public @Nullable Object invoke(Object proxy, Method method,
          @Nullable Object[] args) throws Throwable {
        //如果該方法是Object的方法則直接呼叫
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        //是預設方法則直接執行預設方法
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        //獲取方法的對應ServiceMethod物件並呼叫invoke方法
        return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
      }
    });

} ```

①處的方法除了校驗介面之外,還對是否提前解析介面中的所有方法進行了判斷:

```java private void validateServiceInterface(Class<?> service) { if (!service.isInterface()) { throw new IllegalArgumentException("API declarations must be interfaces."); }

Deque<Class<?>> check = new ArrayDeque<>(1);
check.add(service);
......//驗證介面泛型引數及介面型別


 //如果設定了預解析則提前解析介面中的所有方法
 if (validateEagerly) {
  Platform platform = Platform.get();
  for (Method method : service.getDeclaredMethods()) {
    if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
      loadServiceMethod(method);
    }
  }
}

} ```

如果初始化Retrofit例項的時候設定了預解析,就會提前給介面中的每個方法解析生成對應的一個ServiceMethod物件並存入快取,否則就呼叫方法的時候再動態解析,具體是怎麼解析的看loadServiceMethod這個方法(後面會說明)

在create方法中主要的程式碼就是②處的Proxy.newProxyInstance,當把請求介面類傳進create裡的時候就會動態生成並返回一個代理物件,而介面方法被呼叫的時候實際上是由動態代理物件將方法轉發到InvocationHandler的invoke方法中處理。因為實際呼叫的介面方法不是預設方法,那真正呼叫到的就是loadServiceMethod(method).invoke(args != null ? args : emptyArgs),這也是後續分析的關鍵入口。

2.2 loadServiceMethod方法

```java ServiceMethod<?> loadServiceMethod(Method method) { //從快取中獲取 ServiceMethod<?> result = serviceMethodCache.get(method); if (result != null) return result;

//加鎖,建立該方法的ServiceMethod物件並存入快取
synchronized (serviceMethodCache) {
  result = serviceMethodCache.get(method);
  if (result == null) {
    result = ServiceMethod.parseAnnotations(this, method);
    serviceMethodCache.put(method, result);
  }
}
return result;

} ```

介面方法傳進loadServiceMethod後優先是從快取中查對應的ServiceMethod物件,獲取到了就直接返回,否則加鎖後建立這個方法的ServiceMethod物件,以method為主鍵存入一個叫serviceMethodCache的map中。

可以看出來同一個介面的方法只會解析一次,每一個呼叫過的方法都會存在對應的ServiceMethod物件,因為建介面例項的時候傳進的是class物件(TestApi::class.java),class物件在程序內單例的,所以獲取到的同一個method也是單例的,因此這裡的快取有效。

這段程式碼主要是為了拿到一個ServiceMethod物件,而生成ServiceMethod物件是呼叫了ServiceMethod的parseAnnotations方法,ServiceMethod裡面到底是什麼東東,parseAnnotations方法具體幹了什麼,下面繼續來扒一扒。

2.3 ServiceMethod類

```java abstract class ServiceMethod { static ServiceMethod parseAnnotations(Retrofit retrofit, Method method) { //根據方法的註解生成Request的工廠類例項 RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

Type returnType = method.getGenericReturnType();
//校驗方法的返回型別
if (Utils.hasUnresolvableType(returnType)) {
  throw methodError(method,
      "Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
  throw methodError(method, "Service methods cannot return void.");
}


//生成並返回HttpServiceMethod物件
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);

}

abstract @Nullable T invoke(Object[] args); } ```

回看2.1中獲取的ServiceMethod物件實際上是這裡的HttpServiceMethod物件,呼叫鏈路為loadServiceMethod -> ServiceMethod.parseAnnotations -> HttpServiceMethod.parseAnnotations,繼續往下看HttpServiceMethod的parseAnnotations方法。

2.4 HttpServiceMethod類parseAnnotations方法

```java static HttpServiceMethod parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; boolean continuationWantsResponse = false; boolean continuationBodyNullable = false;

Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
  //Kotlin協程檢測
  ......
  adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
  annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
  //獲取方法返回型別
  adapterType = method.getGenericReturnType();
}


CallAdapter<ResponseT, ReturnT> callAdapter =
    createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
......//校驗responseType


Converter<ResponseBody, ResponseT> responseConverter =
    createResponseConverter(retrofit, method, responseType);


okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
  return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
  //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
  return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
      callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
  //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
  return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
      callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
      continuationBodyNullable);
}

} ```

parseAnnotations方法最後返回的是HttpServiceMethod物件,如果是非掛起方法則直接返回CallAdapted,掛起方法並且返回型別為Response則返回SuspendForResponse,否則返回SuspendForBody。

現在瞭解了Retrofit生成動態代理物件的邏輯,那動態代理是怎麼進行網路請求的和回撥又是如何處理的呢?下一篇將繼續分析。