解锁Retrofit -- 浅析Retrofit源码

前言

在Android的异步网络请求中,一般都会使用到一些优秀的开源库来简化网络请求与线程切换过程,Retrofit+RxJava也是时下最热门的搭配,对与Retrofit的学习,也给我带来的极大的收获,特别是了解其中的一些套路,会给自己的开发带来很大的启示。

1.主线任务

在具体分析之前,采用单步调试的方式先把主线拎出来,理解大致的运行过程:


在开始分析之前,有两个需要明白的地方:
1.Retrofit只能算是一个加强版的API管理库,具体的网络请求是依赖内部的OkHttp来实现的。
2. Retrofit是通过动态代理解析注解来构建Request发起请求的。

从图中可以看到:
1.在发起请求的时候,Retrofit会尝试去加载一个ServiceMethod,这个ServiceMethod的作用是封装了Retrofit的三大核心功能:CallAdapter、Converter和从注解解析出的Request(具体过程后面会提到)。当然,由于每次调用ApiService接口中的方法时都会通过动态代理进行拦截,然后执行上图的一整套过程,所以为了避免每次都去花费过多的时间去创建ServiceMathod,就需要将它缓存到一个Map中,以减少开销。
2.在拿到ServiceMethod过后,需要将它交给OkHttpCall,它其实就相当于是一个OkHttp的包装类,可以用来执行具体的请求,这里只是将ServiceMethod和接口方法中的参数传了进去,给OkHttpCall的成员变量赋值,暂时没有做什么具体的工作。
3.接下来就是将OkHttpCall交给CallAdapter进行适配的,这里就要分情况进行分析了,如果指定了常用的RxJavaCallAdapter,自然就会将OkHttpCall转化为Observable类型的Call;如果没有制定具体的CallAdapter,则会将使用默认的Call(也就是ExecutorCallbackCall,后面会提到);当然,这里完全可以由开发者更具业务需求,自定义一个CallAdapter,这也是我们可以扩展的地方。
4.经过上一步的操作,用于请求的Call也得到了,就该使用OkHttpClient来执行真正的网络请求了。
5.在请求执行完,服务器返回结果之后就会使用Converter来讲结果转换为我们需要的数据格式。
6.最终,采用RxJava或者直接使用Handler切换到到主线程,数据也在这个时候回调到了主线程。

2.理解三个大的模块的逻辑

我认为学习Retrofit主要需要从三个大的方向去入手:Request的构建方式、CallAdapter的创建或注入时机、Converter的创建或注入时机。下面就分别跟着源码来进行一番探索:

2.1 使用动态代理+注解+反射的方式来构建Request

在第一次使用Retrofit的时候,肯定会被这种配置请求方式的形式吸引到,没想到还有注解这种操作..
以常用的GET方式为例,来看看究竟是怎么将这些注解和接口方法转化为请求需要的Call的。

public interface ApiService {
    @GET("top250")
    Call getMovie(@Query("start") int start, @Query("count") int count);
}

在配置Request信息的时候只需要在在接口中使用注解即可,使用的时候直接调用:
ApiService apiService = retrofit.create(ApiService.class);
这一句话就可以拿到ApiServerce的实例,往往外层简单的东西,内层都不简单,进入create() 方法一探究竟:

 public  T create(final Class<T> service) {
   ···    
   return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service },
        new InvocationHandler() {
           ···
            ServiceMethod serviceMethod =
                (ServiceMethod) loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  } 
  

仔细一看,这里直接返回了一个动态代理的代理对象,回想一下动态代理的作用,拦截接口中的方法,在方法调用前后加上自己的逻辑,而这里的主要操作是通过动态代理创建了一个ServiceMethodOkHttpCall
先去看看ServiceMethod是个什么东西,这个类的注释写得很精辟:“Adapts an invocation of an interface method into an HTTP call(将一个接口中方法的调用时配成一个HTTP的Call)”。(因为Retrofit内部是基于OkHttpCall来完成具体的请求的,所以对于Call这个类应该很好理解,有了它,便可以发起网络请求。)
跟进loadServiceMethod()方法:

ServiceMethod, ?> loadServiceMethod(Method method) {
    //现尝试从缓存中取出ServiceMethod,如果取到了就直接返回
    ServiceMethod, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
          //缓存中没有的话,就构造一个ServiceMethod,再加入缓存
        result = new ServiceMethod.Builder<>(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

这里逻辑还是很简单,就是对ServiceMethod进行了一个缓存的操作,不然每次调用ApiServerce中的方法的时候都会创建一个ServiceMethod的实例,势必会带来太大的性能开销。
这里采用了建造者模式的思想来构建ServiceMethod的实例,所以直接进入build() 方法,可以看到便可以看到解析通过反射拿到的注解的方法–parseMethodAnnotation()

public ServiceMethod build() {
     ···      
     for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
     }

    ···

      return new ServiceMethod<>(this);
    }

进入parseMethodAnnotation()方法查看一番:

 private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        ···
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if   ···
    }

一大波if - else来临,这里通过对请求方法中注解的请求方式的判断(这里以“GET”为例),从而调用parseHttpMethodAndPath()来解析出之前配置的URL信息:

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
        ···
      this.httpMethod = httpMethod;
      this.hasBody = hasBody;

        ···

      this.relativeUrl = value;
      this.relativeUrlParamNames = parsePathParameters(value);
    }

比如对应于本文开头的这样一个接口方法:

  @GET("top250")
  Call getMovie(@Query("start") int start, @Query("count") int count);

这个时候parseHttpMethodAndPath()的形参的值分别是:(“GET”,”top250”,false),接下来就是真正的解析工作了。在解析完方法的注解后,就该解析方法的参数的注解了,回到build()方法中可以看到:

public ServiceMethod build() {
     ···      
    int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
            ···
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
          ···

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
    ···

      return new ServiceMethod<>(this);
    }

parseParameter()方法就是用来解析参数注解的,具体的解析过程就不深入了,只要知道build()方法里面将会完成我们所配置的注解的解析。
总结一下,简单跟踪了一下源码就发现了Retrofit是采用动态代理的方式拦截接口中的方法,然后通过反射拿到方法注解,最后解析注解,使用这种方式的好处是可以给上层提供一种方便的方式配置请求的API,所以在我看来,Retrofit本质上是一个管理API的库,真正的网络请求部分是交给OkHTTP库来完成的。

2.2 添加CallAdapter转换OkHttpCall

Retrofit的一大优势就是添加了对RxJava的支持,也就是可以再构造Retrofit的时候添加一个RxJava2CallAdapterFactory,这大大增强了Retrofit的战斗力,那么要是没有添加其他的CallAdapter,它又是如何工作的呢?肯定会有一个默认的CallAdapter,跟着源码来查看一番:
在Retrofit#create()方法里可以看到,动态代理最后返回的是:serviceMethod.callAdapter.adapt(okHttpCall)

  public  T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service },
        new InvocationHandler() {
            ···
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

跟踪进去最终便可以跟到:
ExecutorCallAdapterFactory#get(),最终返回的是一个ExecutorCallbackCall

  @Override
  public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public Call adapt(Call call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  } 
  

这便是默认的的Call了,默认情况下最后也是将它交给了OkHttpClient完成请求。
这里Call完全可以由开发者根据自己的项目的业务逻辑来做具体的开发,灵活性也比较高。

2.3 提供灵活的API来设置Converter

回忆一下,在没有Gson等反序列化的开源库的时候,往往需要用JsonObject和JsonArray来完成反序列化操作,当出现Gson这类开源库的时候,反序列化的步骤优雅了很多,从Json转化到JavaBean的过程我们也不需要花过多的时间关注了,在构建Retrofit的时候提供了一个addConverterFactory()方法来添加一个转换器,这里也支持多种官方提供的转换器:

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

那么转化器是在什么时候构造的?其实Converter也是在ServiceMethod中创建的,跟着代码来分析一下:

 public ServiceMethod build() {
    ···
      responseConverter = createResponseConverter();
      ···

 }

其实build()方法很显眼的位置就有一个createResponseConverter()方法,来创建Converter:

private Converter createResponseConverter() {
      Annotation[] annotations = method.getAnnotations();
        ···
      return retrofit.responseBodyConverter(responseType, annotations);
        ···
}

在这个方法里面也会先拿到方法的注解,再将拿到的注解和Response的类型传入responseBodyConverter()方法,进行转换操作,最便可将服务器返回的流文件反序列化成我们需要的内容。
其实这里使用官方提供的几种Converter也差不多能满足日常开发的需求的,基本不需要怎么扩展。

到这里对于整个Retrofit的简要分析也完成,最后来说几点自己的愚见吧:
1.Retrofit的在实现缓存策略的时候是将CallAdapter,Converter和解析注解的模块都封装进了ServiceMethod,然而在代码里面可以看到OkHttpCall的构建是需要传入ServiceMethod的,再将OkHttpCall适配成其他的Call的时候,又需要将OkHttpCall传入ServiceMethod,这样的操作貌似看起来并不是很优雅,如果单独单独缓存CallAdapter应该会优雅一点。

OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);

return serviceMethod.callAdapter.adapt(okHttpCall);

但其实也并不影响使用,CallAdapter还是可以灵活替换的。
2.在Retrofit底层将完成网络请求的操作限定死了,只能用OkHttp,要是以后出现了更优秀的网络请求库,可能要替换也有些麻烦。(当然都是自己家的东西,估计近几年也不会有什么可替换的)。
3.对于这么优秀的库还有很多细节没有去深究,等以后遇到具体的需求之后去研究起来肯定又有更多的体会了。
4.在Rtrofit中运用了许多有用的设计模式,如:动态代理,Buider模式,工厂模式,对于学习的价值还是很大的。

你可能感兴趣的:(Android,android,源码,retrofit2)