Retrofit源码分析

Retrofit源码分析

Retrofit简介

是Square公司基于Okhttp封装的一款网络开源框架,简化了对网络的请求。

以下基于Retrofit2.1.0版本的分析,本文仿写 码老板的博客https://zhuanlan.zhihu.com/p/35121326关于“Retrofit原理解析最简洁的思路”。。

Retrofit使用

定义接口请求参数

public interface ApiService {

    @GET("app/{volumeId}/updateDefaultVolume")
    Call<ResponseBody> updateDefaultVolume(@Path("volumeId") String volumeId);
}

通过以上方式简化了网络请求,代码也更直观

实例化Retrofit

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("xxx")
        .build();

以上是最简单的Retrofit初始化方式,采用了链式调用的设计

获取接口实现类

ApiService apiService = retrofit.create(ApiService.class);
Call<ResponseBody> call = apiService.updateDefaultVolume("oo121o");

通过retrofit的create方法,获取到接口的实现类,接下来再调用自己定义的方法,进行网络请求。但是我们只定义了一个接口,并没有方法体,请求方式和参数都还是注解,那么方法体它是怎么生成的,接下来源码会具体进行讲解

进行网络请求

//同步网络请求
Request request = call.request();
//异步网络请求
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {

    }
});

以上就是retrofit的最简单的网络请求方法,由于是基于Okhttp的封装,其网络请求变得简洁。接下来具体分析起原理

Retrofit原理分析

retrofit初始化

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com")
        .build();

首先看Builder()源码

public Builder() {
  this(Platform.get());
}

this表示其调用自己的构造方法,如下

Builder(Platform platform) {
  this.platform = platform; //====1
  // Add the built-in converter factory first. This prevents overriding its behavior //but also
  // ensures correct behavior when using converters that consume all types.
  converterFactories.add(new BuiltInConverters());  //====2
}

1处就是简单的赋值,2处是转换器工厂添加各种类型转换器(暂时是这样理解),

点开上面的Platform.get()方法,

private static final Platform PLATFORM = findPlatform();//2

static Platform get() {
  return PLATFORM; //1
}

private static Platform findPlatform() {//3
  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) {
  }
  try {
    Class.forName("org.robovm.apple.foundation.NSObject");
    return new IOS();
  } catch (ClassNotFoundException ignored) {
  }
  return new Platform();
}

从上述代码可以发现get()方法是典型的饿汉式单例,这样写的好处是简单、线程安全、效率高、不会生成多个实例。1和2构成了典型的饿汉式单例。3就是判断系统,根据系统然后实例化不同的平台对象。这里跟我们相关的只有实例化Android这块,但是,在这不进行深究,略过。。。

baseUrl(“https://api.github.com”)源码分析

点开baseUrl(“https://api.github.com”)方法,查看其实现

/**
 * Set the API base URL.
 *
 * @see #baseUrl(HttpUrl)
 */
public Builder baseUrl(String baseUrl) {
  checkNotNull(baseUrl, "baseUrl == null"); //1 
  HttpUrl httpUrl = HttpUrl.parse(baseUrl);  //2
  if (httpUrl == null) {
    throw new IllegalArgumentException("Illegal URL: " + baseUrl);
  }
  return baseUrl(httpUrl);  //3
}

1处是对baseUrl进行判空处理,这个没啥可讲的。

2处是对baseUrl进行解析,可以进去查看parse方法,里面基本都是一些对路径的规范化判定

3处传入了一个HttpUrl对象,查看其实现,如下

public Builder baseUrl(HttpUrl baseUrl) {
  checkNotNull(baseUrl, "baseUrl == null");
  List<String> pathSegments = baseUrl.pathSegments();//获取到路径段列表
  if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { //路径结尾必须以“/”结尾
    throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
  }
  this.baseUrl = baseUrl;
  return this;
}

这个方法主要是给baseUrl赋值,同时规范baseUrl的路径展示形式,结尾必须以“/”结束。否则,会报错。。

总结,由以上可知,baseUrl方法主要是对路径进行了规范的判定,同时进行赋值。

build()方法源码解析

public Retrofit build() {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }

  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }

  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }

  // Make a defensive copy of the adapters and add the default Call adapter.
  List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

  // Make a defensive copy of the converters.
  List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

  return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
      callbackExecutor, validateEagerly);
}

这个方法主要是创建Retrofit的一个实例。并初始化一些参数对象。callFactory是okhttp的工厂实例,用于网络请求的,converterFactories是转换器工厂集合,adapterFactories是回调适配器工厂集合,callbackExecuto应该是回调接口实例,在这个方法中对这些对象进行了实例化,并最终返回Retrofit的实例。。。

接口实现类源码分析

ApiService apiService = retrofit.create(ApiService.class);
Call<ResponseBody> call = apiService.updateDefaultVolume("oo121o");

通过调用Retrofit的create方法获取到接口的实例,那么为什么调用create方法会生成接口实例??点击打开create方法源码

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service); //验证传入的class是否标准的接口类
  if (validateEagerly) {
    eagerlyValidateMethods(service); //把接口类里面的定义方法加入LinkedHashMap集合中
  }
  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);
        }
      });
}

最后一段代码Proxy.newProxyInstance…采用了 动态代理 的设计模式,而且这个方法封装得非常好,我么只需要传入相应的接口类,就能获取到其实例。遵循了 迪米特原则(最少知道原则)。

代理也称“委托”,分为静态代理和动态代理,代理模式也是常用的设计模式之一,具有方法增强、高扩展性的设计优势。其设计就是限制对象的直接访问。。动态代理是JDK提供的代理方式且只支持接口,在JVM虚拟机运行时动态生成一系列代理,主要通过Java提供的InvocationHanler类实现。写一个类实现InvocationHanler接口,实现invoke方法,调用Proxy.newProxyInstance返回实例。

Retrofit在重写这个方法时,主要做了三件事

1.判断该接口类是不是一个Object.class,就直接返回方法原有的返回值

2.判断该方法是否是DefaultMethod,

3.构建一个ServiceMethod对象和OkHttpCall对象,并通过serviceMethod.callAdapter.adapt(okHttpCall)将这两个对象关联起来。点开adapt,发现其是CallAdapter接口类的一个方法,如下

<R> T adapt(Call<R> call);

那这个方法是在那里进行实现的呢,在上面讲解build()源码方法时,即Retrofit实例化时,有下面一段代码:

List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

而adapterFactories其实就是CallAdapter的一个工厂类集,点开defaultCallAdapterFactory方法,如下

CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
  if (callbackExecutor != null) {
    return new ExecutorCallAdapterFactory(callbackExecutor);
  }
  return DefaultCallAdapterFactory.INSTANCE; //获取到默认的CallAdapter的工厂类
}

//接下来打开DefaultCallAdapterFactory类,发现其是继承自CallAdapter.Factory

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

  @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<Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public <R> Call<R> adapt(Call<R> call) {
        return call; //1在这里CallAdapter的接口方法adapt被重写,并返回Call的类型
      }
    };
  }
}

上述代码看出,在Retrofit初始化实例的时候,CallAdapter接口方法adapt就已经被重写了,并且 返回的Call的类型。

到这里为止,我们就知道了网络请求结果返回的是Call类型。

请求参数以及请求方式的解析

在我们上面提到的create方法中有这么一段代码,如下

ServiceMethod serviceMethod = loadServiceMethod(method);

这段代码主要就是对接口参数以及请求方式的解析,其中method表示接口方法,点开loadServiceMethod方法,查看其具体实现。

ServiceMethod loadServiceMethod(Method method) {//传入一个方法体
  ServiceMethod result;
  synchronized (serviceMethodCache) { //采用同步锁,保证访问serviceMethodCache对象的唯一性
    result = serviceMethodCache.get(method); //从serviceMethodCache这个hashmap中取method
    if (result == null) {
      result = new ServiceMethod.Builder(this, method).build();
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

从以上代码可知,其核心的代码就是result = new ServiceMethod.Builder(this, method).build()这句,点开Builder(this, method).法,代码如下

public Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit; 
  this.method = method;
  this.methodAnnotations = method.getAnnotations();//接口方法的注解,在Retrofit中为请求方式
  this.parameterTypes = method.getGenericParameterTypes();//参数类型
  this.parameterAnnotationsArray = method.getParameterAnnotations();//参数注解数组
}

以上代码主要是对一些对象进行了实例化,并没有什么核心操作,接下来看build()这个方法。代码如下

public ServiceMethod build() {
  callAdapter = createCallAdapter();  //(1)
  responseType = callAdapter.responseType(); 
  if (responseType == Response.class || responseType == okhttp3.Response.class) {
    throw methodError("'"
        + Utils.getRawType(responseType).getName()
        + "' is not a valid response body type. Did you mean ResponseBody?");
  }
  responseConverter = createResponseConverter(); //(2)

  for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation); //(3)
  }

 ...省略代码...

//(4)======
  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.");
    }
	//(5)===
    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }

   ...省略代码...
   
  return new ServiceMethod<>(this);
}

从上述代码,并查看其相关源码可知,感兴趣的可以自行查看,

(1).初始化了一个可用的CallAdapter实例,循环遍历adapterFactories工厂,取出可用的CallAdapter对象

(2).初始化了一个可用的Converter<ResponseBody, T>实例,循环遍历converterFactories工厂,取出可用的Converter对象

(3).parseMethodAnnotation(annotation)用来解析注解的请求方式,这里我们只讲解GET请求方式,点开查看其源码。其它 请求方式相似。如下所示

private void parseMethodAnnotation(Annotation annotation) {
  ...省略代码...
  if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
  }
   ...省略代码...
}

打开parseHttpMethodAndPath方法,查看其实现
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
      if (this.httpMethod != null) {
        throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
            this.httpMethod, httpMethod);
      }
      this.httpMethod = httpMethod;  // 赋值
      this.hasBody = hasBody; //赋值

      if (value.isEmpty()) {
        return;
      }

      // Get the relative URL path and existing query string, if present.
      //校验value的值是否合法,规则就是不能有“?”如果有则需要使用@Query注解
      int question = value.indexOf('?');
      if (question != -1 && question < value.length() - 1) {
        // Ensure the query string does not have any named parameters.
        String queryParams = value.substring(question + 1);
        Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
        if (queryParamMatcher.find()) {
          throw methodError("URL query string \"%s\" must not have replace block. "
              + "For dynamic query parameters use @Query.", queryParams);
        }
      }

      this.relativeUrl = value;//赋值给相对路径relativeUrl,相当于省略域名的URL
      this.relativeUrlParamNames = parsePathParameters(value);//解析路径参数
      //到这里,我们能得到app/{volumeId}/updateDefaultVolume这样的一个路径,大括号里面的值就是我们需要赋值的参数。
    }

(4).先获取注解数组parameterAnnotationsArray的长度parameterCount,然后遍历循环parameterAnnotationsArray数组,获取参数类型以及参数注解。

(5).把上面获取的参数类型和注解放在同一个方法中进行解析,

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

parameterType:参数类型

parameterAnnotations:参数注解

通过parseParameter方法对parameterType和parameterAnnotations进行解析,并返回ParameterHandler<?>对象,赋值给对应的原始数组对象。那么parseParameter到底是怎么解析的呢,我们来打开parseParameter这个方法,

private ParameterHandler<?> parseParameter(
    int p, Type parameterType, Annotation[] annotations) {
  ParameterHandler<?> result = null;
  for (Annotation annotation : annotations) {
    ParameterHandler<?> annotationAction = parseParameterAnnotation(
        p, parameterType, annotations, annotation);
 ...省略代码...
   }
 ...省略代码...
  return result;
}


发现还是遍历注解,且赋值。继续打开parseParameterAnnotation方法,当我们查看parseParameterAnnotation( p, parameterType, annotations, annotation)这个方法时,发现其代码量多达400行左右,且里面逻辑大多一致,我们就以Path参数注解为例讲解。

private ParameterHandler<?> parseParameterAnnotation(
    int p, Type type, Annotation[] annotations, Annotation annotation) {
    
    ...省略代码...
    
  if (annotation instanceof Path) {
   ...省略代码...

    Path path = (Path) annotation;
    String name = path.value(); //获取到参数名,即获取@Path("volumeId")中的参数volumeId,
    validatePathName(p, name); //验证参数名是否合法

    Converter<?, String> converter = retrofit.stringConverter(type, annotations);
    return new ParameterHandler.Path<>(name, converter, path.encoded());
  }

...省略代码...

  return null; // Not a Retrofit annotation.
}

重点关注stringConverter(type, annotations)这个方法,点开stringConverter方法,

public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {
  checkNotNull(type, "type == null");
  checkNotNull(annotations, "annotations == null");

  for (int i = 0, count = converterFactories.size(); i < count; i++) {
    Converter<?, String> converter =
        converterFactories.get(i).stringConverter(type, annotations, this);
    if (converter != null) {
      //noinspection unchecked
      return (Converter<T, String>) converter;
    }
  }

  // Nothing matched. Resort to default converter which just calls toString().
  //noinspection unchecked
  return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
}

首先它会循环遍历转换器工厂converterFactories这个数组,以其能从converterFactories中获取到Converter<?, String>对象,并返回。但是,继续点开stringConverter(type, annotations, this)这个方法。

public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
  return null;
}

你会发现它一直是返回为null的,所以stringConverter(type, annotations)这个方法会执行接下来的这个方法,(Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE,可以看出它应该调用的是一个静态对象,点开ToStringConverter类,发现其是一个接口实现类,用来重写Converter<Object, String>接口的convert方法,并返回一个字符串,这个字符串其实就是参数注解名。

static final class ToStringConverter implements Converter<Object, String> {
  static final ToStringConverter INSTANCE = new ToStringConverter();

  @Override public String convert(Object value) {
    return value.toString();
  }
}

那么Converter<Object, String>这个接口,它是在哪里进行实现的呢,接下来我们要讲new ParameterHandler.Path<>(name, converter, path.encoded())这个方法,而Converter<Object, String>接口的实现就在这个方法中。点开Path,查看其源码

static final class Path<T> extends ParameterHandler<T> {
  private final String name;
  private final Converter<T, String> valueConverter;
  private final boolean encoded;

  Path(String name, Converter<T, String> valueConverter, boolean encoded) {
    this.name = checkNotNull(name, "name == null");
    this.valueConverter = valueConverter;
    this.encoded = encoded;
  }

  @Override void apply(RequestBuilder builder, T value) throws IOException {
    if (value == null) {
      throw new IllegalArgumentException(
          "Path parameter \"" + name + "\" value must not be null.");
    }
    builder.addPathParam(name, valueConverter.convert(value), encoded);
  }
}

Path里面首先市实例化了一些对象:参数名name,转换器接口实例valueConverter,是否编码encoded。

接下来重写了ParameterHandler这个抽象类里面的apply方法,在这个方法里面有一个builder.addPathParam(name, valueConverter.convert(value), encoded)方法,其中有传入一个参数 valueConverter.convert(value),valueConverter它是Converter<Object, String>接口的实例对象,而convert就是接口方法,所以,Converter<Object, String>接口的调用就是在这里。然后,接下来,我们分析addPathParam这个方法用来干啥的,点开addPathParam方法,发现它是对路径进行了一个替换处理,

void addPathParam(String name, String value, boolean encoded) {
  if (relativeUrl == null) {
    // The relative URL is cleared when the first query parameter is set.
    throw new AssertionError();
  }
  relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));
}

这个方法把我们传进来的值value按照编码格式转换,然后替换relativeUrl中的{name},构成一个有效的省略域名的URL。至此,URL的拼接已经完成!

总结:Retrofit采用动态代理实现了我们定义的网络请求,并在代理实现方法invoke中创建了ServiceMethod对象,在构建这个对象的过程中,对注解的请求方式进行解析并得到了网络请求方式HttpMethod,以及参数的注解分析,拼接成一个省略域名的路径URL

Retrofit网络请求

Retrofit的网络请求其实在创建接口实例化的时候,就已经开始了,动态代理创建时,其实现方法invoke里面会实例一个OkHttpCall对象okHttpCall,点开会发现OkHttpCall这个类是用来实现Call这个接口的,然后重写了Call里面的方法。这里只分析接口Call的enqueue方法。即异步网络请求方式。

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


这里只展示enqueue方法
final class OkHttpCall<T> implements Call<T> {
 。。。
  OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
    this.serviceMethod = serviceMethod;
    this.args = args;
  }
  @Override public void enqueue(final Callback<T> callback) {
    if (callback == null) throw new NullPointerException("callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          failure = creationFailure = t;
        }
      }
    }
     。。。
  }
}

这里关注call的创键时机,通过上述代码可知,createRawCall方法它会返回一个call的对象,打开createRawCall查看其源码。

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;
}

createRawCall方法中首先,它会创建一个Http Request的请求对象request,接下来它会调用Call.Factory接口中的newCall方法,并返回一个Call对象,注意,Call.Factory接口中方法实现在Retrofit的build()方法中就已经被重写了,如下build()方法中的一段,初始化OkHttpClient对象。。

okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
  callFactory = new OkHttpClient();
}

OkHttpClient重写了Call.Factory接口
public class OkHttpClient implements Cloneable, Call.Factory {
。。。
}

总结:Retrofit主要是在create方法中采用动态代理模式实现接口方法,这个过程构建了一个ServiceMethod对象,根据方法注解获取请求方式,参数类型和参数注解拼接请求的链接,当一切都准备好之后会把数据添加到Retrofit的RequestBuilder中。然后当我们主动发起网络请求的时候会调用okhttp发起网络请求,okhttp的配置包括请求方式,URL等在Retrofit的RequestBuilder的build()方法中实现,并发起真正的网络请求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值