Retrofit 源码分析流程

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/u011692041/article/details/76339018

前言

Retrofit 框架使用了有一年多了,但是说来惭愧,一直都是处于使用的状态,不会用的地方利用百度或者谷歌搜索一下.一直没有去摸索里面的源代码.这几天我对源代码进行阅读之后,不仅理顺了Retrofit 框架的实现,而且对泛型的认识提升到了一个新的高度.因为里面很多代码是对泛型进行处理的.下面就容许我给大家梳理一下Retrofit 的工作原理

源码分析

首先我定义一个接口文件

public interface Api {
    @GET()
    Call<ResponseBody> get(@Url String uri);
}

我为了演示方便我是在Java环境下运行Retrofit的,并不是Android环境哦

public class Main {
    public static void main(String[] args) 
    throws IOException, NoSuchMethodException {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://publicobject.com")
                .build();
        Api api = retrofit.create(Api.class);
        retrofit2.Call<ResponseBody> call = api.get("helloworld.txt");
        Response<ResponseBody> response = call.execute();
        System.out.println("result = " + response.body().string());
    }

}

首先是当你调用Api接口中的方法拿到Call对象的时候(之后可能是其他对象)

// 这是默认的没有加上任何CallFactory转化器和ConverterFactory和转化器的,ResponseBody对象是OkHttp提供的
retrofit2.Call<ResponseBody> call = api.get("helloworld.txt");

当你调用接口文件中的方法返回一个Call对象或者其他对象(Call对象被转化过去的)的时候执行的流程

Created with Raphaël 2.1.0 Api接口文件 通过动态代理ApiProxy对象 InvocationHandler实现类 拦截到方法method1,method2,method3 通过Method对象生成ServiceMethod对象 通过ServiceMethod和方法参数args对象生成OkHttpCall对象 (6)获取到方法返回值(可能是OkHttpCall对象也可能是转化之后的其他对象比如Observable) 结束

上面流程图的第(6)步就是Retrofit 内置的CallAdapter 转化器的作用啦.
原本默认返回的是一个Call对象,而我们可以定义一个CallAdapter转换器来将Call对象转化为其他对象,比如我们的RxJava中的ObServable

上述就是当你调用上面那句话的时候,会执行的流程.具体源码后面分析

执行请求

Response<ResponseBody> response = call.execute();
System.out.println("result = " + response.body().string());
result = 
                         \\           //
                          \\  .ooo.  //
                           .@@@@@@@@@.
                         :@@@@@@@@@@@@@:
                        :@@. '@@@@@' .@@:
                        @@@@@@@@@@@@@@@@@
                        @@@@@@@@@@@@@@@@@

                   :@@ :@@@@@@@@@@@@@@@@@. @@:
                   @@@ '@@@@@@@@@@@@@@@@@, @@@
                   @@@ '@@@@@@@@@@@@@@@@@, @@@
                   @@@ '@@@@@@@@@@@@@@@@@, @@@
                   @@@ '@@@@@@@@@@@@@@@@@, @@@
                   @@@ '@@@@@@@@@@@@@@@@@, @@@
                   @@@ '@@@@@@@@@@@@@@@@@, @@@
                        @@@@@@@@@@@@@@@@@
                        '@@@@@@@@@@@@@@@'
                           @@@@   @@@@
                           @@@@   @@@@
                           @@@@   @@@@
                           '@@'   '@@'

     :@@@.
   .@@@@@@@:   +@@       `@@      @@`   @@     @@
  .@@@@'@@@@:  +@@       `@@      @@`   @@     @@
  @@@     @@@  +@@       `@@      @@`   @@     @@
 .@@       @@: +@@   @@@ `@@      @@` @@@@@@ @@@@@@  @@;@@@@@
 @@@       @@@ +@@  @@@  `@@      @@` @@@@@@ @@@@@@  @@@@@@@@@
 @@@       @@@ +@@ @@@   `@@@@@@@@@@`   @@     @@    @@@   :@@
 @@@       @@@ +@@@@@    `@@@@@@@@@@`   @@     @@    @@#    @@+
 @@@       @@@ +@@@@@+   `@@      @@`   @@     @@    @@:    @@#
  @@:     .@@` +@@@+@@   `@@      @@`   @@     @@    @@#    @@+
  @@@.   .@@@  +@@  @@@  `@@      @@`   @@     @@    @@@   ,@@
   @@@@@@@@@   +@@   @@@ `@@      @@`   @@@@   @@@@  @@@@#@@@@
    @@@@@@@    +@@   #@@ `@@      @@`   @@@@:  @@@@: @@'@@@@@
                                                     @@:
                                                     @@:
                                                     @@:

那么这时候我们我们调用的代码执行的流程是怎么样的呢?

我们上面说过调用接口的方法返回的是一个Call对象或者是其他一个转化过后的对象,我们由于还没有添加转化器,所以默认就只能返回Call对象。
同时我们上面的流程图也介绍了默认返回的Call(其实就是OkHttpCall,实现了Retrofit中的Call接口)其实就是对OkHttp中的Call对象中的一个包裹(也可以说是一个代理)
所以当你执行下面的代码

Response<ResponseBody> response = call.execute();

其实就会调用到OkHttp中的Call对象的 execute() 方法,下面我画一下流程图

Created with Raphaël 2.1.0 调用call.execute()或者enqueue(Callback<T> callback)方法 实际上调用OkHttp中的Call对象的execute() 或者 enqueue(Callback<T> callback)方法 请求服务器返回数据,对数据进行包装成一个ResponseBody 对象 (4)利用Retrofit中的Converter转化器将ResponseBody对象转化为其他对象 结束

上面的(4)至关重要,这里是对数据就行转换的关键.我们使用过Retrofit的同学都知道,我们的Retrofit可以通过添加转化器对返回的json、xml等数据都可以转化成想要的数据.比如常见的json转化成为实体对象供我们使用

流程总结

1.对于Retrofit 有两个流程,就是上述的两个.第一个流程是获取一个可请求的对象.这时候请求并没有发送,你获取到的只是一个OkHttp中的Call对象的一个代理对象.这个对象很可能是一个多次代理的对象,其中内部的OkHttpCall就是对OkHttp中的Call对象的第一层代理.如果你使用了RxJava,你返回的ObServable 对象就是对OkHttpCall的二次代理.
而 OkHttp中的Call 是一个可请求的对象,内部封装并且持有了一个Request

2.第二个流程就是你拿到Retrofit的默认对OkHttpCall代理的Call对象(和OkHttp的Call是两个类,不要混淆) 或者经过转化的其他对象,比如ObServable
如果是默认的Call的话,调用enqueue方法或者execute方法即可请求网络拿到数据.
如果是ObServable 只需要订阅即可,就可以拿到数据

阅读源码证明上述流程

public static void main(String[] args) throws IOException, NoSuchMethodException {

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

        Api api = retrofit.create(Api.class);
        Call<ResponseBody> call = api.getCall("helloworld.txt");
        Response<ResponseBody> response = call.execute();
        System.out.println("result = " + response.body().string());

    }

首先上面的代码是成功请求的代码

流程1

我们可以看到首先构建了一个Retrofit,然后

Api api = retrofit.create(Api.class);

这句代码发生了什么呢?

代理接口

这里写图片描述

有Java动态代理知识的同学可以很明显发现这里使用了动态代理.对接口Api进行了一个代理.
而匿名内部类实现了InvocationHandler 接口就是拦截到所有的方法

ServiceMethod对象的创建

然后我们往下看,我们发现通过方法loadServiceMethod(method)创建了ServiceMethod对象,ServiceMethod对象内部根据method中解析了注解信息和返回值类型,并且根据参数中的注解为每一个参数生成一个处理对象,构建ServiceMethod是采用了建造者模式

  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;
  }
public ServiceMethod build() {
      callAdapter = createCallAdapter();
      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();

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

      if (httpMethod == null) {
        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }

      if (!hasBody) {
        if (isMultipart) {
          throw methodError(
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
        if (isFormEncoded) {
          throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
              + "request body (e.g., @POST).");
        }
      }

      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.");
        }

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

      if (relativeUrl == null && !gotUrl) {
        throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError("Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError("Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError("Multipart method must contain at least one @Part.");
      }

      return new ServiceMethod<>(this);
    }

loadServiceMethod(method) 这个方法内部对获取ServiceMethod进行了缓存,这样子我们调用同一个方法的时候就不会多次解析method中的注解信息,浪费时间

后面的build方法是构建出ServiceMethod对象的Builder对象的build方法。可以看到里面对方法上的注解信息的解析和各种健壮性的判断

OkHttpCall(其实就是Retrofit中的Call接口的实现类) 对象的创建

这里写图片描述

创建OkHttpCall对象我们传入了ServiceMethod对象和实际调用方法的时候的参数数组.
上面说过ServiceMethod解析了方法上的注解信息和生成了各个参数的处理类
(每一个处理类还会遍历所有的转化器,找到第一个支持解析接口中方法参数类型的Converter)
还包括找出了可以转化Api接口中写的泛型中的对象的转化器Converter
现在有ServiceMethod 和 调用方法的参数args 那么就可以生成一个真正的请求对象了.
ServiceMethod 中的信息 + ServiceMethod中的参数处理类处理args得到的信息 = Request对象

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 对象的toRequest方法生成了一个Request对象,用过OkHttp的同学就很清楚了,这里的Request对象就是OkHttp的,然后接下来对这个Request包裹了一下,变成了 okhttp中的Call对象.而 这个call是OkHttpCall对象持有的,所以之前说OkHttpCall是OkHttp中的Call对象的一个代理类,这里得到了验证.

然后下面是转化Call的代码

这里写图片描述

这里的代码看方法名字其实很清楚,就是对OkHttp做转换。里面会遍历所有的CallAdapter,如果某一个CallAdapter 可以支持生成你在接口文件中写的类型,那么就获取到这个CallAdapter 让他对OkHttpCall 对象做转化,这也就是下面代码能返回ObServable 对象的原因

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://publicobject.com")
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

Api api = retrofit.create(Api.class);
Observable<ResponseBody> observable = api.get("helloworld.txt");
public interface Api {

    @GET()
    Observable<ResponseBody> get(@Url String uri);

}

流程2

这时候参数也解析了.请求也有了,就差流程2调用啦,流程2的代码就不一一展示了,但是展示一个最关键的返回的数据类型的转化的部分

上面陈述了OkHttpCall 是对OkHttp中的Call对象的一个代理,所以OkHttpCall内部执行请求之后就是拿到返回值最近的地方,让我们看看,我们以同步的方法为例子:

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

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

      if (creationFailure != null) {
        if (creationFailure instanceof IOException) {
          throw (IOException) creationFailure;
        } else {
          throw (RuntimeException) creationFailure;
        }
      }

      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException e) {
          creationFailure = e;
          throw e;
        }
      }
    }

    if (canceled) {
      call.cancel();
    }

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

ServiceMethod 对象中的方法

/** Builds a method return value from an HTTP response body. */
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

可以看到最后的代码中,又通过 ServiceMethod 对象的toResponse 方法生成一个Response对象,内部利用 ServiceMethod 持有的类型转化器 Converter 对ResponseBody 进行了转化,比如你添加了Gson的转化器,就会帮你转化成对象.不添加任何一个转化器的时候,默认返回的是一个ResponseBody 对象。这在最开始就展示了.

总结

好处:

Retrofit 提供了很高的扩展性,让我们可以定制返回的对象和返回的结果.这可以和轻易的和其他框架搭配使用,影响力比较大的就是和RxJava搭配使用.
同时你还可以自定义转化器 Converter,让你的接口方法中的参数支持更多的参数类型,比如File,Map,List等等,虽然一些集合内部已经支持了.但我就是举个例子.嘿嘿嘿

不足之处:
内部就是基于OkHttp 实现的,并不能让Retrofit 支持其他的网络框架,比如 HttpUrlConnection
如果非要使用Retrofit但是内部使用的是其他的网络框架的话,只能使用 内部的 OkHttp 的拦截器,让所有的方法都走另一个网络框架的请求,也是可以的。

猜你喜欢

转载自blog.csdn.net/u011692041/article/details/76339018