Retrofit 源码详解

Retrofit的使用从很早之前就已经开始了, 但是一直没有深入研究为什么使用Retrofit只要定义一个接口, 同时在接口的方法上和方法的参数上加上一些注解就可以完成Http请求了, 也没有研究请求参数和请求结果是如何进行封装的, 所以使用Retrofit一直是处于一知半解的状态, 不知道其内部的原理, 因此花了一点时间看了Retrofit的源码, 对Retrofit的整个请求流程有了一定的理解.

Retrofit核心源码解读

1. 创建Retrofit对象

创建Retrofit对象的时候使用的是Builder模式, 可以在创建Retrofit对象的时候设置RetrofitbaseURL, 添加自己的converterFactory

例子:

Retrofit retrofit = new Retrofit.Builder() .baseUrl(GITHUB_API) .addConverterFactory(GsonConverterFactory.create()) .build();

注意点:

  • 在设置baseUrl的时候和Retrofit 1.x版本不同的是url必须以'/'结尾

    源码:

      // Retrofit.java#baseUrl method
    
      public Builder baseUrl(HttpUrl baseUrl) {
          ....
         // pathSegments() 返回的是url的查找路径List(查找路径是以'/'分割的字符串, 所以只要split("/")就可以了)
        List<String> pathSegments = baseUrl.pathSegments();
        // 如果List的最后一个元素不是(""), 则说明查找路径不是以'/'结尾的字符串
        if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
          throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
        }
          .... 
      }
  • 如果不添加自己的转换器, 则返回值只能封装成ResponseBody类型或者没有返回值

    源码:

      // Retrofit.java#Builder
    
      Builder(Platform platform) {
             this.platform = platform;
             // 添加默认的BuiltInConverter
             converterFactories.add(new BuiltInConverters());
          }
    
      // BuiltInConverters.java#responseBodyConverter method
    
       public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    
            // 如果返回值是ResponseBody类型
          if (type == ResponseBody.class) {
            return Utils.isAnnotationPresent(annotations, Streaming.class)
                ? StreamingResponseBodyConverter.INSTANCE
                : BufferingResponseBodyConverter.INSTANCE;
          }
    
          // 如果返回值类型是Void类型
          if (type == Void.class) {
            return VoidResponseBodyConverter.INSTANCE;
          }
    
          // 如果返回值是其他的类型
          return null;
        }
  • 创建Retrofit对象时使用了Builder模式, 而使用Builder模式的好处就是创建的对象是不可变对象(因为属性都是private的且没有setter方法), 这样在使用的使用就不用担心对象中属性是否被修改或者不小心修改了对象中的属性了. 如果存在有些请求不能和其他请求共用同一个Retrofit对象, 但是大部分的属性都一样只有少部分的属性不一致, 那么可以用一个Retrofit对象为模版来创建一个新的Retrofit对象

    源码:

      // Retrofit.java#Builder
    
      Builder(Retrofit retrofit) {
          // 以retrofit对象为模版生成一个新的Retrofit对象
        this.platform = Platform.get();
        callFactory = retrofit.callFactory;
        baseUrl = retrofit.baseUrl;
        converterFactories.addAll(retrofit.converterFactories);
        adapterFactories.addAll(retrofit.adapterFactories);
        // Remove the default, platform-aware call adapter added by build().
        adapterFactories.remove(adapterFactories.size() - 1);
        callbackExecutor = retrofit.callbackExecutor;
        validateEagerly = retrofit.validateEagerly;
      }
  • 这里要额外提一下. 因为Retrofit是支持多个平台(jvm, android, ios), 那么Retrofit是如何分辨当前引用的是什么平台的呢, 实际上Retrofit使用的方法就是查看当前的classpath中是否存在相应平台特有的class来判断当前是在哪个平台. 这个和Spring的@ConditionalOnClass(ClassName.class)来决定是否初始化注解所修饰的bean有异曲同工的味道

    关键代码:

      // Platform.java#findPlatform method 
      // 查找当前所在的平台
      private static Platform findPlatform() {
          try {
            // 如果存在"android.os.Build"则是android平台
            Class.forName("android.os.Build");
            if (Build.VERSION.SDK_INT != 0) {
              return new Android();
            }
          } catch (ClassNotFoundException ignored) {
          }
          try {
            // 如果存在"java.util.Optional"则是java8平台
            Class.forName("java.util.Optional");
            return new Java8();
          } catch (ClassNotFoundException ignored) {
          }
          try {
            // 如果存在"org.robovm.apple.foundation.NSObject"则是ios平台
            Class.forName("org.robovm.apple.foundation.NSObject");
            return new IOS();
          } catch (ClassNotFoundException ignored) {
          }
    
          // 默认平台
          return new Platform();
        }

2. 创建发送请求的接口

包含发送请求方法必须是一个接口, 同时这个接口是不能继承其他的接口的

例子:

    public interface GitApi {
        @GET("/users/{user}")
        Call<GitModel> getFeed(@Path("user") String user);
    }

注意点:

  • 包含请求方法必须是一个接口同时不能继承其他的接口的

    源码:

      static <T> void validateServiceInterface(Class<T> service) {
    
           // class必须是一个接口
          if (!service.isInterface()) {
            throw new IllegalArgumentException("API declarations must be interfaces.");
          }
    
          // 接口不能继承其他的接口
          if (service.getInterfaces().length > 0) {
            throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
          }
        }
  • 为什么只要定一个接口和一个请求方法, Retrofit就可以发送Http请求了呢? 因为Retrofit使用了代理, 其实在创建接口的对象的时候返回的是一个代理对象, 这个代理其实也是Retrofit的核心.

    源码:

          // 创建接口的实现对象, 返回的是GitApi接口的代理对象
         GitApi git = retrofit.create(GitApi.class);
    
         // 关键代码: create方法
         public <T> T create(final Class<T> service) {
    
              // 校验service是否是一个没有继承其他接口的接口
              Utils.validateServiceInterface(service);
    
              // 如果validateEagerly==true, 则先将接口中的方法全部都放到serviceMethodCache中,
              // 这样在之后的调用过程中就不需要走loadServiceMethod的流程, 而是直接走的缓存, 这样子可以加快访问的速度,
              // 但是这样子也存在一定的坏处, 因为会造成内存占用量变大而且可能有些方法不会被调用都被放到缓存中了
              if (validateEagerly) {
                eagerlyValidateMethods(service);
              }
    
              // 代理模式,
              // 使用代理拦截接口中所有的方法, 从而解析方法上的注解, 方法参数上的注解, 返回值等, 这也是retrofit可以实现面向接口和注解编程的关键
              // service: 真实对象, proxy: 代理对象, method: 调用的方法, args: 方法参数
              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 {
    
                      // 如果调用的是Object对象中方法则直接返回调用结果
                      if (method.getDeclaringClass() == Object.class) {
                        return method.invoke(this, args);
                      }
                      // 如果调用的是接口中的默认方法则直接返回对应平台对调用默认方法的处理
                      if (platform.isDefaultMethod(method)) {
                        return platform.invokeDefaultMethod(method, service, proxy, args);
                      }
    
                      // 获取或者创建ServiceMethod对象
                      ServiceMethod<Object, Object> serviceMethod =
                          (ServiceMethod<Object, Object>) loadServiceMethod(method);
                     // 创建OkHttpCall对象用于发送请求和解析结果 
                      OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                      return serviceMethod.callAdapter.adapt(okHttpCall);
                    }
                  });
                }
  • 上面很明显可以看到Retrofit实际上使用了jdkProxy动态代理技术使得可以定义一个接口加上注解就完成http请求. 由于使用了Proxy所以必须定义成一个接口而不是一个抽象类的原因也就显而易见了, 因为jdkproxy只能生成接口的代理对象. 如果这里使用的是cglib库的话那也可以定义成抽象类

3. 发送Http请求

要发送http请求就离不开ServiceMethod类和OkHttpCall类, 他们是发送Http请求的核心类. 首先会从Map中获取和当前请求方法相关联的ServiceMethod, 如果找到了(说明之前这个方法已经被调用过了)就直接使用找到的ServiceMethod, 如果没有找到, 则创建一个新的ServiceMethod并且和当前的请求方法相关联putMap中. 最后调用OkHttpCall.execute发送请求.

源码:

  // 查找或者判断和请求的方法相关联的ServiceMethod

    ServiceMethod<?, ?> loadServiceMethod(Method method) {
        // 缓存技术
        ServiceMethod<?, ?> result = serviceMethodCache.get(method);
        if (result != null) return result;

        // 使用了两次判断(两段锁)
        synchronized (serviceMethodCache) {
          // 这一次从缓存中取是有必要的且非常重要, 如果没有这一次则有可能下面的代码会被重复执行, 同一个key也可能被重复赋值
          result = serviceMethodCache.get(method);
          if (result == null) {
            result = new ServiceMethod.Builder<>(this, method).build();
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }
  • 创建ServiceMethod也使用了Build模式, 在build方法中对请求的方法上的注解, 方法参数, 返回值进行了解析

源码:

public ServiceMethod build() {
 // 创建callAdapter对象(默认使用的是DefaultCallAdapterFactory), callAdapter对象用于.
 // callAdapter对象的主要作用就是返回一个CallAdapter接口的实例用于调用底层okhttp的方法发送请求和解析返回值(DefaultCallAdapterFactory中使用OkHttpCall.execute来发送请求和解析结果). 
 // 默认的请求方法返回值都是Call<T>, 可以继承CallAdapter.Factory来实现自定义的返回值类型
 callAdapter = createCallAdapter();

 ...
 other code
 ...

 // 创建返回值解析器(默认只有BuiltInConverters), 可以在创建Retrofit的时候使用addConverterFactory加入其他的converters
 responseConverter = createResponseConverter();

    // 解析方法上的注解
 for (Annotation annotation : methodAnnotations) {
   parseMethodAnnotation(annotation);
 }

 ...
 other code
 ...

 // 解析参数
 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);
 }

 ...
 一些错误情况的处理
 ...

 return new ServiceMethod<>(this);
}
  • Retrofit.java#createproxy中调用请求方法最后返回的是 return serviceMethod.callAdapter.adapt(okHttpCall);, 默认情况下返回的就是OkHttpCall, 所以最后调用的就是OkHttpCall.execute()方法发送请求和解析返回结果

关键代码:

    @Override public Response<T> execute() throws IOException {
    okhttp3.Call call;

    synchronized (this) {

      ...

      call = rawCall;
      if (call == null) {
        try {
          // 创建Okhttp的call方法
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException e) {
          ...
        }
      }
    }

     ....

     // call.execute: 调用okhttp发送真正的请求
     // parseResponse: 对返回的结果进行解析, 方法就是调用创建retrofit时加入的所有converterFactory, 知道找到一个converter可以处理返回值
    return parseResponse(call.execute());
  }

总结

至此, Retrofit发送请求的整个流程就已经讲解完毕了, 实际上整个流程中关键点就只有几个, 比如:

  • Retrofit.java#create方法, 这个方法的作用就是使用Proxy创建用户定义的接口的实现, 从而实现使用时只要创建接口, 创建方法, 添加相关注释三个步骤就可以完成一个http请求的关键

  • ServiceMethod.java#build方法, 这个方法的作用是对请求方法上的注解, 方法的参数, 方法的返回值进行解析, 同时创建callAdapter对象, 这是Retrofit留下的一个’插口’, 只要我们实现CallAdapter.Factory就可以处理自定义的方法返回值类型, 方法不一定要返回Call<T>类型

  • OkHttpCall.java#execute方法, 这个方法的作用是调用底层的okhttp发送真正的http请求 , 然后对返回结果进行解析. 解析返回结果是的方法就是遍历Retrofit中的converterFactories, 知道找到一个可以解析返回结果的对象. 这事Retrofit留下的另一个’插口’, 只要我们实现Converter.Factory就可以处理自定义的返回结果, 而不一定只能是Call<ResponseBody>类型

另外, Retrofit提供了

Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

这写转换器, 应该可以满足日常开发需要了.

从上面可以看到, Retrofit可以允许我们自定义返回类型, 返回结果同时屏蔽掉了底层的OkHttp的复杂性, 使得我们只要定义一个接口就完成Http请求的发送, 这对于使用者来说是非常友好的. 易于上手和高度的定制性是现如今Retrofit如此流行的关键

    原文作者:HTTP
    原文地址: https://juejin.im/entry/5885db18570c350062cbbdff
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞