拆轮子系列:拆 Retrofit
本文是 Piasy 原创,发表于 https://blog.piasy.com,请阅读原文支持原创https://blog.piasy.com/2016/06/25/Understand-Retrofit/
安卓开发领域,很多重要的问题都有了很好的开源解决方案,例如网络请求 OkHttp + Retrofit 简直就是不二之选。“我们不重复造轮子不表示我们不需要知道轮子该怎么造及如何更好的造!”,在用了这些好轮子将近两年之后,现在是时候拆开轮子一探究竟了。本文基于 Retrofit 截至 2016.6.23 的最新源码对其进行了详细分析。
1,整体思路
从使用方法出发,首先是怎么使用,其次是我们使用的功能在内部是如何实现的,实现方案上有什么技巧,有什么范式。全文基本上是对 Retrofit 源码的一个分析与导读,非常建议大家下载 Retrofit 源码之后,跟着本文,过一遍源码。对于技巧和范式,由于目前我的功力还不到位,分析内容没多少,欢迎大家和我一起讨论。
2,基本用例
来自 Retrofit 官方网站。
2.1,创建 Retrofit 对象
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();
builder 模式,外观模式(门面模式),这就不多说了,可以看看 stay 的 Retrofit分析-经典设计模式案例这篇文章。
2.2,定义 API 并获取 API 实例
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}
GitHubService github = retrofit.create(GitHubService.class);
先看定义,非常简洁,也没有什么特别之处,除了两个注解:@GET 和 @Path。它们的用处稍后再分析,我们接着看创建 API 实例:retrofit.create(GitHubService.class)。这样就创建了 API 实例了,就可以调用 API 的方法发起 HTTP 网络请求了,太方便了。
但 create 方法是怎么创建 API 实例的呢?
public <T> T create(final Class<T> service) {
  // 省略非关键代码
  return (T) Proxy.newProxyInstance(service.getClassLoader(), 
      new Class<?>[] { service },
      new InvocationHandler() {
        @Override 
        public Object invoke(Object proxy, Method method, Object... args)
            throws Throwable {
          // 先省略实现
        }
      });
}
创建 API 实例使用的是动态代理技术,关于动态代理的详细介绍,可以查看 codeKK 公共技术点之 Java 动态代理这篇文章。
简而言之,就是动态生成接口的实现类(当然生成实现类有缓存机制),并创建其实例(称之为代理),代理把对接口的调用转发给 InvocationHandler 实例,而在 InvocationHandler 的实现中,除了执行真正的逻辑(例如再次转发给真正的实现类对象),我们还可以进行一些有用的操作,例如统计执行时间、进行初始化和清理、对接口调用进行检查等。
为什么要用动态代理?因为对接口的所有方法的调用都会集中转发到 InvocationHandler#invoke 函数中,我们可以集中进行处理,更方便了。你可能会想,我也可以手写这样的代理类,把所有接口的调用都转发到 InvocationHandler#invoke 呀,当然可以,但是可靠地自动生成岂不更方便?
2.3,调用 API 方法
获取到 API 实例之后,调用方法和普通的代码没有任何区别:
Call<List<Repo>> call = github.listRepos("square");
List<Repo> repos = call.execute().body();
这两行代码就发出了 HTTP 请求,并把返回的数据转化为了 List<Repo>,太方便了!
现在我们来看看调用 listRepos 是怎么发出 HTTP 请求的。上面 Retrofit#create 方法返回时省略的代码如下:
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 {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        ServiceMethod serviceMethod = loadServiceMethod(method);
        OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
        return serviceMethod.callAdapter.adapt(okHttpCall);
      }
    });
如果调用的是 Object 的方法,例如 equals,toString,那就直接调用。如果是 default 方法(Java 8 引入),就调用 default 方法。这些我们都先不管,因为我们在安卓平台调用 listRepos,肯定不是这两种情况,那这次调用真正干活的就是这三行代码了(好好记住这三行代码,因为接下来很长的篇幅都是在讲它们 :) ):
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
在继续分析这三行代码之前,我们先看看 Stay 在 Retrofit分析-漂亮的解耦套路 这篇文章中分享的流程图,完整的流程概览建议仔细看看这篇文章:

这三行代码基本就是对应于流程图中轴上部了,ServiceMethod,build OkHttpCall,CallAdapter adapt。
2.4,ServiceMethod<T>
ServiceMethod<T> 类的作用正如其 JavaDoc 所言:
Adapts an invocation of an interface method into an HTTP call. 把对接口方法的调用转为一次 HTTP 调用。
一个 ServiceMethod 对象对应于一个 API interface 的一个方法,loadServiceMethod(method) 方法负责加载 ServiceMethod:
ServiceMethod loadServiceMethod(Method method) {
  ServiceMethod result;
  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      result = new ServiceMethod.Builder(this, method).build();
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}
这里实现了缓存逻辑,同一个 API 的同一个方法,只会创建一次。这里由于我们每次获取 API 实例都是传入的 class 对象,而 class 对象是进程内单例的,所以获取到它的同一个方法 Method 实例也是单例的,所以这里的缓存是有效的。
我们再看看 ServiceMethod 的构造函数:
ServiceMethod(Builder<T> builder) {
  this.callFactory = builder.retrofit.callFactory();
  this.callAdapter = builder.callAdapter;
  this.baseUrl = builder.retrofit.baseUrl();
  this.responseConverter = builder.responseConverter;
  this.httpMethod = builder.httpMethod;
  this.relativeUrl = builder.relativeUrl;
  this.headers = builder.headers;
  this.contentType = builder.contentType;
  this.hasBody = builder.hasBody;
  this.isFormEncoded = builder.isFormEncoded;
  this.isMultipart = builder.isMultipart;
  this.parameterHandlers = builder.parameterHandlers;
}
成员很多,但这里我们重点关注四个成员:callFactory,callAdapter,responseConverter 和parameterHandlers。
- callFactory负责创建 HTTP 请求,HTTP 请求被抽象为了- okhttp3.Call类,它表示一个已经准备好,可以随时执行的 HTTP 请求;
- callAdapter把- retrofit2.Call<T>转为- T(注意和- okhttp3.Call区分开来,- retrofit2.Call<T>表示的是对一个 Retrofit 方法的调用),这个过程会发送一个 HTTP 请求,拿到服务器返回的数据(通过- okhttp3.Call实现),并把数据转换为声明的- T类型对象(通过- Converter<F, T>实现);
- responseConverter是- Converter<ResponseBody, T>类型,负责把服务器返回的数据(JSON、XML、二进制或者其他格式,由- ResponseBody封装)转化为- T类型的对象;
- parameterHandlers则负责解析 API 定义时每个方法的参数,并在构造 HTTP 请求时设置参数;
它们的使用稍后再分析,这里先看看它们的创建(代码比较分散,就不贴太多代码了,大多是结论):
2.4.1,callFactory
this.callFactory = builder.retrofit.callFactory(),所以 callFactory 实际上由 Retrofit类提供,而我们在构造 Retrofit 对象时,可以指定 callFactory,如果不指定,将默认设置为一个 okhttp3.OkHttpClient。
2.4.2,callAdapter
private CallAdapter<?> createCallAdapter() {
  // 省略检查性代码
  Annotation[] annotations = method.getAnnotations();
  try {
    return retrofit.callAdapter(returnType, annotations);
  } catch (RuntimeException e) { 
    // Wide exception range because factories are user code.
    throw methodError(e, "Unable to create call adapter for %s", returnType);
  }
}
可以看到,callAdapter 还是由 Retrofit 类提供。在 Retrofit 类内部,将遍历一个CallAdapter.Factory 列表,让工厂们提供,如果最终没有工厂能(根据 returnType 和annotations)提供需要的 CallAdapter,那将抛出异常。而这个工厂列表我们可以在构造Retrofit 对象时进行添加。
2.4.3,responseConverter
private Converter<ResponseBody, T> createResponseConverter() {
  Annotation[] annotations = method.getAnnotations();
  try {
    return retrofit.responseBodyConverter(responseType, annotations);
  } catch (RuntimeException e) { 
    // Wide exception range because factories are user code.
    throw methodError(e, "Unable to create converter for %s", responseType);
  }
}
同样,responseConverter 还是由 Retrofit 类提供,而在其内部,逻辑和创建 callAdapter 基本一致,通过遍历 Converter.Factory 列表,看看有没有工厂能够提供需要的 responseBodyConverter。工厂列表同样可以在构造 Retrofit 对象时进行添加。
2.4.4,parameterHandlers
每个参数都会有一个 ParameterHandler,由 ServiceMethod#parseParameter 方法负责创建,其主要内容就是解析每个参数使用的注解类型(诸如 Path,Query,Field 等),对每种类型进行单独的处理。构造 HTTP 请求时,我们传递的参数都是字符串,那 Retrofit 是如何把我们传递的各种参数都转化为 String 的呢?还是由 Retrofit 类提供 converter!
Converter.Factory 除了提供上一小节提到的 responseBodyConverter,还提供 requestBodyConverter 和 stringConverter,API 方法中除了 @Body 和 @Part 类型的参数,都利用 stringConverter 进行转换,而 @Body 和 @Part 类型的参数则利用 requestBodyConverter 进行转换。
这三种 converter 都是通过“询问”工厂列表进行提供,而工厂列表我们可以在构造 Retrofit 对象时进行添加。
2.4.5,工厂让各个模块得以高度解耦
上面提到了三种工厂:okhttp3.Call.Factory,CallAdapter.Factory 和 Converter.Factory,分别负责提供不同的模块,至于怎么提供、提供何种模块,统统交给工厂,Retrofit 完全不掺和,它只负责提供用于决策的信息,例如参数/返回值类型、注解等。
这不正是我们苦苦追求的高内聚低耦合效果吗?解耦的第一步就是面向接口编程,模块之间、类之间通过接口进行依赖,创建怎样的实例,则交给工厂负责,工厂同样也是接口,添加(Retrofit doc 中使用 install 安装一词,非常贴切)怎样的工厂,则在最初构造 Retrofit 对象时决定,各个模块之间完全解耦,每个模块只专注于自己的职责,全都是套路,值得反复玩味、学习与模仿。
除了上面重点分析的这四个成员,ServiceMethod 中还包含了 API 方法的 url 解析等逻辑,包含了众多关于泛型和反射相关的代码,有类似需求的时候,也非常值得学习模仿。
2.5,OkHttpCall
终于把 ServiceMethod 看了个大概,接下来我们看看 OkHttpCall。
OkHttpCall 实现了 retrofit2.Call,我们通常会使用它的 execute() 和 enqueue(Callback<T> callback) 接口。前者用于同步执行 HTTP 请求,后者用于异步执行。
2.5.1,先看 execute()
@Override 
public Response<T> execute() throws IOException {
  okhttp3.Call call;
  synchronized (this) {
    // 省略部分检查代码
    call = rawCall;
    if (call == null) {
      try {
        call = rawCall = createRawCall();
      } catch (IOException | RuntimeException e) {
        creationFailure = e;
        throw e;
      }
    }
  }
  return parseResponse(call.execute());
}
private okhttp3.Call createRawCall() throws IOException {
  Request request = serviceMethod.toRequest(args);
  okhttp3.Call call = serviceMethod.callFactory.newCall(request);
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
  ResponseBody rawBody = rawResponse.body();
  // Remove the body's source (the only stateful object) so we can pass the response along.
  rawResponse = rawResponse.newBuilder()
      .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
      .build();
  int code = rawResponse.code();
  if (code < 200 || code >= 300) {
    // ...返回错误
  }
  if (code == 204 || code == 205) {
    return Response.success(null, rawResponse);
  }
  ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
  try {
    T body = serviceMethod.toResponse 
                     
                    
                 
                    
                