开源框架 | Retrofit 源码解析

2020-09-09  本文已影响0人  南子李

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

1. 使用步骤

2. 源码解析

2.1 Retrofit.Builder

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

2.2 Retrofit
  public <T> T create(final Class<T> 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<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }

重点来看 loadServiceMethod() 方法:

  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 retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
    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);
    }
  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<Object>[] handlers = (ParameterHandler<Object>[]) 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
  }
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }
  T adapt(Call<R> call) {
    return callAdapter.adapt(call);
  }

2.4 OkHttpCall

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

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

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. 参考

上一篇 下一篇

猜你喜欢

热点阅读