Retrofit原始碼分析

語言: CN / TW / HK

highlight: androidstudio theme: cyanosis


MLOC8WOJ(}41[1PM6B]R7VX.gif

原始碼分析第一步 , 先把原始碼clone下來 retrofit 原始碼地址點這

專案結構

把原始碼 clone 下來 , 可以看到 retrofit 整體結構如下 image.png

圖 http包目錄下就是一些http協議常用介面 , 比如 請求方法 url , 請求體, 請求行 之類的

retrofit 使用

把retrofit使用作為分析的切入口吧 , retrofit單元測試使用如下 ```java public final class BasicCallTest { @Rule public final MockWebServer server = new MockWebServer();

interface Service {
    @GET("/") Call<ResponseBody> getBody();
}

@Test public void responseBody() throws IOException {
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(server.url("/"))
        .build();
    Service example = retrofit.create(Service.class);

    server.enqueue(new MockResponse().setBody("1234"));

    Response<ResponseBody> response = example.getBody().execute();
    assertEquals("1234", response.body().string());
}

}

``` Retrofit 構建 , 以構建者模式構建出Retrofit image.png

可以看到builer可以配置baseUrl , 回撥執行緒池 , 還有一些介面卡的工廠 , 這些介面卡的作用後面說

Retrofit #create

從create 方法開始分析 , 跟進看下create 方法 ```java public T create(final Class service) { validateServiceInterface(service); return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] {service}, new InvocationHandler() { private final Object[] emptyArgs = new Object[0];

        @Override
        public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
            throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args);
            }
            args = args != null ? args : emptyArgs;
            Platform platform = Platform.get();
            return platform.isDefaultMethod(method)
                ? platform.invokeDefaultMethod(method, service, proxy, args)
                : loadServiceMethod(method).invoke(args);
        }
    });

}

service 是請求的介面的class , 動態代理只能是介面 , 所以validateServiceInterface 先驗證是不是介面 , 不是介面則拋異常 method.getDeclaringClass() 獲取宣告類的Class 比如 A類 有個method , method.getDeclaringClass() 返回為A.class , 如果method宣告類是Object.class 則直接method.invoke , 往下執行毫無意義 Platform#get(); 會根據當前平臺獲取Platform ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e68f5a3423745d4865597baeadcfcef~tplv-k3u1fbpfcp-zoom-1.image) 有點類似狀態模式思想 , 根據當前的平臺選擇合適的子類java public boolean isDefaultMethod(Method method) { return method.isDefault(); } isDefault , 在介面型別中以default關鍵字宣告 則返回true, 比如java interface InterfaceWithDefault { void firstMethod();

default void newMethod() {
    System.out.println("newMethod");
}

} ``` 所以此處會返回 false 接著呼叫 loadServiceMethod image.png

ServiceMethod #parseAnnotations

跟進ServiceMethod #parseAnnotations java static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) { RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory); } 根據當前的方法資訊構建出RequestFactory , 然後把具體實現細節交給HttpServiceMethod 處理 , HttpServiceMethod 繼承自ServiceMethod , 有三個子類 image.png 我們在Api.class定義的方法 , 解析並不是由HttpServiceMethod 完成 , 而是由RequestFactory去處理的 , 比如解析方法的註解 image.png 更多的解析方法如下 image.png 方法解析的細節就不說了 , 繼續看RequestFactory 這個類 , 這個類的作用難道就是負責方法資訊的解析 , 感覺和名字不太符合 , RequestFactory 顧名思義應該是用來構建Request的工廠 , 果不其然內部還有個 create 方法 , 用來構建okhttp3.Request的 java //RequestFactory #create okhttp3.Request create(Object[] args) throws IOException { return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build(); } 就只有這一個create方法 , 難道retrofit 就只能使用okhttp來負責網路請求 ? 答案是肯定的 , 從最開始的 loadServiceMethod(method).invoke(args)也可以看出 , 方法裡面只構建出OkHttpCall 沒提供api可以讓我們切換到其他的網路請求庫 image.png 但是 , Call 又抽象成介面的形式 ,如下, 這麼做的目的可能是以後便於框架的維護 image.png 沿途風景再美麗 , 也要回到主線路 , 繼續分析 HttpServiceMethod#parseAnnotations

HttpServiceMethod#parseAnnotations

這個方法太長 , 貼關鍵程式碼吧 ```java static HttpServiceMethod parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; boolean continuationWantsResponse = false; boolean continuationBodyNullable = false; boolean continuationIsUnit = false;

Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
  Type[] parameterTypes = method.getGenericParameterTypes();
  Type responseType =
      Utils.getParameterLowerBound(
          0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
  if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
    continuationWantsResponse = true;
  }
} else {
  //非kt 協程情況
  adapterType = method.getGenericReturnType();
}

CallAdapter<ResponseT, ReturnT> callAdapter =
    createCallAdapter(retrofit, method, adapterType, annotations);

Type responseType = callAdapter.responseType();

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

okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
    //非kt 協程情況
  return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
  return (HttpServiceMethod<ResponseT, ReturnT>)
      new SuspendForResponse<>(
          requestFactory,
          callFactory,
          responseConverter,
          (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
  return (HttpServiceMethod<ResponseT, ReturnT>)
      new SuspendForBody<>(
          requestFactory,
          callFactory,
          responseConverter,
          (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
          continuationBodyNullable,
          continuationIsUnit);
}

}

``` 分兩種情況 :

  1. kotlin 協程情況
  2. 非Kotlin 協程情況

第二種 非Kotlin協程情況

第一種情況稍許複雜 , 先分析第二種
adapterType = method.getGenericReturnType(); java @GET("/") Call<ResponseBody> getBody(); 如果是上面程式碼 , method.getGenericReturnType() = Call , 然後根據方法的返回值型別 / 方法註解資訊 , 構建出CallAdapter
createCallAdapter() 方法會使用 CallAdapter.Factory 構建CallAdapter , 因為初始化retrofit的時候沒有配置CallAdapter.Factory , 所以會使用預設的DefaultCallAdapterFactory
最終會進入DefaultCallAdapterFactory#get

DefaultCallAdapterFactory#get

這個方法作用就是返回CallAdapter , 修改下原始碼加入兩個列印 ```java public @Nullable CallAdapter<?, ?> get( Type returnType, Annotation[] annotations, Retrofit retrofit) {

final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
System.out.println("TAG" + " ->" + "returnType = " +getRawType(returnType) .getSimpleName());
System.out.println("TAG" + " ->" + "responseType = " +getRawType(responseType) .getSimpleName());

return new CallAdapter<Object, Call<?>>() {
  @Override
  public Type responseType() {
    return responseType;
  }

  @Override
  public Call<Object> adapt(Call<Object> call) {
    return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
  }
};

} ``` 執行可以看到以下列印資訊 image.png returnType = Call ,responseType =ResponseBody
總結一下 , returnType就是方法返回值 , responseType 就是方法返回值上的泛型 DefaultCallAdapterFactory會根據平臺環境去構建 image.png 以Android24分析 , DefaultCallAdapterFactory(Executor callbackExecutor) , 構造方法中 , 執行緒池為主執行緒池 , 在retrofit初始化的時候新增到 到callAdapterFactories 集合中

至此 , CallAdapterFactory 和 CallAdapter 分析完了 , 總結下就是給Call (retrofit記憶體只有OkHttpCall 作為唯一實現類)做適配 , 讓其可以在 Rxjava / 協程 等各個環境中使用 Call

非kt 協程情況下 , parseAnnotations 方法最終返回的是將requestFactory , callFactory , responseConverter, callAdapter 封裝好的CallAdapted 物件

再次回到夢開始的地方Retrofit#create 方法 , loadServiceMethod獲取的ServiceMethod最終實現類為CallAdapted , 獲取之後會呼叫invoke方法 , invoke是一個final方法 , 裡面構建了OkHttpCall , 然後呼叫了adapt方法 , adapt中呼叫了callAdapter.adapt(call);
java @Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) { return callAdapter.adapt(call); } 這裡的ReturnT 就是ExecutorCallbackCall<>(executor, call) 物件 , 所以 example.getBody().execute() 就是呼叫ExecutorCallbackCall#execute方法 java //ExecutorCallbackCall#execute public Response<T> execute() throws IOException { return delegate.execute(); } delegate為OkHttpCall , 所以就呼叫到OkHttpCallCall#execute方法 , 這裡就轉給Okhttp去請求網路載入資料了 , 程式碼就不貼了 , 我們看下網路請求之後 , 資料Response 的處理 , 關鍵程式碼OkHttpCall#parseResponse ```java Response parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body();

ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
  T body = responseConverter.convert(catchingBody);
  return Response.success(body, rawResponse);
}

}

``` responseConverter 在 HttpServiceMethod#parseAnnotations 方法中獲取 , 迴應資料轉換器 , 把資料轉換成我們可以直接使用的物件 , 比如我們常用的 GsonConverterFactory
最後把轉換好之後的資料 , 封裝成Response物件返回 image.png response.body()就是responseConverter 轉換後的資料 來張大致流程圖感受下吧 未命名檔案 (1).png

第一種 Kotlin協程情況

其實大致流程第二種情況分析的差不多了 , 接下來分析下Retrofit對於kotlin的特殊處理吧 java if (Utils.getRawType(parameterType) == Continuation.class) { isKotlinSuspendFunction = true; return null; } 協程掛起方法 , 第一個引數為Continuation , 所以判斷是不是掛起方法也很簡單 , 根據ResponseType 去構建協程專用的HttpServiceMethod , 主要有兩類

  1. SuspendForResponse , 對應type為Continuation>
  2. SuspendForBody , 對應type為Continuation

這裡看下 SuspendForBody 實現 , 套娃情況就不分析了 image.png 如果是這樣使用 , 最終會調到SuspendForBody #adapt java @Override protected Object adapt(Call<ResponseT> call, Object[] args) { call = callAdapter.adapt(call); Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1]; try { //去掉干擾程式碼 , 僅保留這個 return KotlinExtensions.awaitNullable(call, continuation); } } 這個地方就很關鍵了 , java 直接調kotlin 協程 suspend 方法
KotlinExtensions.awaitNullable 會調到KotlinExtensions#await方法
retrofit與協程適配的細節都在 KotlinExtensions這個類裡
進入await , 可以看到使用suspendCancellableCoroutine把回撥裝換成協程
```kotlin @JvmName("awaitNullable") suspend fun Call.await(): T? { return suspendCancellableCoroutine { continuation -> continuation.invokeOnCancellation { cancel() } enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { continuation.resume(response.body()) } else { continuation.resumeWithException(HttpException(response)) } }

        override fun onFailure(call: Call<T?>, t: Throwable) {
            continuation.resumeWithException(t)
        }
    })
}

} 其實內部也是呼叫 OkHttp Call.enqueue() , 只不過是用**suspendCancellableCoroutine**給協程做了一層包裝處理 通過 **suspendCancellableCoroutine**包裝之後使用就很簡單了java GlobalScope.launch { try { val result = xxxApi.getXxx() } catch (exception: Exception) {

        }
    }

```

總結

Call 這個介面用於與網路請求庫做適配 , 比如Okhttp

CallAdapter 用於retrofit 與各種環境搭配使用做適配 , 比如rxjava / 協程 / java

Converter 用於將請求結果轉換實體類Bean 或者其他

用到的設計模式有: 動態代理/靜態代理 / 構建者 / 工廠 / 介面卡 / 狀態 等