开源框架 | Retrofit 源码解析

对于 Retrofit 应该是再熟悉不过了,都知道它是一个网络框架,但是为什么它还要基于 OkHttp 呢?了解 Retrofit 后会发现,它虽是网络框架却不做任何网络请求,网络请求任务都是交给 OkHttp 去执行的。Retrofit 使用动态代理、建造者、工厂、外观、适配器等设计模式,实现了自身的高度解耦以及在实际在网络请求中的高扩展性,例如多种类型 CallAdapter 和 Converter 支持。先来看看 Retrofit 的基本使用:

1. 使用步骤

  • 创建请求的接口类 IService 和请求方法
    设置请求方法类型,动态设置请求的 url、参数
    public interface IService {
        @GET("JakeWharton")
        Call getResult();
    }
    
  • 创建 Retrofit
      Retrofit RETROFIT_CLIENT = new Retrofit.Builder()
              .baseUrl("https://github.com/")
              .client(OKHttpHolder.OK_HTTP_CLIENT)
              .addConverterFactory(GsonConverterFactory.create()) //数据转换器
              .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //Retrofit配合RxJava使用
              .build();
    
  • 创建请求方法接口类(代理类)对象
    IService service = retrofit.create(IService.class);
    
  • 创建 Call
    Call call1 = service.get();
    Call call2 = service.post();
    
  • 执行请求(同步/异步)
    call.enqueue()/call.execute()
    
  • 处理请求响应 Callback
          call.enqueue(new Callback() {
              @Override
              public void onResponse(@NonNull Call call, @NonNull Response response) {
                  //请求成功
              }
    
              @Override
              public void onFailure(@NonNull Call call, @NonNull Throwable t) {
                  //请求失败
              }
          });
    

2. 源码解析

2.1 Retrofit.Builder

Retrofit 的创建使用了建造者模式,调用 Retorfit.Builder() 时,首先会找到一个 Platform 即代码的执行平台,例如 Android平台、Java平台。

  • Retrofit.Bulider() 构造方法
      public Builder() {
        this(Platform.get());
      }
    
  • Platform.get()

    findPlatform() 方法中实现了平台创建,由于我们是在 Android 平台使用 Retorfit,直接来看看 Android 平台做了些什么处理:

    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) {
      }
      return new Platform();
    }
    
  • Android 平台

    有一个内部类 MainThreadExecutor,创建了一个主线程的 Handlerececute() 方法中通过 handler 发送消息,用于将请求的回调切回主线程,在主线程处理响应的数据。

    static class Android extends Platform {
      @Override public Executor defaultCallbackExecutor() {
        return new MainThreadExecutor();
      }
    
      @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
        if (callbackExecutor == null) throw new AssertionError();
        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);
        }
      }
    }
    
  • baseUrl(String baseUrl)

    这里的 baseUrl 是我们的主机地址,通常是使用绝对路径,例如请求地址为:https://github.com/square/retrofit,那么 baseUrl 即为 https://github.com/,注意 baseUrl 必须以 / 结尾。

  • client()

    添加一个 OkHttpClient,默认是一个不带任何配置的 OkHttpClient,如果我们想自定义 OkHttpClient 的连接时长、读/写时长以及拦截器时,可以通过 client() 传入我们自定义的 OkHttpClient。

  • addConverterFactory()

    用于添加数据的转换器工厂,实现对象的序列化和反序列化,可以是基本数据类型的转换器工厂,也可以是 Gson、FastJson等;例如GsonConverterFactory,请求时 GsonRequestBodyConverter 将我们自己定义 JavaBean 或者一些基本的数据类型写入 Gson 中并转换成请求体 RequestBody 发送给服务器,响应返回时 GsonResponseBodyConverter 首先将响应体 ResponseBody 转换为 Gson,Gson 再转换为我们需要的 JavaBean 类型,省去了我们自己对数据的解析便于直接提取数据。

  • addCallAdapterFactory()

    用于添加请求适配器工厂,默认请求适配默认使用 OkHttpCall,也可以是 RxJava 的 Observer 或者 Flowable(都是通过请求适配器将 OkHttpCall 转换得来的),Retrofit 中有三种请求适配器工厂类:

    • DefaultCallAdapterFactory
    • ExecutorCallAdapterFactory( 默认的)

      OkHttpCall 转换为 ExecutorCallbackCall,ExecutorCallbackCall 是 DefaultCallAdapterFactory 的内部类实现了 retrofit2.Call 接口;

    • RxJava2CallAdapterFactory

      创建 RxJava2CallAdapter ,将 OkHttpCall 转换为 Observer 或者 Flowable;

    @Override public Object adapt(Call call) {
      ...
      Observable observable;
      if (isFlowable) {
          return observable.toFlowable(BackpressureStrategy.LATEST);
      }
      ...
      return observable;
    }
    

    CallAdapter 的 adapt() 方法的作用是将默认的 OkHttpCall 转换为我们指定的请求类型,比如 RxJavaObserver 类型。

  • build()
2.2 Retrofit
  • create()
    create() 用于动态返回请求接口类的代理类对象,代理类对象调用接口类中的方法时会调用代理类对象的 InvocationHandler 的 invoke() 方法,invoke() 方法的参数 method 即调用的接口方法,数组 args 即接口方法的参数,在这里通过反射我们的接口方法去调用 HTTP 请求方法,反射是在 ServiceMethod 中实现。
    其中Proxy.newProxyInstance() 有三个参数:
    • service.getClassLoader():service 的 ClassLoader,service 就是我们调用 create() 传进来的自定义的接口类;
    • new Class[] { service }:代理类要实现的接口列表,即我们在自定义的接口类中的接口方法;
    • new InvocationHandler(){}:代理类要执行的调用程序,在代理类对象调用接口方法时执行;
  public  T create(final Class service) {
    ...//省略内容主要用于检测 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, @Nullable Object[] args)
              throws Throwable {
            ...
            ServiceMethod serviceMethod =
                (ServiceMethod) loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }

重点来看 loadServiceMethod() 方法:

  • loadServiceMethod()
    loadServiceMethod() 用于返回一个 ServieMethod 实例;
    1. 先从缓存 serviceMethodCache 中获取一个 ServiceMethod,serviceMethodCache 是一个 Map,其中 key 为 Method,value 为 ServiceMethod,即一个接口方法对应一个 ServiceMethod;
    2. 如果获取到的 result 不为空直接返回,为空则创建一个新的 ServiceMethod 存入缓存中并返回;
  ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder<>(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

为什么有一个 serviceMethodCache 缓存我们的 ServiceMethod?
我们都知道反射机制会影响程序执行的效率,而 Retrofit 将一次请求的结果(ServiceMethod)进行缓存,当再次调用同一个请求时,我们就不需要去进行反射获取 HTTP 方法和参数等信息,而是直接使用 ServiceMethod 里上一次请求已经保存的相关请求信息进行一次请求,所以当我们频繁调用同一个请求时,这里的缓存就可以在很大程度上减少反射带来的性能开销。

2.3 ServiceMethod

ServiceMethod 的作用是解析注解,通过反射机制将请求接口类的接口方法调整为 HTTP 调用,调整的依据就是接口方法和方法参数的 注解,所有和请求接口相关的参数、参数类型、回调类型、返回数据类型、请求适配器、数据转换器都保存在 ServiceMethod 中,同时 ServiceMethod 还实现了请求适配和数据转换器的调用,并向 OkHttpCall 提供执行 OkHttp 请求的 okhttp3.Call。

先来看ServiceMethod 内部类 Builder 的构造方法:

  • Builder
    首先保存 Retrofit 和 Method 的对象,接着获取了 Method 的 methodAnnotations 、parameterTypes 、parameterAnnotationsArray 并保存,这三者分别代表方法的注解、方法的参数类型以及方法参数的注解,其中方法的注解和方法参数的注解会在 build() 方法中进行解析。
    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
  • Builder.build()
    build() 方法中首先创建了 CallAdapter 对象和 ResponseConverter 对象,然后将我们在 Builder 构造方法里获取到的 methodAnnotations 、parameterAnnotationsArray 通过分别调用 parseMethodAnnotation()、parseParameterAnnotation() 方法对方法的注解和方法参数的注解进行解析,代码中省略的部分都是对各个对象类型合理性的判断,如果不符合规定则抛出异常。
    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      ...//检测 responseType 不能是 Response.class 和 okhttp3.Response.class,否则抛出异常
      responseConverter = createResponseConverter();

      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      ...//检测方法的注解是否正确使用
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        ...//检测参数类型是否正确

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

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
      ...//检测方法参数的注解是否合理

      return new ServiceMethod<>(this);
    }
  • parseMethodAnnotation()
    1. 根据请求方法的不同注解调用 parseHttpMethodAndPath() 方法,获取相对 url 路径;
    2. 如果方法注解中有 retrofit2.http.Headers 调用 parseHeaders() 方法解析 headers;
    3. 方法的注解 MultipartFormUrlEncoded 不能共存,即一个方法的注解中只能有 @Multipart 或 @FormUrlEncoded,共存则抛出异常;
  • parseParameterAnnotation()

    根据不同的方法参数注解通过 ParameterHandler 返回不同的参数处理类并保存在 parameterHandlers 数组中,每一个参数对应一个 ParameterHandler;

  • toCall()

    用于获取 OkHttp 请求的 Call 对象,实现 OkHttp 请求:

    1. 创建 RequestBuilder 对象;
    2. 遍历 parameterHandlers 数组,根据每个参数注解类型的不同依次将参数添加到 RequestBuilder 中;
    3. newCall(),创建一个新的请求调用并返回;
  okhttp3.Call toCall(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart); //1

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

    int argumentCount = args != null ? args.length : 0;
    ...

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

    return callFactory.newCall(requestBuilder.build()); //3
  }

  • toResponse()
    解析响应,将响应体 ResponseBody 传入数据转换器 Convert 的 convert() 方法解析响应(转换为接口类方法中指定的 Java 对象),convert() 方法根据参数类型的不同将 ResponseBody 转换为对应数据类型的数据,Retrofit 中默认实现该响应数据转换的是 BuiltInConverters ,我们在前面使用 Retrofit.Builder.addConvertFactory() 方法添加的转换器工厂类就是用于为这里提供数据转换器的。
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }
  • adapt()
    调用 CallAdapter 的 adapt() 方法,将 OkHttp 请求 Call 转换为我们设置的适配器类型,即我们在 Retrofit.Builder.addCallAdapterFactory() 方法中添加的适配器工厂类,默认是 Call,还可以是 RxJava。
  T adapt(Call call) {
    return callAdapter.adapt(call);
  }

2.4 OkHttpCall

Retrofit本身用一个 OkHttpCall 类负责处理网络请求,而我们在接口中定义需要定义很多种 Call,例如 Call,或者Flowable(RxJava的Flowable对象)等,接口里的 Call 和 Retrofit 里的 OkHttpCall 并不一致,所以我们需要用一个 CallAdapter 去做一个适配转换。

Retrofit底层虽然使用了 OkHttpClient 去处理网络请求,但它并没有使用 okhttp3.Call 这个 Call 接口,而是自己又建了一个 retrofit2.Call 接口,OkHttpCall 继承的是 retrofit2.Call,与 okhttp3.Call 只是引用关系,即在 OkHttpCall 内部创建了 okhttp3.Call。
这样的设计符合依赖倒置原则,可以尽可能的与OkHttpClient解耦。

  • enqueue()

    异步请求,使用 okhttp3.Call 执行异步请求,在 okhttp3.Call 的请求回调中调用 parseResponse() 将 okhttp3.Response 转换为 retrofit2.Response,然后将这个 Response 传给 Retrofit 的 Callback,回调里调用了 ExecutorCallbackCall 的 execute(),通过主线程的 Handler 发送消息 post(runnable) 将回调切换到了主线程处理;

  • execute()

    同步请求,和异步请求一样,都是通过 okhttp3.Call 执行同步请求,接着调用 parseResponse() 将 okhttp3.Response 转换为 retrofit2.Response,由于同步请求没有在子线程中执行,不存在线程切换问题,直接返回结果。

  • createRawCall()

    创建一个 OkHttp 的 Call,即 okhttp3.Call,实际上是调用 ServiceMethod 的 toCall()` 创建而来的,由此可见 Retrofit 在整个请求流程中的分工是很明确的,OkHttpCall 就是和 OkHttp 建立连接的桥梁,它的任务就只是进行网络请求和响应回调处理。

  • parseResponse()

    okhttp3.Response 转换为 Retrofit 中的 Response,而要转换成我们想要的数据类型则需要通过 Converter 进行转换。

3. 总结

简单来说 Retrofit 虽然是网络框架,但它其实并没有做任何和网络请求相关的操作,这些都是交给 OkHttp 去完成的,而对于 Retrofit 而言,它被设计出来一定有目的的。

  1. 在实际业务中,我们的请求参数、请求方法、Call类型、数据转换器都可能不同,Retrofit 通过注解标注接口方法和接口方法参数,通过动态代理生成接口类对象,通过工厂模式支持多种类型的 Call 和 数据转换器,这在一定程度上满足了业务需求的多样性需求;
  2. 我们发现,在使用 Retrofit 时代码是极其简洁的,这得益于 Retrofit 使用了建造者模式创建 Retrofit,使用外观模式去实现内部 ServiceMethod 和 OkHttpCall 的创建和使用;
  3. 最后会发现,其实 Retrofit 主要的类就三个,而且它们分工明确,Retrofit 用于保存我们的 CallAdapter 和 Converter 工厂以及 OkHttpClient,ServiceMethod 用于解析方法注解和方法参数并转换为 HTTP 对应的请求方法,它保存了和请求有关的所有信息,最后 OkHttpCall 用于真正的网络请求和回调处理。

最后以一张 Retrofit 的整体流程图结束这次探索之旅:

4. 参考

  • 浅析 Retrofit 基本思想
  • 以 API 使用的角度来解析 Retrofit
  • Retrofit分析-漂亮的解耦套路

你可能感兴趣的:(开源框架 | Retrofit 源码解析)