Retrofit源碼分析

Retrofit源碼分析

Retrofit簡介

是Square公司基於Okhttp封裝的一款網絡開源框架,簡化了對網絡的請求。

以下基於Retrofit2.1.0版本的分析,本文仿寫 碼老闆的博客https://zhuanlan.zhihu.com/p/35121326關於“Retrofit原理解析最簡潔的思路”。。

Retrofit使用

定義接口請求參數

public interface ApiService {

    @GET("app/{volumeId}/updateDefaultVolume")
    Call<ResponseBody> updateDefaultVolume(@Path("volumeId") String volumeId);
}

通過以上方式簡化了網絡請求,代碼也更直觀

實例化Retrofit

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("xxx")
        .build();

以上是最簡單的Retrofit初始化方式,採用了鏈式調用的設計

獲取接口實現類

ApiService apiService = retrofit.create(ApiService.class);
Call<ResponseBody> call = apiService.updateDefaultVolume("oo121o");

通過retrofit的create方法,獲取到接口的實現類,接下來再調用自己定義的方法,進行網絡請求。但是我們只定義了一個接口,並沒有方法體,請求方式和參數都還是註解,那麼方法體它是怎麼生成的,接下來源碼會具體進行講解

進行網絡請求

//同步網絡請求
Request request = call.request();
//異步網絡請求
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {

    }
});

以上就是retrofit的最簡單的網絡請求方法,由於是基於Okhttp的封裝,其網絡請求變得簡潔。接下來具體分析起原理

Retrofit原理分析

retrofit初始化

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com")
        .build();

首先看Builder()源碼

public Builder() {
  this(Platform.get());
}

this表示其調用自己的構造方法,如下

Builder(Platform platform) {
  this.platform = platform; //====1
  // Add the built-in converter factory first. This prevents overriding its behavior //but also
  // ensures correct behavior when using converters that consume all types.
  converterFactories.add(new BuiltInConverters());  //====2
}

1處就是簡單的賦值,2處是轉換器工廠添加各種類型轉換器(暫時是這樣理解),

點開上面的Platform.get()方法,

private static final Platform PLATFORM = findPlatform();//2

static Platform get() {
  return PLATFORM; //1
}

private static Platform findPlatform() {//3
  try {
    Class.forName("android.os.Build");
    if (Build.VERSION.SDK_INT != 0) {
      return new Android(); 
    }
  } catch (ClassNotFoundException ignored) {
  }
  try {
    Class.forName("java.util.Optional");
    return new Java8();
  } catch (ClassNotFoundException ignored) {
  }
  try {
    Class.forName("org.robovm.apple.foundation.NSObject");
    return new IOS();
  } catch (ClassNotFoundException ignored) {
  }
  return new Platform();
}

從上述代碼可以發現get()方法是典型的餓漢式單例,這樣寫的好處是簡單、線程安全、效率高、不會生成多個實例。1和2構成了典型的餓漢式單例。3就是判斷系統,根據系統然後實例化不同的平臺對象。這裏跟我們相關的只有實例化Android這塊,但是,在這不進行深究,略過。。。

baseUrl(“https://api.github.com”)源碼分析

點開baseUrl(“https://api.github.com”)方法,查看其實現

/**
 * Set the API base URL.
 *
 * @see #baseUrl(HttpUrl)
 */
public Builder baseUrl(String baseUrl) {
  checkNotNull(baseUrl, "baseUrl == null"); //1 
  HttpUrl httpUrl = HttpUrl.parse(baseUrl);  //2
  if (httpUrl == null) {
    throw new IllegalArgumentException("Illegal URL: " + baseUrl);
  }
  return baseUrl(httpUrl);  //3
}

1處是對baseUrl進行判空處理,這個沒啥可講的。

2處是對baseUrl進行解析,可以進去查看parse方法,裏面基本都是一些對路徑的規範化判定

3處傳入了一個HttpUrl對象,查看其實現,如下

public Builder baseUrl(HttpUrl baseUrl) {
  checkNotNull(baseUrl, "baseUrl == null");
  List<String> pathSegments = baseUrl.pathSegments();//獲取到路徑段列表
  if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { //路徑結尾必須以“/”結尾
    throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
  }
  this.baseUrl = baseUrl;
  return this;
}

這個方法主要是給baseUrl賦值,同時規範baseUrl的路徑展示形式,結尾必須以“/”結束。否則,會報錯。。

總結,由以上可知,baseUrl方法主要是對路徑進行了規範的判定,同時進行賦值。

build()方法源碼解析

public Retrofit build() {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }

  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }

  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }

  // Make a defensive copy of the adapters and add the default Call adapter.
  List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

  // Make a defensive copy of the converters.
  List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

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

這個方法主要是創建Retrofit的一個實例。並初始化一些參數對象。callFactory是okhttp的工廠實例,用於網絡請求的,converterFactories是轉換器工廠集合,adapterFactories是回調適配器工廠集合,callbackExecuto應該是回調接口實例,在這個方法中對這些對象進行了實例化,並最終返回Retrofit的實例。。。

接口實現類源碼分析

ApiService apiService = retrofit.create(ApiService.class);
Call<ResponseBody> call = apiService.updateDefaultVolume("oo121o");

通過調用Retrofit的create方法獲取到接口的實例,那麼爲什麼調用create方法會生成接口實例??點擊打開create方法源碼

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service); //驗證傳入的class是否標準的接口類
  if (validateEagerly) {
    eagerlyValidateMethods(service); //把接口類裏面的定義方法加入LinkedHashMap集合中
  }
  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, 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);
          }
          if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
          }
          ServiceMethod serviceMethod = loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}

最後一段代碼Proxy.newProxyInstance…採用了 動態代理 的設計模式,而且這個方法封裝得非常好,我麼只需要傳入相應的接口類,就能獲取到其實例。遵循了 迪米特原則(最少知道原則)。

代理也稱“委託”,分爲靜態代理和動態代理,代理模式也是常用的設計模式之一,具有方法增強、高擴展性的設計優勢。其設計就是限制對象的直接訪問。。動態代理是JDK提供的代理方式且只支持接口,在JVM虛擬機運行時動態生成一系列代理,主要通過Java提供的InvocationHanler類實現。寫一個類實現InvocationHanler接口,實現invoke方法,調用Proxy.newProxyInstance返回實例。

Retrofit在重寫這個方法時,主要做了三件事

1.判斷該接口類是不是一個Object.class,就直接返回方法原有的返回值

2.判斷該方法是否是DefaultMethod,

3.構建一個ServiceMethod對象和OkHttpCall對象,並通過serviceMethod.callAdapter.adapt(okHttpCall)將這兩個對象關聯起來。點開adapt,發現其是CallAdapter接口類的一個方法,如下

<R> T adapt(Call<R> call);

那這個方法是在那裏進行實現的呢,在上面講解build()源碼方法時,即Retrofit實例化時,有下面一段代碼:

List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

而adapterFactories其實就是CallAdapter的一個工廠類集,點開defaultCallAdapterFactory方法,如下

CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
  if (callbackExecutor != null) {
    return new ExecutorCallAdapterFactory(callbackExecutor);
  }
  return DefaultCallAdapterFactory.INSTANCE; //獲取到默認的CallAdapter的工廠類
}

//接下來打開DefaultCallAdapterFactory類,發現其是繼承自CallAdapter.Factory

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

  @Override
  public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }

    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public <R> Call<R> adapt(Call<R> call) {
        return call; //1在這裏CallAdapter的接口方法adapt被重寫,並返回Call的類型
      }
    };
  }
}

上述代碼看出,在Retrofit初始化實例的時候,CallAdapter接口方法adapt就已經被重寫了,並且 返回的Call的類型。

到這裏爲止,我們就知道了網絡請求結果返回的是Call類型。

請求參數以及請求方式的解析

在我們上面提到的create方法中有這麼一段代碼,如下

ServiceMethod serviceMethod = loadServiceMethod(method);

這段代碼主要就是對接口參數以及請求方式的解析,其中method表示接口方法,點開loadServiceMethod方法,查看其具體實現。

ServiceMethod loadServiceMethod(Method method) {//傳入一個方法體
  ServiceMethod result;
  synchronized (serviceMethodCache) { //採用同步鎖,保證訪問serviceMethodCache對象的唯一性
    result = serviceMethodCache.get(method); //從serviceMethodCache這個hashmap中取method
    if (result == null) {
      result = new ServiceMethod.Builder(this, method).build();
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

從以上代碼可知,其核心的代碼就是result = new ServiceMethod.Builder(this, method).build()這句,點開Builder(this, method).法,代碼如下

public Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit; 
  this.method = method;
  this.methodAnnotations = method.getAnnotations();//接口方法的註解,在Retrofit中爲請求方式
  this.parameterTypes = method.getGenericParameterTypes();//參數類型
  this.parameterAnnotationsArray = method.getParameterAnnotations();//參數註解數組
}

以上代碼主要是對一些對象進行了實例化,並沒有什麼核心操作,接下來看build()這個方法。代碼如下

public ServiceMethod build() {
  callAdapter = createCallAdapter();  //(1)
  responseType = callAdapter.responseType(); 
  if (responseType == Response.class || responseType == okhttp3.Response.class) {
    throw methodError("'"
        + Utils.getRawType(responseType).getName()
        + "' is not a valid response body type. Did you mean ResponseBody?");
  }
  responseConverter = createResponseConverter(); //(2)

  for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation); //(3)
  }

 ...省略代碼...

//(4)======
  int parameterCount = parameterAnnotationsArray.length;
  parameterHandlers = new ParameterHandler<?>[parameterCount];
  for (int p = 0; p < parameterCount; p++) {
    Type parameterType = parameterTypes[p];
    if (Utils.hasUnresolvableType(parameterType)) {
      throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
          parameterType);
    }

    Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
    if (parameterAnnotations == null) {
      throw parameterError(p, "No Retrofit annotation found.");
    }
	//(5)===
    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }

   ...省略代碼...
   
  return new ServiceMethod<>(this);
}

從上述代碼,並查看其相關源碼可知,感興趣的可以自行查看,

(1).初始化了一個可用的CallAdapter實例,循環遍歷adapterFactories工廠,取出可用的CallAdapter對象

(2).初始化了一個可用的Converter<ResponseBody, T>實例,循環遍歷converterFactories工廠,取出可用的Converter對象

(3).parseMethodAnnotation(annotation)用來解析註解的請求方式,這裏我們只講解GET請求方式,點開查看其源碼。其它 請求方式相似。如下所示

private void parseMethodAnnotation(Annotation annotation) {
  ...省略代碼...
  if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
  }
   ...省略代碼...
}

打開parseHttpMethodAndPath方法,查看其實現
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
      if (this.httpMethod != null) {
        throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
            this.httpMethod, httpMethod);
      }
      this.httpMethod = httpMethod;  // 賦值
      this.hasBody = hasBody; //賦值

      if (value.isEmpty()) {
        return;
      }

      // Get the relative URL path and existing query string, if present.
      //校驗value的值是否合法,規則就是不能有“?”如果有則需要使用@Query註解
      int question = value.indexOf('?');
      if (question != -1 && question < value.length() - 1) {
        // Ensure the query string does not have any named parameters.
        String queryParams = value.substring(question + 1);
        Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
        if (queryParamMatcher.find()) {
          throw methodError("URL query string \"%s\" must not have replace block. "
              + "For dynamic query parameters use @Query.", queryParams);
        }
      }

      this.relativeUrl = value;//賦值給相對路徑relativeUrl,相當於省略域名的URL
      this.relativeUrlParamNames = parsePathParameters(value);//解析路徑參數
      //到這裏,我們能得到app/{volumeId}/updateDefaultVolume這樣的一個路徑,大括號裏面的值就是我們需要賦值的參數。
    }

(4).先獲取註解數組parameterAnnotationsArray的長度parameterCount,然後遍歷循環parameterAnnotationsArray數組,獲取參數類型以及參數註解。

(5).把上面獲取的參數類型和註解放在同一個方法中進行解析,

parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);

parameterType:參數類型

parameterAnnotations:參數註解

通過parseParameter方法對parameterType和parameterAnnotations進行解析,並返回ParameterHandler<?>對象,賦值給對應的原始數組對象。那麼parseParameter到底是怎麼解析的呢,我們來打開parseParameter這個方法,

private ParameterHandler<?> parseParameter(
    int p, Type parameterType, Annotation[] annotations) {
  ParameterHandler<?> result = null;
  for (Annotation annotation : annotations) {
    ParameterHandler<?> annotationAction = parseParameterAnnotation(
        p, parameterType, annotations, annotation);
 ...省略代碼...
   }
 ...省略代碼...
  return result;
}


發現還是遍歷註解,且賦值。繼續打開parseParameterAnnotation方法,當我們查看parseParameterAnnotation( p, parameterType, annotations, annotation)這個方法時,發現其代碼量多達400行左右,且裏面邏輯大多一致,我們就以Path參數註解爲例講解。

private ParameterHandler<?> parseParameterAnnotation(
    int p, Type type, Annotation[] annotations, Annotation annotation) {
    
    ...省略代碼...
    
  if (annotation instanceof Path) {
   ...省略代碼...

    Path path = (Path) annotation;
    String name = path.value(); //獲取到參數名,即獲取@Path("volumeId")中的參數volumeId,
    validatePathName(p, name); //驗證參數名是否合法

    Converter<?, String> converter = retrofit.stringConverter(type, annotations);
    return new ParameterHandler.Path<>(name, converter, path.encoded());
  }

...省略代碼...

  return null; // Not a Retrofit annotation.
}

重點關注stringConverter(type, annotations)這個方法,點開stringConverter方法,

public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {
  checkNotNull(type, "type == null");
  checkNotNull(annotations, "annotations == null");

  for (int i = 0, count = converterFactories.size(); i < count; i++) {
    Converter<?, String> converter =
        converterFactories.get(i).stringConverter(type, annotations, this);
    if (converter != null) {
      //noinspection unchecked
      return (Converter<T, String>) converter;
    }
  }

  // Nothing matched. Resort to default converter which just calls toString().
  //noinspection unchecked
  return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
}

首先它會循環遍歷轉換器工廠converterFactories這個數組,以其能從converterFactories中獲取到Converter<?, String>對象,並返回。但是,繼續點開stringConverter(type, annotations, this)這個方法。

public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
  return null;
}

你會發現它一直是返回爲null的,所以stringConverter(type, annotations)這個方法會執行接下來的這個方法,(Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE,可以看出它應該調用的是一個靜態對象,點開ToStringConverter類,發現其是一個接口實現類,用來重寫Converter<Object, String>接口的convert方法,並返回一個字符串,這個字符串其實就是參數註解名。

static final class ToStringConverter implements Converter<Object, String> {
  static final ToStringConverter INSTANCE = new ToStringConverter();

  @Override public String convert(Object value) {
    return value.toString();
  }
}

那麼Converter<Object, String>這個接口,它是在哪裏進行實現的呢,接下來我們要講new ParameterHandler.Path<>(name, converter, path.encoded())這個方法,而Converter<Object, String>接口的實現就在這個方法中。點開Path,查看其源碼

static final class Path<T> extends ParameterHandler<T> {
  private final String name;
  private final Converter<T, String> valueConverter;
  private final boolean encoded;

  Path(String name, Converter<T, String> valueConverter, boolean encoded) {
    this.name = checkNotNull(name, "name == null");
    this.valueConverter = valueConverter;
    this.encoded = encoded;
  }

  @Override void apply(RequestBuilder builder, T value) throws IOException {
    if (value == null) {
      throw new IllegalArgumentException(
          "Path parameter \"" + name + "\" value must not be null.");
    }
    builder.addPathParam(name, valueConverter.convert(value), encoded);
  }
}

Path裏面首先市實例化了一些對象:參數名name,轉換器接口實例valueConverter,是否編碼encoded。

接下來重寫了ParameterHandler這個抽象類裏面的apply方法,在這個方法裏面有一個builder.addPathParam(name, valueConverter.convert(value), encoded)方法,其中有傳入一個參數 valueConverter.convert(value),valueConverter它是Converter<Object, String>接口的實例對象,而convert就是接口方法,所以,Converter<Object, String>接口的調用就是在這裏。然後,接下來,我們分析addPathParam這個方法用來幹啥的,點開addPathParam方法,發現它是對路徑進行了一個替換處理,

void addPathParam(String name, String value, boolean encoded) {
  if (relativeUrl == null) {
    // The relative URL is cleared when the first query parameter is set.
    throw new AssertionError();
  }
  relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));
}

這個方法把我們傳進來的值value按照編碼格式轉換,然後替換relativeUrl中的{name},構成一個有效的省略域名的URL。至此,URL的拼接已經完成!

總結:Retrofit採用動態代理實現了我們定義的網絡請求,並在代理實現方法invoke中創建了ServiceMethod對象,在構建這個對象的過程中,對註解的請求方式進行解析並得到了網絡請求方式HttpMethod,以及參數的註解分析,拼接成一個省略域名的路徑URL

Retrofit網絡請求

Retrofit的網絡請求其實在創建接口實例化的時候,就已經開始了,動態代理創建時,其實現方法invoke裏面會實例一個OkHttpCall對象okHttpCall,點開會發現OkHttpCall這個類是用來實現Call這個接口的,然後重寫了Call裏面的方法。這裏只分析接口Call的enqueue方法。即異步網絡請求方式。

OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);


這裏只展示enqueue方法
final class OkHttpCall<T> implements Call<T> {
 。。。
  OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
    this.serviceMethod = serviceMethod;
    this.args = args;
  }
  @Override public void enqueue(final Callback<T> callback) {
    if (callback == null) throw new NullPointerException("callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          failure = creationFailure = t;
        }
      }
    }
     。。。
  }
}

這裏關注call的創鍵時機,通過上述代碼可知,createRawCall方法它會返回一個call的對象,打開createRawCall查看其源碼。

private okhttp3.Call createRawCall() throws IOException {
  Request request = serviceMethod.toRequest(args);
  okhttp3.Call call = serviceMethod.callFactory.newCall(request);
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

createRawCall方法中首先,它會創建一個Http Request的請求對象request,接下來它會調用Call.Factory接口中的newCall方法,並返回一個Call對象,注意,Call.Factory接口中方法實現在Retrofit的build()方法中就已經被重寫了,如下build()方法中的一段,初始化OkHttpClient對象。。

okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
  callFactory = new OkHttpClient();
}

OkHttpClient重寫了Call.Factory接口
public class OkHttpClient implements Cloneable, Call.Factory {
。。。
}

總結:Retrofit主要是在create方法中採用動態代理模式實現接口方法,這個過程構建了一個ServiceMethod對象,根據方法註解獲取請求方式,參數類型和參數註解拼接請求的鏈接,當一切都準備好之後會把數據添加到Retrofit的RequestBuilder中。然後當我們主動發起網絡請求的時候會調用okhttp發起網絡請求,okhttp的配置包括請求方式,URL等在Retrofit的RequestBuilder的build()方法中實現,併發起真正的網絡請求

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章