Retrofit 源码阅读笔记(一)

Retrofit 源码阅读笔记(一)

Retrofit 相信每一个 Android 开发者都对它非常熟悉,它使我们调用 Http 请求变得非常的简单(内部实现是 OkHttp ),我们只需要定义一个接口,接口的每一个方法就表示一个 Http 请求,方法的注解,参数的注解和参数他们共同来描述了这一个 Http 请求的 Request,而方法的返回值描述了如何处理 HttpResponse。通过 Retrofit#create() 方法就能够生成一个上面接口的实现类(通过动态代理实现),通过调用这个实现类的方法就能够完成一次 Http 请求,一次网络请求就好像调用了一次我们的本地方法一样。刚开始接触 Retrofit 的我,感觉这个真的是太神奇,工作多年后渐渐地我也知道了它的原理,所以这次文章来基于 Retrofit 的源码来记录一下它的实现方式。

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

动态代理创建接口实现类

创建我们定义的接口实现类通过 Retrofit#create() 方法传一个接口的 Class 对象。我们来看看它的源码:

Java 复制代码
  @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 对象的:

Java 复制代码
  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(),看看代码实现:

Java 复制代码
  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() 方法去创建,然后存放在缓存中,下次再使用就不需要再创建。

Java 复制代码
  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() 方法:

Java 复制代码
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {  
   return new Builder(retrofit, method).build();  
}

朴实无华的代码,直接创建一个 Builder() 对象,然后调用其 build() 方法,我们看看 它的构造函数和 build() 方法的源码实现:

Java 复制代码
    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() 方法的源码:

Java 复制代码
    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() 方法的实现:

Java 复制代码
    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() 方法的实现:

Java 复制代码
    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 的动态代理实现、方法注解解析等等内容,后续的文章还会继续介绍方法参数和参数注解的解析,请求任务的解析等等逻辑。

相关推荐
似霰1 小时前
安卓adb shell串口基础指令
android·adb
fatiaozhang95273 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO4 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师4 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师4 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫5 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白5 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong6 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519877 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android
老狼孩111227 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发