Retrofit 源码阅览笔记(一)

Retrofit 信任每一个 Android 开发者都对它十分了解,它使咱们调用 Http 恳求变得十分的简略(内部完结是 OkHttp ),咱们只需求界说一个接口,接口的每一个办法就表明一个 Http 恳求,办法的注解,参数的注解和参数他们共同来描绘了这一个 Http 恳求的 Request,而办法的返回值描绘了如何处理 HttpResponse。经过 Retrofit#create() 办法就能够生成一个上面接口的完结类(经过动态署理完结),经过调用这个完结类的办法就能够完结一次 Http 恳求,一次网络恳求就好像调用了一次咱们的本地办法相同。刚开端触摸 Retrofit 的我,感觉这个真的是太神奇,工作多年后渐渐地我也知道了它的原理,所以这次文章来根据 Retrofit 的源码来记载一下它的完结办法。

本篇文章是系列文章的第一篇,我阅览的 Retrofit 源码版本是 2.9.0

动态署理创立接口完结类

创立咱们界说的接口完结类经过 Retrofit#create() 办法传一个接口的 Class 目标。咱们来看看它的源码:

  @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
  public <T> T create(final Class<T> service) {
    // 校验 Class 目标是否合法
    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];
              // inovke() 办法会在每一个办法的调用时,调用这个办法,对应被调用的目标便是 proxy,办法是 method,办法的参数是 args。
              @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.
                // 假如是 Object 中的办法,直接经过反射调用 Object 中对应的办法。
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                // 判别是否是接口中的 default 办法,假如是经过 platform 目标调用,假如不是经过 loadServiceMethod() 办法获取一个目标,然后调用 invoke() 办法。
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

上面代码做了两件事,经过 validateServiceInterface() 办法查看 Class 目标是否合法,然后经过动态署理构建一个署理目标(假如对动态署理没有一点了解的同学,建议先去查查相关材料)。
署理目标的每一个办法调用都会触发 InvocationHandler#invoke() 的办法,proxy 是署理的目标,method 是对应的办法的 Method(常写反射的同学或许很了解),args 便是对应办法的参数。invoke() 办法的处理首要做了以下工作:

  1. 判别是否是 Object 中的办法,假如是直接经过反射的办法调用对应的办法。
  2. 判别是否是接口中的 default 办法,假如是直接经过 Platform#invokeDefaultMethod() 办法去调用。
  3. 假如第一点和第二点的条件都不满足,经过 loadServiceMethod() 办法加载一个目标,然后调用它的 invoke() 办法来执行调用。(这也是咱们剖析的首要逻辑)

validateServiceInterface()

咱们简略介绍一下是如何校验 Class 目标的:

  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);
    // 接口不能够有泛型的参数
    while (!check.isEmpty()) {
      Class<?> candidate = check.removeFirst();
      if (candidate.getTypeParameters().length != 0) {
        StringBuilder message =
            new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
        if (candidate != service) {
          message.append(" which is an interface of ").append(service.getName());
        }
        throw new IllegalArgumentException(message.toString());
      }
      Collections.addAll(check, candidate.getInterfaces());
    }
    // 是否提早加载所有的办法
    if (validateEagerly) {
      Platform platform = Platform.get();
      for (Method method : service.getDeclaredMethods()) {
        if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
          loadServiceMethod(method);
        }
      }
    }
  }

上面首要验证两点:

  1. Class 目标必须是接口。
  2. Class 目标和其对应的父类接口都不能有泛型参数。

后续还经过 validateEagerly 参数来判别是否要提早加载对应的恳求 Method,默许是不提早加载,只要在办法调用时才会去判别加载。

loadServiceMethod()

加载办法的完结是经过 loadServiceMethod(),看看代码完结:

  ServiceMethod<?> loadServiceMethod(Method method) {
    // 从缓存中去获取 ServiceMethod
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    // 缓存中获取失利,创立一个新的
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        // 创立一个新的
        result = ServiceMethod.parseAnnotations(this, method);
        // 添加到缓存中
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

恳求办法解析完结后运用 ServiceMethod 类封装,首要从本地缓存中去获取 ServiceMethod 目标,假如没有再经过 ServiceMethod#parseAnnotaions() 办法去创立,然后存放在缓存中,下次再运用就不需求再创立。
这儿有一个有意思的问题便是这儿的锁用的是 serviceMethodCache,用的锁范围略微大了一点点,也就相当于所有的 Service 创立都会竞赛这个锁,这儿有相关的 Issue,这个锁的竞赛还造成了 ANR。有一个大佬把上面的锁的目标修改成了 ServiceClass 目标,这个问题就得到了很大的缓解。

  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    // 构建 http 恳求
    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.");
    }
    // 构建恳求使命
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

上面的代码首要做了以下工作:

  1. 经过 RequestFactory.parseAnnotations() 办法来解析办法的注解和参数的注解来构建 Http 的恳求。
  2. 校验办法返回值的类型,返回类型中不能够有不确定的泛型,也不能够是返回空。
  3. 经过 HttpServiceMethod.parseAnnotations() 办法来构建 Http 的恳求使命。

RequestFactory.parseAnnotations()HttpServiceMethod.parseAnnotations() 这两个办法能够说是中心代码中的中心,后续的剖析也都从它们两个开端。

办法注解解析

咱们接着看前面一节提到的 RequestFactory.parseAnnotations() 办法:

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
   return new Builder(retrofit, method).build();  
}

朴实无华的代码,直接创立一个 Builder() 目标,然后调用其 build() 办法,咱们看看 它的结构函数和 build() 办法的源码完结:

    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      // 获取办法的注解
      this.methodAnnotations = method.getAnnotations();
      // 获取参数的类型
      this.parameterTypes = method.getGenericParameterTypes();
      // 获取参数的注解
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
    RequestFactory build() {
      // 办法注解解析
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      // 假如不知道 Http 恳求的类型报错
      if (httpMethod == null) {
        throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }
      // 假如 Multipart 和 FormEncoded 没有 Body 报错。
      if (!hasBody) {
        if (isMultipart) {
          throw methodError(
              method,
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
        if (isFormEncoded) {
          throw methodError(
              method,
              "FormUrlEncoded can only be specified on HTTP methods with "
                  + "request body (e.g., @POST).");
        }
      }
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      // 解析参数的注解,不同的参数注解处理办法经过 ParameterHandler 类来封装
      for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        parameterHandlers[p] =
            parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
      }
      // 假如没有获取到 URL 和 恳求的 Path,报错。
      if (relativeUrl == null && !gotUrl) {
        throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
      }
      // 不答应有 Body 的恳求,但是有 Body,报错
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError(method, "Non-body HTTP method cannot contain @Body.");
      }
      // FormEncoded 恳求没有获取到 Field, 报错
      if (isFormEncoded && !gotField) {
        throw methodError(method, "Form-encoded method must contain at least one @Field.");
      }
      // Multipart 恳求,没有获取到 Part Body,报错
      if (isMultipart && !gotPart) {
        throw methodError(method, "Multipart method must contain at least one @Part.");
      }
      return new RequestFactory(this);
    }

结构函数中首要获取到办法的注解(能够有多个注解,所以是一个数组);获取办法中参数的类型;获取办法参数中的注解(由于有多个参数,一个参数又能够有多个注解,所以是一个二维数组)。

build() 办法中首要做了两件事,经过 parseMethodAnnotation() 来解析办法的注解;经过 parseParameter() 来解析参数和对应的注解,解析成功后对应的处理办法用 ParameterHandler 类来封装。

build() 函数中还判别了各种恳求参数是否正确,有以下几点:

  1. 假如不知道 Http 恳求的类型(便是 GETPOST 啥的没有明确)报错。
  2. 假如 MultipartFormEncoded 没有 Body 报错。
  3. 假如没有获取到 Url 和 恳求的 Path,报错 (也便是 Url 和 恳求的 Path 取其一即可) 。
  4. 不答应有 Body 的恳求,但是有 Body,报错(比方 GET 恳求就不答应有 Body)。
  5. FormEncoded 恳求没有获取到 Field, 报错。
  6. Multipart 恳求,没有获取到 Part Body,报错。

咱们看看 parseMethodAnnotation() 办法的源码:

    private void parseMethodAnnotation(Annotation annotation) {
      // 首要经过 parseHttpMethodAndPath() 办法取解析 Http 恳求的办法和恳求的相对路径
      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);
      } 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(method, "@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        // Multipart
        // 和 FormUrlEncoded 抵触
        if (isFormEncoded) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        // FormUrlEncoded
        // 和 Multipart 抵触
        if (isMultipart) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }

假如是恳求办法类的注解经过 parseHttpMethodAndPath() 办法去解析它的恳求办法和解析恳求的相对路径,恳求办法类的注解包括 @DELETE@GET@HEAD@PATCH@POST@PUT@OPTIONS、 和 @HTTP 等等。
@Headers 注解表明恳求的 Header,经过 parseHeaders() 办法完结解析。
@Multipart 符号恳求 BodyMultipart,它和 FormUrlEncoded 抵触。
@FormUrlEncoded 符号恳求 BodyForm 表单,它和 Multipart 抵触。

咱们看看 parseHttpMethodAndPath() 办法的完结:

    private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
      // 多次设置 HttpMethod 报错
      if (this.httpMethod != null) {
        throw methodError(
            method,
            "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.
      // 假如在恳求测 Query 参数后添加参数化的 Path 报错。
      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(
              method,
              "URL query string "%s" must not have replace block. "
                  + "For dynamic query parameters use @Query.",
              queryParams);
        }
      }
      // 记载相对路径 Path
      this.relativeUrl = value;
      // 获取参数化的 Path 的姓名。
      this.relativeUrlParamNames = parsePathParameters(value);
    }

这儿首要做了以下的工作:

  1. 假如已经设置过了 HttpMethod,报错。
  2. 假如 Query 参数后有参数化的 Path 设置,报错。
  3. 记载相对路径 Path 和解析 Path 参数化的姓名。

或许你有点忘了什么是参数化的 Path,我这儿简略介绍一下,parseHttpMethodAndPath() 中只能处理相对路径 Path,比方 /a/b/c 便是一个相对路径,参数化便是比方我界说的 Path 是这样的 /a/{name}/c,其中的 {name} 便是一个占位符,能够经过参数中的注解 @Path 来替换它(后续的文章中会看到它),比方我的参数 nametans,那么恳求是的 Path 便是 /a/tans/c
上面的 Path 是能够为空的,假如为空就需求参数的注解中有 @Url(后续的文章中会看到它),它是来表明一个绝对路径的,比方 https://www.tans.com/a/b/c。相对路径和绝对路径只能有一个存在。

咱们再看看 parseHeaders() 办法的完结:

    private Headers parseHeaders(String[] headers) {
      Headers.Builder builder = new Headers.Builder();
      for (String header : headers) {
        int colon = header.indexOf(':');
        if (colon == -1 || colon == 0 || colon == header.length() - 1) {
          throw methodError(
              method, "@Headers value must be in the form "Name: Value". Found: "%s"", header);
        }
        // 获取 Header 的姓名和对应的 Value
        String headerName = header.substring(0, colon);
        String headerValue = header.substring(colon + 1).trim();
        // 假如是 Content-Type 记载一下
        if ("Content-Type".equalsIgnoreCase(headerName)) {
          try {
            contentType = MediaType.get(headerValue);
          } catch (IllegalArgumentException e) {
            throw methodError(method, e, "Malformed content type: %s", headerValue);
          }
        } else {
          // 添加到 Header.Builder 中。 
          builder.add(headerName, headerValue);
        }
      }
      return builder.build();
    }

Http 协议的 HeaderNameValue 都是以 : 分割开,上面的解析代码也是十分的简略,就不多说了。

最终

本篇文章中介绍了 Retrofit 的动态署理完结、办法注解解析等等内容,后续的文章还会持续介绍办法参数和参数注解的解析,恳求使命的解析等等逻辑。