Retrofit 阅读笔记

895 阅读8分钟

前言

Retrofit 是现在 Android 主流的网络请求库,对比 Volley 它解耦更加彻底,使用更加方便,而且支持 RxJava。具体的区别的可以看看这篇文章——OkHttp, Retrofit, Volley应该选择哪一个?

Retrofit 中文可以翻译成 在原有基础上改进。它的底层基于 OkHttp。

Retrofit 的精简流程图,图片来自Stay:

Retrofit 中使用了大量设计模式,分析源码之前最好先熟悉一下这些模式。

  1. 建造者模式
  2. 工厂模式
  3. 外观模式
  4. 策略模式
  5. 适配器模式
  6. 装饰模式
  7. 代理模式及 Java 动态代理

还涉及一些基础的 Java 注解的知识,建议先打好基础再分析源码。

Retrofit的类的结构

上层有4个抽象接口,有默认的实现类。核心服务类是ServiceMethod。Platform判断Android,Java平台。

Call

/**
 * 这是一个Retrofit方法给服务器发送一个request,返回一个response的调用。
 * 每个Call生产一组HTTP request和response。对于同一个完全一样请求,实现
 * clone方法来创建多个call,这个可以用于轮询和错误重试的场景。
 *
 * Calls 同步执行的时候使用 execute(), 异步执行使用 enqueue()。无论是
 * 同步还是异步都可以在请求的时候使用 cancel() 随时被取消。正在写入request或者读取response的Call可能会引起IOException。
 *
 *
 * @param <T> 请求成功时返回的 response body 类型
 */
public interface Call<T> extends Cloneable {
 
  // 同步发起请求返回response
  Response<T> execute() throws IOException;

  

  // 异步发起请求,结果返回给回调
  void enqueue(Callback<T> callback);

 

  // 如果call已经调用了execute()或者enqueue()就返回true。不允许一个call重复请求。
  boolean isExecuted();

  

  // 取消正在执行中的请求,如果call还没开始执行请求,就不做任何处理。
  void cancel();


  // 是否取消了请求
  boolean isCanceled();


  // 创建一个新的和当前完全一样的call
  Call<T> clone();
  
  // 原始的HTTP请求
  Request request();
}

CallAdapter

用于RxJava的转化。

public interface CallAdapter<R, T> {
  // 返回解析成java对象的response的类型。
  Type responseType();

  T adapt(Call<R> call);
  
  // 抽象工厂类
  abstract class Factory {
   
    public abstract CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);

    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
}

Callback

public interface Callback<T> {

  void onResponse(Call<T> call, Response<T> response);

  void onFailure(Call<T> call, Throwable t);
}

Converter

public interface Converter<F, T> {
  // 实体类和HTTP的RequestBody和ResponseBody的互相转换
  T convert(F value) throws IOException;

  // 抽象工厂
  abstract class Factory {
    
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

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

正常请求网络的套路

QQ20170102-1928262x.png
如果我们自己写一个网络请求模块一般是这样的套路:

  1. build request 参数,加入到请求队列中
  2. 在子线程轮询执行
  3. 得到服务器数据后,回调给上层

Retrofit 不外乎也是这种套路,那么它到底有什么精妙的地方呢?

举个栗子

先看一个正常 Retrofit 请求的例子:

Retrofit build = new Retrofit.Builder().baseUrl(ServiceApi.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
    ServiceApi serviceApi = build.create(ServiceApi.class);

    serviceApi.getHistoryDate().enqueue(new Callback<String>() {
      @Override public void onResponse(Call<String> call, Response<String> response) {
        Log.d(TAG, "onResponse- " + response.body());
      }

      @Override public void onFailure(Call<String> call, Throwable t) {
        Log.d(TAG, "onFailure- " + t.getMessage());
      }
    });  
    
/**
 * API 来自 gank.io,感谢 @代码家
 */
interface ServiceApi {
  String BASE_URL = "http://gank.io/api/";
  /**
   * 获取某一天的数据
   */
  @GET("day/{year}/{month}/{day}") Call<String> getDataOnSomeday(
      @Path("year") String year, @Path("month") String month, @Path("day") String day);
}

我们通过调用 serviceApi.getHistoryDate().enqueue(callback) 请求数据并在回调中处理数据。 就从这行代码作为切入点,看看发出请求的时候到底内部发生了啥? debug serviceApi.getHistoryDate() 这行代码我发现,运行至此的时候调用了动态代理,代码走到了下面的 create(final Class<T> service) 中的 InvocationHandler() 中,回调了 invoke()

QQ20170103-1341382x.png

我们来仔细分析一下这个函数。

/**
 * 创建由 service 定义的 API 的实现。
 * return  类型还是为 T 的代理对象	
 */
public <T> T create(final Class<T> service) {
    // 检查 service 是否合法,必须是接口且只有 1 个接口,
    Utils.validateServiceInterface(service);
    // 创建的时候如果设置验证方法就先验证,否则只在动态代理里面验证。
    if (validateEagerly) {  
      eagerlyValidateMethods(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, 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);
          }
        });
  }

很明显这是一个 Java 动态代理。这个方法在 ServiceApi serviceApi = build.create(ServiceApi.class); 调用,返回的是一个代理对象。
invoke(Object proxy, Method method, Object... args)中,Retrofit 只关心 method 和 args 两个参数。 核心代码是这 3 句:

ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall); 

ServcieMethod

ServiceMethod就像是一个中央处理器,传入Retrofit对象和Method对象,调用各个接口和解析器,最终生成一个Request,包含api 的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个Call对象,Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client。

使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送。

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

build方法里面创建了CallAdapter和Converter。处理注解,将其转化成OkHttp Call。

/**
  * 创建 CallAdapter,创建 Converter,解析注释
  * ServiceMethod 的作用是将接口方法的调用适配为 HTTP Call
  * return ServiceMethod 对象
  */
public ServiceMethod build() {
       
       // 创建callAdapter
       callAdapter = createCallAdapter();
       responseType = callAdapter.responseType();
       // 创建Converter
       responseConverter = createResponseConverter();
       // 遍历,解析方法注释
       for (Annotation annotation : methodAnnotations) {
           parseMethodAnnotation(annotation);
       }
       
       // 参数里的注解的个数
       int parameterCount = parameterAnnotationsArray.length;
       // 创建对应的 parameterHandlers数组。将每个参数的注解解析成parameterHandler对象存入数组。
       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);
   }

这里的parameterHandlers 负责解析API定义时每个方法的参数,并在构造HTTP请求时设置参数。

解析方法注释

private void parseMethodAnnotation(Annotation annotation) {
    if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
    } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
    }
    
    // ...省略代码
}
    private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
     
      this.httpMethod = httpMethod;
      this.hasBody = hasBody;

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

      // Get the relative URL path and existing query string, if present.
      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);

      }

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

static Set<String> parsePathParameters(String path) {
    Matcher m = PARAM_URL_REGEX.matcher(path);
    Set<String> patterns = new LinkedHashSet<>();
    while (m.find()) {
      patterns.add(m.group(1));
    }
    return patterns;
}

parameterHandlers

每个参数都会有一个 ParameterHandler,由 ServiceMethod#parseParameter 方法负责创建,其主要内容就是解析每个参数使用的注解类型(诸如 Path,Query,Field 等),对每种类型进行单独的处理。构造 HTTP 请求时,我们传递的参数都是字符串,那 Retrofit 是如何把我们传递的各种参数都转化为 String 的呢?还是由 Retrofit 类提供 converter!
Converter.Factory 除了提供上一小节提到的 responseBodyConverter,还提供 requestBodyConverter 和 stringConverter,API 方法中除了 @Body 和 @Part 类型的参数,都利用 stringConverter 进行转换,而 @Body 和 @Part 类型的参数则利用 requestBodyConverter 进行转换。

CallAdapter

    // #ServiceMethod.java

this.callFactory = builder.retrofit.callFactory();

CallAdapter由retrofit提供,我们可以自己指定,默认是okhttp3.OkHttpClient。

创建callAdapter由retrofit完成。

private CallAdapter<T, R> createCallAdapter() {
    return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
}
    
 // # Retrofit.java
 public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
        return nextCallAdapter(null, returnType, annotations);
 }

最终走到nextCallAdapter(),通过CallAdapter.Factory来创造。

public CallAdapter<?, ?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
                                             Annotation[] annotations) {
    // ...省略多若干码
    
    // 去除skipPast这个过去的工厂,Retrofit默认传的是null。
    int start = adapterFactories.indexOf(skipPast) + 1;
    // 遍历取出第一个工厂,获得adapter。
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
        CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
        if (adapter != null) {
            return adapter;
        }
    }
}

ResponseConverter

和callAdapter一样,也是由Retrofit创建的。通过遍历 Converter.Factory 列表,看看有没有工厂能够提供需要的 responseBodyConverter。工厂列表同样可以在构造 Retrofit 对象时进行添加。

OkHttpCall

OkHttpCall实现了Call接口。

okHttpCall 关键的部分是:

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;
  }
    
    
  // ServiceMethod.java # toRequest() 
  /** Builds an HTTP request from method arguments. */
  Request toRequest(Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    
    // 省略部分检查代码
    
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.build();
  }

这里由serviceMethod创建request,之前的ParameterHandler这里就用到了,这里把参数传进去一起组成完整request。并且由serviceMethod里的callFactory创建一个Call,默认就是OkHttpCall。

调用execute()执行同步请求:

@Override public Response<T> execute() throws IOException {
    okhttp3.Call call;

    // 省略判断代码...
    
    call = rawCall = createRawCall();
    
    // 省略判断代码...

    // 最终调用 parseResponse 解析返回的结果
    return parseResponse(call.execute());
  }
  
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) {
      try {
        // Buffer the entire body to avoid future I/O.
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally {
        rawBody.close();
      }
    }

    if (code == 204 || code == 205) {
      rawBody.close();
      return Response.success(null, rawResponse);
    }

    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      
      // 这里调用serviceMethod中的方法用对应的ConvertFactory转换
      T body = serviceMethod.toResponse(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.
      catchingBody.throwIfCaught();
      throw e;
    }
  }


参考资料

Retrofit源码分析 - 有心课堂

Retrofit分析-漂亮的解耦套路

Retrofit 官方文档

「Android技术汇」Retrofit2 源码解析和案例说明

深入浅出 Retrofit,这么牛逼的框架你们还不来看看?

拆轮子系列:拆 Retrofit