retrofit 原始碼分析

philadelphia發表於2019-01-19

簡介

retrofit是square出品的一個優秀的網路框架,注意,不是一個網路引擎。它的定位和Volley是一樣的。

它完成了封裝請求,執行緒切換,資料裝換等一系列工作,如果自己有能力也可以封裝一個這種框架,本質上是沒有區別的。

retrofit使用的網路引擎是OkHttp.

而OKHttp和HTTPClient,HttpUrlConnection是一個級別的。

使用

//1 建立網路請求介面類
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

//2 建立Retrofit例項物件
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
     .addConverterFactory(GsonConverterFactory.create())
     .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();

//3 通過動態代理建立網路介面代理物件
GitHubService service = retrofit.create(GitHubService.class);

//4 獲取Call物件
Call<List<Repo>> repos = service.listRepos("octocat");

//5    執行同步請求或非同步請求
repos.execute();
repos.enqueue(callback)

Retrofit

Retrofit也是使用Build模式建立的。

螢幕快照 2019-01-11 下午3.12.35

builder類有這些方法。從圖表可以看出,我們可以呼叫client方法傳入一個我們自定義的OkhttpClient,

呼叫baseUrl方法傳入Host,最後調動build方法生成一個Retrofit 物件

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

   //如果沒有設定callFactory物件,系統自動生成一個OkhttpClient物件.因為OKHttpclient實現了            Call.Factory介面
   // public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }

  //如果沒有設定callbackExecutor,系統自動生成一個,platform.defaultCallbackExecutor,這個platform是無參構造方法裡呼叫Platform.get()方法得到的。
    /** 
    public Builder() {
      this(Platform.get());
    }**/
    
  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);
}

Platform

class Platform {
  private static final Platform PLATFORM = findPlatform();

  static Platform get() {
    return PLATFORM;
  }

  private static Platform findPlatform() {
    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 {
        //怎麼還有IOS程式碼呢?
      Class.forName("org.robovm.apple.foundation.NSObject");
      return new IOS();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
  }

  Executor defaultCallbackExecutor() {
    return null;
  }

  CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
    if (callbackExecutor != null) {
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }
    return DefaultCallAdapterFactory.INSTANCE;
  }

  boolean isDefaultMethod(Method method) {
    return false;
  }

  Object invokeDefaultMethod(Method method, Class<?> declaringClass, Object object, Object... args)
      throws Throwable {
    throw new UnsupportedOperationException();
  }

  static class Android extends Platform {
    @Override public Executor defaultCallbackExecutor() {
      return new MainThreadExecutor();
    }

    @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }

    static class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override public void execute(Runnable r) {
        handler.post(r);
      }
    }
  }
}

Retrofit 要求必須將請求API寫到一個interface介面檔案裡,這是動態代理特性要求的。

從介面檔案裡我們可以看到,我們將每個請求用這種形式表達

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}        

從介面檔案我們可以看出,一個請求介面被各種註解所表示。

我們知道一個方法有一下關鍵欄位組成

首先一個方法必須有描述符,返回值,方法名,引數型別,引數構成。

那我們用一個方法表示一個http請求需要哪些東西呢?

Http請求,首先我們得知道是GET請求還是POST請求,

然後就是請求頭資訊,請求路徑,查詢引數等等。

POST請求還需要Body。

Retrofit 已經提供了足夠的註解來表示一個方法。

Retrofit的核心思想AOP,面向切面變成,通過動態代理的反射,將介面檔案裡的每個方法記性處理,也就是分析該方法的註解生成一個ServiceMethod類。

Retrofit 裡有個關鍵的類,ServiceMethod

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(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, 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 serviceMethod = loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}

從第3步我們可以看出create方法的實現就是使用了動態代理,在執行時生成了GitHubService物件。

//建立ServiceMethod物件
ServiceMethod serviceMethod = loadServiceMethod(method);

ServiceMethod loadServiceMethod(Method method) {
  ServiceMethod result;
  synchronized (serviceMethodCache) {
  //先從換從中取改方法對應的ServiceMethod物件,如果為null就構建一個ServiceMethod物件並存入到map中,如果不為null直接返回
    result = serviceMethodCache.get(method);
    if (result == null) {
      result = new ServiceMethod.Builder(this, method).build();
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

我們可以看到loadServiceMethod(Method method)方法返回了一個ServiceMethod物件
這個serviceMethodCache物件是Retrofit的一個欄位,是一個Map集合。

private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();

將介面檔案裡每個方法轉換為一個ServiceMethod物件後放入改map中作為快取,下次呼叫該方法後就不用再次解析改方法物件了,直接從改map裡去以方法為key去取對應的ServiceMethod就行了。666

接下來看一下ServiceMethod物件的構造

ServiceMethod

final class ServiceMethod<T> {
  // Upper and lower characters, digits, underscores, and hyphens, starting with a character.
  static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
  static final Pattern PARAM_URL_REGEX = Pattern.compile("\{(" + PARAM + ")\}");
  static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);

  final okhttp3.Call.Factory callFactory;
  final CallAdapter<?> callAdapter;

  private final HttpUrl baseUrl; 主機地址
  private final Converter<ResponseBody, T> responseConverter;
  private final String httpMethod; 
  private final String relativeUrl; 相對路徑
  private final Headers headers;    請求頭部資訊
  private final MediaType contentType; 請求引數型別
  private final boolean hasBody;  是否有請求體
  private final boolean isFormEncoded; 是否是格式化的表單
  private final boolean isMultipart; 是不是分塊
  private final ParameterHandler<?>[] parameterHandlers;

  ServiceMethod(Builder<T> builder) {
    this.callFactory = builder.retrofit.callFactory();
    this.callAdapter = builder.callAdapter;
    this.baseUrl = builder.retrofit.baseUrl();
    this.responseConverter = builder.responseConverter;
    this.httpMethod = builder.httpMethod;
    this.relativeUrl = builder.relativeUrl;
    this.headers = builder.headers;
    this.contentType = builder.contentType;
    this.hasBody = builder.hasBody;
    this.isFormEncoded = builder.isFormEncoded;
    this.isMultipart = builder.isMultipart;
    this.parameterHandlers = builder.parameterHandlers;
  }
}

ServiceMethod是採用Builder模式建立的。

static final class Builder<T> {
  final Retrofit retrofit;
  final Method method;         //介面裡生命的方法
  final Annotation[] methodAnnotations;  //方法的註解,get/post/header之類的
  final Annotation[][] parameterAnnotationsArray; //方法的引數註解陣列,二維陣列
  final Type[] parameterTypes;  //方法的引數陣列

  Type responseType;
  boolean gotField;
  boolean gotPart;
  boolean gotBody;
  boolean gotPath;
  boolean gotQuery;
  boolean gotUrl;
  String httpMethod;
  boolean hasBody;
  boolean isFormEncoded;
  boolean isMultipart;
  String relativeUrl;
  Headers headers;
  MediaType contentType;
  Set<String> relativeUrlParamNames;
  ParameterHandler<?>[] parameterHandlers;
  Converter<ResponseBody, T> responseConverter;
  CallAdapter<?> callAdapter;

  public 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物件。

首先解析方法物件,將其註解和引數註解放到對應的陣列裡。

首先在構造方法裡獲取該方法的註解,方法的引數,以及每個引數的註解。

關鍵就在build方法,在build方法裡對方法做了一個徹底的分解

public ServiceMethod build() {
  //1 處理返回結果,做一定的轉換
  callAdapter = createCallAdapter();
  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);
  }
    //如果httpMethod為null,即沒有使用方法型別註解修飾,丟擲異常進行提示
  if (httpMethod == null) {
    throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
  }
//如果沒有請求體,即使用了GET,HEAD,DELETE,OPTIONS等所修飾,即不涉及到表單的提交,但是同時使用了Multipart,或者FormUrlEncoded所修飾,就報錯
  if (!hasBody) {
    if (isMultipart) {
      throw methodError(
          "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
    }
    if (isFormEncoded) {
      throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
          + "request body (e.g., @POST).");
    }
  }

  //3提取方法的引數
  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.");
    }

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

  //相對路徑為null且gotURL為false的話,丟擲異常,因為沒有相對路徑無法請求。
  if (relativeUrl == null && !gotUrl) {
    throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
  }
  //沒有使用@FormUrlEncoded,@Multipart主機並且hasBody為false,但是gotBody為true,丟擲異常,提示
    Non-Body型別的HTTP method 不能引數不能使用@Body註解
  if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
    throw methodError("Non-body HTTP method cannot contain @Body.");
  }
  //使用@FormUrlEncoded修飾的方法中的引數至少有一個引數被@Field註解修飾
  if (isFormEncoded && !gotField) {
    throw methodError("Form-encoded method must contain at least one @Field.");
  }
    
  //使用@Multipart修飾的方法中的引數至少有一個引數被@Part註解修飾
  if (isMultipart && !gotPart) {
    throw methodError("Multipart method must contain at least one @Part.");
  }

 //4 當前Builder物件初始化完畢,可以用來夠著ServiceMethod物件。
  return new ServiceMethod<>(this);
}

處理返回結果

private CallAdapter<?> createCallAdapter() {
  //獲取方法的返回結果,如果有不能解析的型別則丟擲異常,也就是說介面中定義的方法的返回值不能使用泛型
  Type returnType = method.getGenericReturnType();
  if (Utils.hasUnresolvableType(returnType)) {
    throw methodError(
        "Method return type must not include a type variable or wildcard: %s", returnType);
  }
   //介面裡的方法不能返回void
  if (returnType == void.class) {
    throw methodError("Service methods cannot return void.");
  }
  Annotation[] annotations = method.getAnnotations();
  try {
    return retrofit.callAdapter(returnType, annotations);
  } catch (RuntimeException e) { // Wide exception range because factories are user code.
    //使用者自定義的Adapter可能不能正確的處理返回結果,這時候丟擲異常
    throw methodError(e, "Unable to create call adapter for %s", returnType);
  }
}

解析方法註解

1處處理方法的註解,就是先處理GET/POST/Header等註解資訊

private void parseMethodAnnotation(Annotation annotation) {
  if (annotation instanceof DELETE) {
    parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
  } else if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
  } else if (annotation instanceof HEAD) {
    parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
    if (!Void.class.equals(responseType)) {
      throw methodError("HEAD method must use Void as response type.");
    }
  } else if (annotation instanceof PATCH) {
    parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
  } else if (annotation instanceof POST) {
    parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
  } else if (annotation instanceof PUT) {
    parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
  } else if (annotation instanceof OPTIONS) {
    parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
  } else if (annotation instanceof HTTP) {
    HTTP http = (HTTP) annotation;
    parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
  } else if (annotation instanceof retrofit2.http.Headers) {
  headers註解
    String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
    if (headersToParse.length == 0) {
      throw methodError("@Headers annotation is empty.");
    }
    headers = parseHeaders(headersToParse);
  } else if (annotation instanceof Multipart) {//如果是Multipart註解
    if (isFormEncoded) {
    //如果同時使用了FormUrlEncoded註解報錯
      throw methodError("Only one encoding annotation is allowed.");
    }
    isMultipart = true;
  } else if (annotation instanceof FormUrlEncoded) {
    if (isMultipart) {
    //如果同時使用了Multipart註解報錯,從這我們可以看出一個方法不能同時被Multipart和FormUrlEncoded所修飾
      throw methodError("Only one encoding annotation is allowed.");
    }
    isFormEncoded = true;
  }
}

然後根據具體的註解型別,在做進一步的處理,這裡主要分析GET/POST/HEADER/ 等註解

@GET

else if (annotation instanceof GET) {
  parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} 

get型別的請求,沒有請求體

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
 //如果該Builder已經有HTTPMethod了就不能改變了,直接拋異常
    if (this.httpMethod != null) {
    throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
        this.httpMethod, httpMethod);
  }
  //將HTTPMethod賦值給httpMethod物件,Get、Post、Delete等
  this.httpMethod = httpMethod;
  this.hasBody = hasBody;//是否有請求體

    //如果value為null,返回,因為value引數的值其實就是relativeURL。所以不能為null
  if (value.isEmpty()) {
    return;
  }

  // Get the relative URL path and existing query string, if present.
  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()) {
        //如果在value裡面找到裡查詢引數的話,丟擲異常。因為查詢引數可以使用@Query註解來動態配置。
      throw methodError("URL query string "%s" must not have replace block. "
          + "For dynamic query parameters use @Query.", queryParams);
    }
  }

  this.relativeUrl = value; //將value賦值給relativeUrl
  this.relativeUrlParamNames = parsePathParameters(value); //獲取value裡面的path佔位符,如果有的話
}

再來看下解析value裡的path佔位符的方法。

/**
獲取已知URI裡面的路徑集合,如果一個引數被使用了兩次,它只會在set中出現一次,好拗口啊,使用LinkedHashSet來儲存path引數集合,保證了路徑引數的順序。
 * Gets the set of unique path parameters used in the given URI. If a parameter is used twice
 * in the URI, it will only show up once in the set.
 */
static Set<String> parsePathParameters(String path) {
  Matcher m = PARAM_URL_REGEX.matcher(path);
  Set<String> patterns = new LinkedHashSet<>();
  while (m.find()) {
    patterns.add(m.group(1));
  }
  return patterns;
}

至此,GET方法的相關的註解分析完畢

@POST

else if (annotation instanceof POST) {
  parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} 

POST型別的請求,沒有請求體。所以hasBody引數為true。

parseHttpMethodAndPath()方法已將在GET方法裡面分析過了,這裡面都一樣。

其他的請求型別也是大同小異。

然後接著分析方法的Header註解

@Headers

else if (annotation instanceof retrofit2.http.Headers) {
 //   首先獲取Headers註解的值,是一個字串陣列。
  String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
    如果header註解長度為0,丟擲異常,所以使用了header註解必須設定值,不能存在空的header
  if (headersToParse.length == 0) {
    throw methodError("@Headers annotation is empty.");
  }
    處理header資訊,我猜肯定是一個map
  headers = parseHeaders(headersToParse);

啊,居然不是,666.因為header不是KV結構的資料型別,而是一個key可以對應多個值。理論上可以使用Map<String,Set<String>>表示。

private Headers parseHeaders(String[] headers) {
  Headers.Builder builder = new Headers.Builder();
  for (String header : headers) {
  // header以“:"分割,前面是key,後面是value
    int colon = header.indexOf(`:`);
    if (colon == -1 || colon == 0 || colon == header.length() - 1) {
    //header必須是key:value格式表示,不然報錯
      throw methodError(
          "@Headers value must be in the form "Name: Value". Found: "%s"", header);
    }
    String headerName = header.substring(0, colon); //key值
    String headerValue = header.substring(colon + 1).trim(); //value值,必須是一個陣列,艹,又猜錯了。
    if ("Content-Type".equalsIgnoreCase(headerName)) {
    //遇到"Content-Type"欄位。還需要獲得具體的MediaType。
      MediaType type = MediaType.parse(headerValue);
      if (type == null) {
      //如果mediaType為null。丟擲一個type畸形的錯誤。
        throw methodError("Malformed content type: %s", headerValue);
      }
      contentType = type;
    } else {
    將header的key和value加入到Builder裡面。
      builder.add(headerName, headerValue);
    }
  }
  最後呼叫build方法生成一個Header對愛。
  return builder.build();
}
/**
 * Add a header with the specified name and value. Does validation of header names and values.
 */
public Builder add(String name, String value) {
  checkNameAndValue(name, value);
  return addLenient(name, value);
}
Builder addLenient(String name, String value) {
  namesAndValues.add(name);
  namesAndValues.add(value.trim());
  return this;
}
final List<String> namesAndValues = new ArrayList<>(20);

namesAndValues是Header.Builder類的一種子段。可見在Builder內部header資訊是按照key/value異常放到一個String集合裡面的。為什麼不放到一個Map裡面呢,不懂。

總之,最後就是講方法的Headers註解資訊提取完畢。

處理方法引數

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);
  }
//獲取第p個引數的註解陣列,如果沒有註解丟擲異常,可見,使用了Retrofit,介面方法中每個引數都必須使用註解進行修飾。
  Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
  if (parameterAnnotations == null) {
    throw parameterError(p, "No Retrofit annotation found.");
  }

   //解析方法中的引數,存入parameterHandlers[]陣列中。
  parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}

引數校驗

Utils.hasUnresolvableType(parameterType),這個方法是對引數的型別做個校驗。

static boolean hasUnresolvableType(Type type) {
  //如果引數是引用資料型別,返回false,可見,介面定義中方法的引數只能是基本資料型別
  if (type instanceof Class<?>) {
    return false;
  }
  //如果引數是泛型
  if (type instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) type;
    //去除泛型類中的實際型別,遍歷
    for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
    //如果有一個泛型引數是基本資料型別,返回true,都不是返回false
      if (hasUnresolvableType(typeArgument)) {
        return true;
      }
    }
    return false;
  }
  //如果引數是泛型陣列型別
  if (type instanceof GenericArrayType) {
    return hasUnresolvableType(((GenericArrayType) type).getGenericComponentType());
  }
  if (type instanceof TypeVariable) {
    return true;
  }
  if (type instanceof WildcardType) {
    return true;
  }
  String className = type == null ? "null" : type.getClass().getName();
  throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
      + "GenericArrayType, but <" + type + "> is of type " + className);
}

解析引數

parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
private ParameterHandler<?> parseParameter(
        int p, Type parameterType, Annotation[] annotations) {
      ParameterHandler<?> result = null;
    //遍歷引數的註解陣列,呼叫parseParameterAnnotation()
      for (Annotation annotation : annotations) {
        ParameterHandler<?> annotationAction = parseParameterAnnotation(
            p, parameterType, annotations, annotation);
        //如果該註解沒有返回,則解析下一個註解
        if (annotationAction == null) {
          continue;
        }

        if (result != null) {
          throw parameterError(p, "Multiple Retrofit annotations found, only one allowed.");
        }
        
        result = annotationAction; //將解析的結果賦值給Result
      }

    //如果註解為null,丟擲異常。這個地方永遠不會呼叫,因為在獲取註解陣列之前就做過判斷了,如果註解陣列為null,直接拋異常,Line197-Line200 in ServiceMethod.Builder中
      if (result == null) {
        throw parameterError(p, "No Retrofit annotation found.");
      }

      return result;
    }

獲取引數註解資訊

再來看看parseParameterAnnotation()方法,內容略多

private ParameterHandler<?> parseParameterAnnotation(
    int p, Type type, Annotation[] annotations, Annotation annotation) {
  if (annotation instanceof Url) {
      //如果使用了Url註解,
    if (gotUrl) {
        //如果gotUrl為true,因為gotURL預設為false,說明之前處理過Url註解了,丟擲多個@Url註解異常
      throw parameterError(p, "Multiple @Url method annotations found.");
    }
    if (gotPath) {
        //如果gotPath為true,丟擲異常,說明@Path註解不能和@Url註解一起使用
      throw parameterError(p, "@Path parameters may not be used with @Url.");
    }
    if (gotQuery) {
        //如果gotQuery為true,丟擲異常,說明@Url註解不能用在@Query註解後面
      throw parameterError(p, "A @Url parameter must not come after a @Query");
    }
    if (relativeUrl != null) {
        //如果relativeUrl不為null,丟擲異常,說明使用了@Url註解,relativeUrl必須為null
      throw parameterError(p, "@Url cannot be used with @%s URL", httpMethod);
    }

    gotUrl = true;
      
----------------------------------------------------------------------------------------    
      //如果引數型別是HttpURL,String,URI或者引數型別是“android.net.Uri",返回ParameterHandler.RelativeUrl(),實際是交由這個類處理
    if (type == HttpUrl.class
        || type == String.class
        || type == URI.class
        || (type instanceof Class && "android.net.Uri".equals(((Class<?>) type).getName()))) {
      return new ParameterHandler.RelativeUrl();
    } else {
        //不然就丟擲異常,也就是說@Url註解必須使用在okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri 這幾種型別的引數上。
      throw parameterError(p,
          "@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type.");
    }
------------------------------------------------------------------------------------------
  } else if (annotation instanceof Path) { //@Path註解
     //如果gotQuery為true。丟擲異常,因為@Path修飾的引數是路徑的佔位符。不是查詢引數,不能使用@Query註解修飾
    if (gotQuery) {
      throw parameterError(p, "A @Path parameter must not come after a @Query.");
    }
    if (gotUrl) {
      throw parameterError(p, "@Path parameters may not be used with @Url.");
    }
      //如果相對路徑為null,那@path註解也就無意義了。
    if (relativeUrl == null) {
      throw parameterError(p, "@Path can only be used with relative url on @%s", httpMethod);
    }
    gotPath = true;

    Path path = (Path) annotation;
    String name = path.value(); //獲取@Path註解的值
    validatePathName(p, name); //對改值進行校驗,1該value必須是合法字元,2:該相對路徑必須包含相應的佔位符
 
      //然後將改引數的所有註解進行處理,最終呼叫ParameterHandler.Path進行處理。
    Converter<?, String> converter = retrofit.stringConverter(type, annotations);
    return new ParameterHandler.Path<>(name, converter, path.encoded());

  } else if (annotation instanceof Query) { //Query註解,看不太懂,最後也是呼叫ParameterHandler.Query進行處理
    Query query = (Query) annotation;
    String name = query.value();
    boolean encoded = query.encoded();

    Class<?> rawParameterType = Utils.getRawType(type);
    gotQuery = true;
    if (Iterable.class.isAssignableFrom(rawParameterType)) {
      if (!(type instanceof ParameterizedType)) {
        throw parameterError(p, rawParameterType.getSimpleName()
            + " must include generic type (e.g., "
            + rawParameterType.getSimpleName()
            + "<String>)");
      }
      ParameterizedType parameterizedType = (ParameterizedType) type;
      Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
      Converter<?, String> converter =
          retrofit.stringConverter(iterableType, annotations);
      return new ParameterHandler.Query<>(name, converter, encoded).iterable();
    } else if (rawParameterType.isArray()) {
      Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
      Converter<?, String> converter =
          retrofit.stringConverter(arrayComponentType, annotations);
      return new ParameterHandler.Query<>(name, converter, encoded).array();
    } else {
      Converter<?, String> converter =
          retrofit.stringConverter(type, annotations);
      return new ParameterHandler.Query<>(name, converter, encoded);
    }

  } else if (annotation instanceof QueryMap) {
    Class<?> rawParameterType = Utils.getRawType(type);
    if (!Map.class.isAssignableFrom(rawParameterType)) {
      throw parameterError(p, "@QueryMap parameter type must be Map.");
    }
    Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
    if (!(mapType instanceof ParameterizedType)) {
      throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
    }
    ParameterizedType parameterizedType = (ParameterizedType) mapType;
    Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
    if (String.class != keyType) {
      throw parameterError(p, "@QueryMap keys must be of type String: " + keyType);
    }
    Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
    Converter<?, String> valueConverter =
        retrofit.stringConverter(valueType, annotations);

    return new ParameterHandler.QueryMap<>(valueConverter, ((QueryMap) annotation).encoded());

  } else if (annotation instanceof Header) {
    Header header = (Header) annotation;
    String name = header.value();

    Class<?> rawParameterType = Utils.getRawType(type);
    if (Iterable.class.isAssignableFrom(rawParameterType)) {
      if (!(type instanceof ParameterizedType)) {
        throw parameterError(p, rawParameterType.getSimpleName()
            + " must include generic type (e.g., "
            + rawParameterType.getSimpleName()
            + "<String>)");
      }
      ParameterizedType parameterizedType = (ParameterizedType) type;
      Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
      Converter<?, String> converter =
          retrofit.stringConverter(iterableType, annotations);
      return new ParameterHandler.Header<>(name, converter).iterable();
    } else if (rawParameterType.isArray()) {
      Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
      Converter<?, String> converter =
          retrofit.stringConverter(arrayComponentType, annotations);
      return new ParameterHandler.Header<>(name, converter).array();
    } else {
      Converter<?, String> converter =
          retrofit.stringConverter(type, annotations);
      return new ParameterHandler.Header<>(name, converter);
    }

  } else if (annotation instanceof HeaderMap) {
    Class<?> rawParameterType = Utils.getRawType(type);
    if (!Map.class.isAssignableFrom(rawParameterType)) {
      throw parameterError(p, "@HeaderMap parameter type must be Map.");
    }
    Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
    if (!(mapType instanceof ParameterizedType)) {
      throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
    }
    ParameterizedType parameterizedType = (ParameterizedType) mapType;
    Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
    if (String.class != keyType) {
      throw parameterError(p, "@HeaderMap keys must be of type String: " + keyType);
    }
    Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
    Converter<?, String> valueConverter =
        retrofit.stringConverter(valueType, annotations);

    return new ParameterHandler.HeaderMap<>(valueConverter);

  } else if (annotation instanceof Field) {
    if (!isFormEncoded) {
      throw parameterError(p, "@Field parameters can only be used with form encoding.");
    }
    Field field = (Field) annotation;
    String name = field.value();
    boolean encoded = field.encoded();

    gotField = true;

    Class<?> rawParameterType = Utils.getRawType(type);
    if (Iterable.class.isAssignableFrom(rawParameterType)) {
      if (!(type instanceof ParameterizedType)) {
        throw parameterError(p, rawParameterType.getSimpleName()
            + " must include generic type (e.g., "
            + rawParameterType.getSimpleName()
            + "<String>)");
      }
      ParameterizedType parameterizedType = (ParameterizedType) type;
      Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
      Converter<?, String> converter =
          retrofit.stringConverter(iterableType, annotations);
      return new ParameterHandler.Field<>(name, converter, encoded).iterable();
    } else if (rawParameterType.isArray()) {
      Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
      Converter<?, String> converter =
          retrofit.stringConverter(arrayComponentType, annotations);
      return new ParameterHandler.Field<>(name, converter, encoded).array();
    } else {
      Converter<?, String> converter =
          retrofit.stringConverter(type, annotations);
      return new ParameterHandler.Field<>(name, converter, encoded);
    }

  } else if (annotation instanceof FieldMap) {
    if (!isFormEncoded) {
      throw parameterError(p, "@FieldMap parameters can only be used with form encoding.");
    }
    Class<?> rawParameterType = Utils.getRawType(type);
    if (!Map.class.isAssignableFrom(rawParameterType)) {
      throw parameterError(p, "@FieldMap parameter type must be Map.");
    }
    Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
    if (!(mapType instanceof ParameterizedType)) {
      throw parameterError(p,
          "Map must include generic types (e.g., Map<String, String>)");
    }
    ParameterizedType parameterizedType = (ParameterizedType) mapType;
    Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
    if (String.class != keyType) {
      throw parameterError(p, "@FieldMap keys must be of type String: " + keyType);
    }
    Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
    Converter<?, String> valueConverter =
        retrofit.stringConverter(valueType, annotations);

    gotField = true;
    return new ParameterHandler.FieldMap<>(valueConverter, ((FieldMap) annotation).encoded());

  } else if (annotation instanceof Part) {
    if (!isMultipart) {
      throw parameterError(p, "@Part parameters can only be used with multipart encoding.");
    }
    Part part = (Part) annotation;
    gotPart = true;

    String partName = part.value();
    Class<?> rawParameterType = Utils.getRawType(type);
    if (partName.isEmpty()) {
      if (Iterable.class.isAssignableFrom(rawParameterType)) {
        if (!(type instanceof ParameterizedType)) {
          throw parameterError(p, rawParameterType.getSimpleName()
              + " must include generic type (e.g., "
              + rawParameterType.getSimpleName()
              + "<String>)");
        }
        ParameterizedType parameterizedType = (ParameterizedType) type;
        Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
        if (!MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(iterableType))) {
          throw parameterError(p,
              "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
        }
        return ParameterHandler.RawPart.INSTANCE.iterable();
      } else if (rawParameterType.isArray()) {
        Class<?> arrayComponentType = rawParameterType.getComponentType();
        if (!MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) {
          throw parameterError(p,
              "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
        }
        return ParameterHandler.RawPart.INSTANCE.array();
      } else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
        return ParameterHandler.RawPart.INSTANCE;
      } else {
        throw parameterError(p,
            "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
      }
    } else {
      Headers headers =
          Headers.of("Content-Disposition", "form-data; name="" + partName + """,
              "Content-Transfer-Encoding", part.encoding());

      if (Iterable.class.isAssignableFrom(rawParameterType)) {
        if (!(type instanceof ParameterizedType)) {
          throw parameterError(p, rawParameterType.getSimpleName()
              + " must include generic type (e.g., "
              + rawParameterType.getSimpleName()
              + "<String>)");
        }
        ParameterizedType parameterizedType = (ParameterizedType) type;
        Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
        if (MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(iterableType))) {
          throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
              + "include a part name in the annotation.");
        }
        Converter<?, RequestBody> converter =
            retrofit.requestBodyConverter(iterableType, annotations, methodAnnotations);
        return new ParameterHandler.Part<>(headers, converter).iterable();
      } else if (rawParameterType.isArray()) {
        Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
        if (MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) {
          throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
              + "include a part name in the annotation.");
        }
        Converter<?, RequestBody> converter =
            retrofit.requestBodyConverter(arrayComponentType, annotations, methodAnnotations);
        return new ParameterHandler.Part<>(headers, converter).array();
      } else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
        throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
            + "include a part name in the annotation.");
      } else {
        Converter<?, RequestBody> converter =
            retrofit.requestBodyConverter(type, annotations, methodAnnotations);
        return new ParameterHandler.Part<>(headers, converter);
      }
    }

  } else if (annotation instanceof PartMap) {
    if (!isMultipart) {
      throw parameterError(p, "@PartMap parameters can only be used with multipart encoding.");
    }
    gotPart = true;
    Class<?> rawParameterType = Utils.getRawType(type);
    if (!Map.class.isAssignableFrom(rawParameterType)) {
      throw parameterError(p, "@PartMap parameter type must be Map.");
    }
    Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
    if (!(mapType instanceof ParameterizedType)) {
      throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
    }
    ParameterizedType parameterizedType = (ParameterizedType) mapType;

    Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
    if (String.class != keyType) {
      throw parameterError(p, "@PartMap keys must be of type String: " + keyType);
    }

    Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
    if (MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(valueType))) {
      throw parameterError(p, "@PartMap values cannot be MultipartBody.Part. "
          + "Use @Part List<Part> or a different value type instead.");
    }

    Converter<?, RequestBody> valueConverter =
        retrofit.requestBodyConverter(valueType, annotations, methodAnnotations);

    PartMap partMap = (PartMap) annotation;
    return new ParameterHandler.PartMap<>(valueConverter, partMap.encoding());

  } else if (annotation instanceof Body) {
    if (isFormEncoded || isMultipart) {
      throw parameterError(p,
          "@Body parameters cannot be used with form or multi-part encoding.");
    }
    if (gotBody) {
      throw parameterError(p, "Multiple @Body method annotations found.");
    }

    Converter<?, RequestBody> converter;
    try {
      converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations);
    } catch (RuntimeException e) {
      // Wide exception range because factories are user code.
      throw parameterError(e, p, "Unable to create @Body converter for %s", type);
    }
    gotBody = true;
    return new ParameterHandler.Body<>(converter);
  }

  return null; // Not a Retrofit annotation.找不到該註解
}

從上面可以看出,改立引數註解的套路就是:先判斷該註解的型別,然後使用策略模式分別呼叫ParameterHandler裡對應的子類來處理

寫到這裡我已經暈了。暈暈乎乎好舒服

@Header

使用場景

有時候我們需要動態的設定請求header中的某個請求頭的值,這個時候就可以使用@Header來修飾個引數。

最終都是講header裡的資訊提取到Request裡面

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

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

  @Override void apply(RequestBuilder builder, T value) throws IOException {
    if (value == null) return; // Skip null values.
    builder.addHeader(name, valueConverter.convert(value));
  }
}
void addHeader(String name, String value) {
  if ("Content-Type".equalsIgnoreCase(name)) {
    MediaType type = MediaType.parse(value);
    if (type == null) {
      throw new IllegalArgumentException("Malformed content type: " + value);
    }
    contentType = type;
  } else {
    requestBuilder.addHeader(name, value);
  }
}

呼叫requestBuilder.addHeader()方法。

這個requestBuilder是OKHttp中Request的內部靜態類Builder類的一個物件。

private final Request.Builder requestBuilder;

從中我們可以看出最後將@Header註釋的引數的值解析後新增到Request物件中的Header資訊裡。

@Path

使用場景

有時候請求路徑是不定的,即請求路徑裡的某個segment是變化的,也就是需要我們使用引數來動態的改變,這個時候我們就需要使用@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);
  }
}
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();
  }
   //將佔位符”{name}”使用value替換
  relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));
}

@Query

使用場景

@Query用來修飾介面方法中的查詢欄位

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

  Query(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) return; // Skip null values.
    builder.addQueryParam(name, valueConverter.convert(value), encoded);
  }
}
//將查詢引數組合到相對路徑上。
void addQueryParam(String name, String value, boolean encoded) {
  if (relativeUrl != null) {
    // Do a one-time combination of the built relative URL and the base URL.
    urlBuilder = baseUrl.newBuilder(relativeUrl);
    if (urlBuilder == null) {
      throw new IllegalArgumentException(
          "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
    }
    relativeUrl = null;
  }

  if (encoded) {
    urlBuilder.addEncodedQueryParameter(name, value);
  } else {
    urlBuilder.addQueryParameter(name, value);
  }
}

@QueryMap

使用場景

當介面中的一個 方法有比較多的查詢欄位時,全部定義到方法中時比較麻煩且容易出錯,這個使用我們完全可以將所有的查詢引數放到一個Map裡面。

可想而知,其內部實現必定是遍歷map ,然後像處理@Query引數一樣呼叫addQueryParam()處理每個查詢引數。

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

  FieldMap(Converter<T, String> valueConverter, boolean encoded) {
    this.valueConverter = valueConverter;
    this.encoded = encoded;
  }

  @Override void apply(RequestBuilder builder, Map<String, T> value) throws IOException {
    if (value == null) {
      throw new IllegalArgumentException("Field map was null.");
    }

    for (Map.Entry<String, T> entry : value.entrySet()) {
      String entryKey = entry.getKey();
      if (entryKey == null) {
        throw new IllegalArgumentException("Field map contained null key.");
      }
      T entryValue = entry.getValue();
      if (entryValue == null) {
        throw new IllegalArgumentException(
            "Field map contained null value for key `" + entryKey + "`.");
      }
      //果然不假
      builder.addFormField(entryKey, valueConverter.convert(entryValue), encoded);
    }
  }
}

@Field

使用場景

@Field註解一般用在表單引數的提交上

static final class Field<T> extends ParameterHandler<T> {
  private final String name; //引數名字
  private final Converter<T, String> valueConverter; //引數值轉換器
  private final boolean encoded; //是否編碼

  Field(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) return; // Skip null values. 所以使用@Field修飾的欄位,是不會上傳到伺服器的。
    //呼叫ResuestBuilder物件的具體想法來處理@Field修飾的表單欄位
    builder.addFormField(name, valueConverter.convert(value), encoded);
  }
}
void addFormField(String name, String value, boolean encoded) {
//根據引數值是否被編碼,呼叫不同的方法。formBuilder是OKHttp中的一個類。也是使用Builder模式建立的。
  if (encoded) {
    formBuilder.addEncoded(name, value);
  } else {
    formBuilder.add(name, value);
  }
}

@FieldMap

@FieldMap

使用場景

假如表單引數有很多個,我們可以使用一個Map<String,String>來表示,然後使用@FieldMap註解來修飾該引數就行了。可想而知,如同@QueryMap一樣,其內部實現肯定是遍歷Map,然後像處理@Field引數一樣呼叫

builder.addFormField(name, valueConverter.convert(value), encoded);

@Body

使用場景

在以下需要提交表單的請求裡,我們可以使用@Field,@FieldMap,我們還可以使用@Body來修飾我們提交的表單資料,這個時候我們需要定義一個Bean類,Bean類的各個Field必須和表單欄位的key一樣

static final class Body<T> extends ParameterHandler<T> {
  private final Converter<T, RequestBody> converter;

  Body(Converter<T, RequestBody> converter) {
    this.converter = converter;
  }

  @Override void apply(RequestBuilder builder, T value) {
    if (value == null) {
      throw new IllegalArgumentException("Body parameter value must not be null.");
    }
    RequestBody body;
    try {
      body = converter.convert(value);
    } catch (IOException e) {
      throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
    }
    builder.setBody(body);
  }
}

這裡Retrofit並沒有像@Field一樣處理表單引數。仔細想想也對,因為凡是提交的表單資料都需要放到請求體裡面,即使使用@Field,@FieldMap提交的資料,最終還是需要放到請求體裡面。

@Part

@RawPart

@PartMap

以上三個註解都是使用修飾上傳檔案的引數的,

結論

從對上面的分析可以知道,我們在提取使用註解修飾的引數後將值存放到RequestBuilder物件裡。

這裡又引入了RequestBuilder類

RequestBuilder

final class RequestBuilder {
  private static final char[] HEX_DIGITS =
      { `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `A`, `B`, `C`, `D`, `E`, `F` };
  private static final String PATH_SEGMENT_ALWAYS_ENCODE_SET = " "<>^`{}|\?#";

  private final String method; //方法型別

  private final HttpUrl baseUrl; //scheme+host
  private String relativeUrl;     //相對路徑
  private HttpUrl.Builder urlBuilder; //URL構造器

  private final Request.Builder requestBuilder; //OkHttp中Request構造器
  private MediaType contentType;        //提交表單的資料型別

  private final boolean hasBody;        //是否有請求體
  private MultipartBody.Builder multipartBuilder; //上傳檔案的構造器
  private FormBody.Builder formBuilder;                //表單資料的構造器
  private RequestBody body;                            //請求體

  RequestBuilder(String method, HttpUrl baseUrl, String relativeUrl, Headers headers,
      MediaType contentType, boolean hasBody, boolean isFormEncoded, boolean isMultipart) {
    this.method = method;
    this.baseUrl = baseUrl;
    this.relativeUrl = relativeUrl;
    this.requestBuilder = new Request.Builder();
    this.contentType = contentType;
    this.hasBody = hasBody;

    if (headers != null) {
      requestBuilder.headers(headers);
    }

    if (isFormEncoded) {
      // Will be set to `body` in `build`.
      formBuilder = new FormBody.Builder();
    } else if (isMultipart) {
      // Will be set to `body` in `build`.
      multipartBuilder = new MultipartBody.Builder();
      multipartBuilder.setType(MultipartBody.FORM);
    }
  }

  void setRelativeUrl(Object relativeUrl) {
    if (relativeUrl == null) throw new NullPointerException("@Url parameter is null.");
    this.relativeUrl = relativeUrl.toString();
  }

  void addHeader(String name, String value) {
    if ("Content-Type".equalsIgnoreCase(name)) {
      MediaType type = MediaType.parse(value);
      if (type == null) {
        throw new IllegalArgumentException("Malformed content type: " + value);
      }
      contentType = type;
    } else {
      requestBuilder.addHeader(name, value);
    }
  }

  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));
  }

  private static String canonicalizeForPath(String input, boolean alreadyEncoded) {
    int codePoint;
    for (int i = 0, limit = input.length(); i < limit; i += Character.charCount(codePoint)) {
      codePoint = input.codePointAt(i);
      if (codePoint < 0x20 || codePoint >= 0x7f
          || PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1
          || (!alreadyEncoded && (codePoint == `/` || codePoint == `%`))) {
        // Slow path: the character at i requires encoding!
        Buffer out = new Buffer();
        out.writeUtf8(input, 0, i);
        canonicalizeForPath(out, input, i, limit, alreadyEncoded);
        return out.readUtf8();
      }
    }

    // Fast path: no characters required encoding.
    return input;
  }

  private static void canonicalizeForPath(Buffer out, String input, int pos, int limit,
      boolean alreadyEncoded) {
    Buffer utf8Buffer = null; // Lazily allocated.
    int codePoint;
    for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
      codePoint = input.codePointAt(i);
      if (alreadyEncoded
          && (codePoint == `	` || codePoint == `
` || codePoint == `f` || codePoint == `
`)) {
        // Skip this character.
      } else if (codePoint < 0x20 || codePoint >= 0x7f
          || PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1
          || (!alreadyEncoded && (codePoint == `/` || codePoint == `%`))) {
        // Percent encode this character.
        if (utf8Buffer == null) {
          utf8Buffer = new Buffer();
        }
        utf8Buffer.writeUtf8CodePoint(codePoint);
        while (!utf8Buffer.exhausted()) {
          int b = utf8Buffer.readByte() & 0xff;
          out.writeByte(`%`);
          out.writeByte(HEX_DIGITS[(b >> 4) & 0xf]);
          out.writeByte(HEX_DIGITS[b & 0xf]);
        }
      } else {
        // This character doesn`t need encoding. Just copy it over.
        out.writeUtf8CodePoint(codePoint);
      }
    }
  }

  void addQueryParam(String name, String value, boolean encoded) {
    if (relativeUrl != null) {
      // Do a one-time combination of the built relative URL and the base URL.
      urlBuilder = baseUrl.newBuilder(relativeUrl);
      if (urlBuilder == null) {
        throw new IllegalArgumentException(
            "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
      }
      relativeUrl = null;
    }

    if (encoded) {
      urlBuilder.addEncodedQueryParameter(name, value);
    } else {
      urlBuilder.addQueryParameter(name, value);
    }
  }

  void addFormField(String name, String value, boolean encoded) {
    if (encoded) {
      formBuilder.addEncoded(name, value);
    } else {
      formBuilder.add(name, value);
    }
  }

  void addPart(Headers headers, RequestBody body) {
    multipartBuilder.addPart(headers, body);
  }

  void addPart(MultipartBody.Part part) {
    multipartBuilder.addPart(part);
  }

  void setBody(RequestBody body) {
    this.body = body;
  }

  Request build() {
    HttpUrl url;
    HttpUrl.Builder urlBuilder = this.urlBuilder;
    if (urlBuilder != null) {
      url = urlBuilder.build();
    } else {
      // No query parameters triggered builder creation, just combine the relative URL and base URL.
      url = baseUrl.resolve(relativeUrl);
      if (url == null) {
        throw new IllegalArgumentException(
            "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
      }
    }

    RequestBody body = this.body;
    if (body == null) {
      // Try to pull from one of the builders.
      if (formBuilder != null) {
        body = formBuilder.build();
      } else if (multipartBuilder != null) {
        body = multipartBuilder.build();
      } else if (hasBody) {
        // Body is absent, make an empty body.
        body = RequestBody.create(null, new byte[0]);
      }
    }

    MediaType contentType = this.contentType;
    if (contentType != null) {
      if (body != null) {
        body = new ContentTypeOverridingRequestBody(body, contentType);
      } else {
        requestBuilder.addHeader("Content-Type", contentType.toString());
      }
    }

    //生成一個Request物件
    return requestBuilder
        .url(url)
        .method(method, body)
        .build();
  }

  private static class ContentTypeOverridingRequestBody extends RequestBody {
    private final RequestBody delegate;
    private final MediaType contentType;

    ContentTypeOverridingRequestBody(RequestBody delegate, MediaType contentType) {
      this.delegate = delegate;
      this.contentType = contentType;
    }

    @Override public MediaType contentType() {
      return contentType;
    }

    @Override public long contentLength() throws IOException {
      return delegate.contentLength();
    }

    @Override public void writeTo(BufferedSink sink) throws IOException {
      delegate.writeTo(sink);
    }
  }
}

OkHttpCall

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

在建立了ServiceMethod物件後,使用該ServiceMethod物件和其引數建立一個OKHttPCall物件

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

在合適的時候呼叫ServiceMethod物件的toRequest方法生成一個Request物件,toReques()的內部實現就是呼叫RequestBuilder物件的build方法。

/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
  RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
      contentType, hasBody, isFormEncoded, isMultipart);

  @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
  ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

  int argumentCount = args != null ? args.length : 0;
  if (argumentCount != handlers.length) {
    throw new IllegalArgumentException("Argument count (" + argumentCount
        + ") doesn`t match expected count (" + handlers.length + ")");
  }

  for (int p = 0; p < argumentCount; p++) {
    handlers[p].apply(requestBuilder, args[p]);
  }

  return requestBuilder.build();
}

OkHttpCall 實現了Call介面,這個Call介面和OkHttp中的Call介面一樣,畢竟一家公司嘛。

其實就是對OkHttpCall 做了一層包裝。

最後方法的執行時通過呼叫

return serviceMethod.callAdapter.adapt(okHttpCall);

返回介面中方法定義的返回值。

這塊的流程就是構造一個OKHttp物件需要使用ServiceMethod物件和相應的引數。

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

最後建立具體的Call物件時

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;
}

呼叫了ServiceMethod物件的toRequest方法,然後使用這個request物件建立了一個Call物件。

/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
  RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
      contentType, hasBody, isFormEncoded, isMultipart);

  @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
  ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

  int argumentCount = args != null ? args.length : 0;
  if (argumentCount != handlers.length) {
    throw new IllegalArgumentException("Argument count (" + argumentCount
        + ") doesn`t match expected count (" + handlers.length + ")");
  }

  for (int p = 0; p < argumentCount; p++) {
    handlers[p].apply(requestBuilder, args[p]);
  }
//生成一個Request物件
  return requestBuilder.build();
}

相關文章