Android小知識-剖析Retrofit中ServiceMethod相關引數以及建立過程

公眾號_顧林海發表於2018-11-04

本平臺的文章更新會有延遲,大家可以關注微信公眾號-顧林海,包括年底前會更新kotlin由淺入深系列教程,目前計劃在微信公眾號進行首發,如果大家想獲取最新教程,請關注微信公眾號,謝謝!

在上一節《Android小知識-剖析Retrofit中的網路請求介面》介紹了在Retrofit中通過動態代理獲取網路請求介面的代理類,在執行網路請求時會執行InvocationHandler的invoke方法執行網路請求的相關操作。

    public <T> T create(final Class<T> service) {
        ...
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
                new InvocationHandler() {
                    private final Platform platform = Platform.get();

                    @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                            throws Throwable {
                        //標記1
                        ServiceMethod<Object, Object> serviceMethod =
                                (ServiceMethod<Object, Object>) loadServiceMethod(method);
                        
                        OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                        return serviceMethod.adapt(okHttpCall);
                    }
                });
    }
複製程式碼

在標記1處呼叫了loadServiceMethod方法,這個方法在上一小節已經介紹過,從快取集合serviceMethodCache獲取對應方法的ServiceMethod,快取集合中存在就直接返回,否則建立ServiceMethod物件,這個ServiceMethod物件對應的是網路請求介面中的一個方法相關引數的封裝。

進入ServiceMethod類:

final class ServiceMethod<R, T> {
  ...

  private final okhttp3.Call.Factory callFactory;
  private final CallAdapter<R, T> callAdapter;

  private final HttpUrl baseUrl;
  private final Converter<ResponseBody, R> responseConverter;
  private final String httpMethod;
  private final String relativeUrl;
  private final Headers headers;
  private final MediaType contentType;
  ...
  private final ParameterHandler<?>[] parameterHandlers;

  ...
}
複製程式碼

這個ServiceMethod是個泛型類,內部定義了很多成員變數,callFactory是我們的網路請求工廠,用於生產網路請求的Call物件,這個Call在Retrofit中是對OkHttp Call的封裝;callAdapter是網路請求介面卡,主要將網路請求適用於不同的平臺,比如RxJava;baseUrl是網路基地址;responseConverter是資料轉換器,將伺服器返回的資料,比如json格式或者是xml格式等等,轉換成Java物件;httpMethod是網路請求的方法,比如GET、POST等等;relativeUrl是網路請求的相對地址,比如網路請求介面中註解GET後的http請求地址;headers表示Http的請求頭;contentType表示網路請求的body型別;parameterHandlers是方法引數的處理器。

ServiceMethod的例項是通過Builder來建立,進入ServiceMethod的Builder類:

    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
複製程式碼

Builder建構函式中對成員變數進行賦值,傳入retrofit和method分別賦值給Builder的成員變數retrofit和method,method表示網路請求方式,比如GET、POST等等,接著獲取方法當中的註解賦值給methodAnnotations,parameterTypes獲取的是網路請求介面中方法引數的型別,parameterAnnotationsArray獲取的是網路請求介面方法中註解的內容。

Builder的相關初始化完畢後,通過build方法建立ServiceMethod物件:

    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      ...
    }
複製程式碼

在build方法中通過createCallAdapter方法建立CallAdapter物件,createCallAdapter方法根據網路請求介面方法返回值和它的註解型別,從Retrofit中獲取對應網路請求介面卡。

createCallAdapter方法:

    private CallAdapter<T, R> createCallAdapter() {
      Type returnType = method.getGenericReturnType();
      if (Utils.hasUnresolvableType(returnType)) {
        throw methodError(
            "Method return type must not include a type variable or wildcard: %s", returnType);
      }
      if (returnType == void.class) {
        throw methodError("Service methods cannot return void.");
      }
      //標記1
      Annotation[] annotations = method.getAnnotations();
      try {
        //noinspection unchecked
        return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create call adapter for %s", returnType);
      }
    }
複製程式碼

通過method.getGenericReturnType()獲取網路請求介面方法返回的型別,之後在標記2處,通過method.getAnnotations()獲取網路請求介面方法中的註解,最後呼叫retrofit的callAdater方法返回CallAdapter物件,傳入的就是前面獲取的網路請求介面方法的返回型別和方法內的註解型別。

進入Retrofit的callAdapter方法:

  public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
  }
複製程式碼

繼續進入nextCallAdapter方法:

  public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
      Annotation[] annotations) {
    checkNotNull(returnType, "returnType == null");
    checkNotNull(annotations, "annotations == null");

    int start = callAdapterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
      CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }

    ...
    throw new IllegalArgumentException(builder.toString());
  }
複製程式碼

方法內前兩行做非空判斷,接著通過for迴圈遍歷callAdapterFactories網路請求介面卡工廠集合,看有沒有合適的網路請求介面卡工廠,如果沒有丟擲異常。

回到ServiceMethod的Builder類的build方法:

    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      ...
    }
複製程式碼

根據前面獲取到的網路請求介面卡之後,通過responseType方法獲取網路介面卡返回的資料型別。

    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      ...
      responseConverter = createResponseConverter();
      ...
    }
複製程式碼

接著通過createResponseConverter方法獲取資料解析器。

進入createResponseConverter方法:

    private Converter<ResponseBody, T> createResponseConverter() {
      Annotation[] annotations = method.getAnnotations();
      try {
        return retrofit.responseBodyConverter(responseType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create converter for %s", responseType);
      }
    }
複製程式碼

通過method的getAnnotations方法獲取網路請求介面中方法的註解,接著通過retrofit的responseBodyConverter方法獲取Converter物件。

responseBodyConverter方法:

  public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(null, type, annotations);
  }
複製程式碼

繼續點進nextResponseBodyConverter方法:

  public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
      @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
    ...
    int start = converterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = converterFactories.size(); i < count; i++) {
      Converter<ResponseBody, ?> converter =
          converterFactories.get(i).responseBodyConverter(type, annotations, this);
      if (converter != null) {
        return (Converter<ResponseBody, T>) converter;
      }
    }

    ...
    throw new IllegalArgumentException(builder.toString());
  }
複製程式碼

nextResponseBodyConverter方法內部遍歷資料轉換器工廠集合,從中獲取合適的資料轉換器,這裡預設獲取的是Gson的資料轉換器。

回到ServiceMethod的Builder類的build方法:

    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      ...
      responseConverter = createResponseConverter();
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      ...
    }
複製程式碼

拿到資料轉換器後,通過遍歷網路請求介面方法的註解,parseMethodAnnotation方法主要是對網路請求介面方法上面的註解型別進行判斷,同時根據註解型別對Builder的相關成員變數進行賦值。

回到ServiceMethod的Builder類的build方法:

    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      ...
      responseConverter = createResponseConverter();
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      ...
      //標記1
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        ...
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        ...

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
      ...
      return new ServiceMethod<>(this);
    }
複製程式碼

在標記1處,獲取方法內註解的長度,從而建立引數解析器陣列,接著遍歷網路請求介面方法內的註解,並通過parseParameter方法來解析引數,最後通過new建立ServiceMethod物件並將配置好引數的Builder物件傳遞過去。

最後總結下這個build方法,主要根據網路請求介面內方法的返回值型別和方法中的註解,來從網路請求介面卡工廠和資料轉換器工廠,分別獲取我們需要的網路請求介面卡和資料轉換器,然後根據引數上的註解獲取到網路請求所需要的相關引數。


838794-506ddad529df4cd4.webp.jpg

搜尋微信“顧林海”公眾號,定期推送優質文章。

相關文章