Retrofit2源码分析(一)

本文将顺着构建请求对象→构建请求接口→发起同步/异步请求的流程,分析retrofit2是如何实现的。

组成部分

Retrofit2源码主要分为以下几个部分:

  • retrofit
  • retrofit-adapters
  • retrofit-converters

本篇先分析retrofit部分,也就是retrofit源码的主干部分。

下面顺着retrofit2的使用流程进行分析。

Retrofit对象的构建

首先我们看看构建一个Retrofit对象,都需要或者可选哪些配置:

1234567891011121314151617181920212223242526272829303132333435
/**   * Build a new {@link Retrofit}.   * <p>   * Calling {@link #baseUrl} is required before calling {@link #build()}. All other methods   * are optional.   */  public static final class Builder {  	//支持的平台    private Platform platform;    //发起请求的okhttp3的client工厂    private okhttp3.Call.Factory callFactory;    //okhttp3里的HttpUrl对象,用来解析和包装url    private HttpUrl baseUrl;    //转换器的工厂集合,retrofit构建可以插入多个转换器,比如gson转换器,Jackson转换器等    private List<Converter.Factory> converterFactories = new ArrayList<>();    //用来发起request和接收response的Call适配器,retrofit支持rxjava就是通过引入retrofit-adapters支持的,就是这个CallAdapter,这里先不作过多解释    private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();    //Executor并发框架,包括线程池等    private Executor callbackExecutor;    //是否需要立即生效    private boolean validateEagerly;

    Builder(Platform platform) {      this.platform = platform;      // 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());    }

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

    ... }

Retrofit对象的构建使用了建造者模式,这里有一系列参数可以供我们选择,我给这些参数加了注释。这里构造方法中Platform.get()就是获取当前使用retrofit的平台信息,之前我用retrofit的时候,以为只支持Android平台,没想到还支持java8和ios,只不过这里的ios是指通过robovm平台构建的ios程序,目前robovm主要的成功案例还是游戏,跨android & ios双平台。在Builder中,有一个比较重要的配置项,就是baseURL。我们设置baseUrl就是一个string字符串,retrofit是这么处理的:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
/**     * Set the API base URL.     * <p>     * The specified endpoint values (such as with {@link GET @GET}) are resolved against this     * value using {@link HttpUrl#resolve(String)}. The behavior of this matches that of an     * {@code <a href="">} link on a website resolving on the current URL.     * <p>     * <b>Base URLs should always end in {@code /}.</b>     * <p>     * A trailing {@code /} ensures that endpoints values which are relative paths will correctly     * append themselves to a base which has path components.     * <p>     * <b>Correct:</b><br>     * Base URL: http://example.com/api/<br>     * Endpoint: foo/bar/<br>     * Result: http://example.com/api/foo/bar/     * <p>     * <b>Incorrect:</b><br>     * Base URL: http://example.com/api<br>     * Endpoint: foo/bar/<br>     * Result: http://example.com/foo/bar/     * <p>     * This method enforces that {@code baseUrl} has a trailing {@code /}.     * <p>     * <b>Endpoint values which contain a leading {@code /} are absolute.</b>     * <p>     * Absolute values retain only the host from {@code baseUrl} and ignore any specified path     * components.     * <p>     * Base URL: http://example.com/api/<br>     * Endpoint: /foo/bar/<br>     * Result: http://example.com/foo/bar/     * <p>     * Base URL: http://example.com/<br>     * Endpoint: /foo/bar/<br>     * Result: http://example.com/foo/bar/     * <p>     * <b>Endpoint values may be a full URL.</b>     * <p>     * Values which have a host replace the host of {@code baseUrl} and values also with a scheme     * replace the scheme of {@code baseUrl}.     * <p>     * Base URL: http://example.com/<br>     * Endpoint: https://github.com/square/retrofit/<br>     * Result: https://github.com/square/retrofit/     * <p>     * Base URL: http://example.com<br>     * Endpoint: //github.com/square/retrofit/<br>     * Result: http://github.com/square/retrofit/ (note the scheme stays ‘http‘)     */    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到底是什么,应该遵循什么样的格式,然后经过HttpUrl的解析,组合成okhttp3用来请求的url链接。这里规定了baseUrl末尾应该以/符号结尾,在后续API的接口类中后半部分定义应该不以/开头,这是和retrofit 1.x版本不同的地方。最后就是build方法了:

12345678910111213141516171819202122232425262728293031
/**     * Create the {@link Retrofit} instance using the configured values.     * <p>     * Note: If neither {@link #client} nor {@link #callFactory} is called a default {@link     * OkHttpClient} will be created and used.     */    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);    }

从这里可以看出如果我们不设置callFactory,retrofit会默认帮我们new一个OkHttpClient,如果我们不设置callbackExecutor,也会帮我们默认获取到当前平台默认的callbackExecutor,最后new一个Retrofit对象,到这里,Retrofit对象的构建就讲完了。这里有个值得注意的地方:CallAdapter和Converter的工厂集合都使用了保护性拷贝。那么保护性拷贝是什么呢?这是用来保证代码健壮性的。为什么要在这里用?因为Builder的这些配置的方法都是public的,虽然看起来这些是不可变的,但是可以通过传入构造参数来修改引用的值,这就会造成约束条件被破坏,所以使用了保护性拷贝来防止这种情况。

API的编写

我们已经new好了一个我们需要的Retrofit对象,那么下一步就是编写API了。如何编写API呢?Retrofit的方式是用过java interface和注解的方式进行定义。例如:

1234
public interface GitHubService {  @GET("users/{user}/repos")  Call<List<Repo>> listRepos(@Path("user") String user);}

这是官方文档上的一个例子。这里简单的定义了一个API,针对这小部分代码,我们来分析分析。
首先是GET注解,我们来看看这个注解是什么:

1234567891011121314
/** Make a GET request. */@Documented@Target(METHOD)@Retention(RUNTIME)public @interface GET {  /**   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first   * parameter of the method is annotated with {@link Url @Url}.   * <p>   * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how   * this is resolved against a base URL to create the full endpoint URL.   */  String value() default "";}

这是一个运行时的方法注解,用来构造get请求,唯一的参数是一个string值,默认是空字符串。那么我们可以理解了,@GET(xxxxx)就是构造了一个用于get请求的url。下一个注解是Path注解,是一个运行时的参数注解,它是为了方便我们构建动态的url,参数是一个string值,还可以设置参数是否已经是URL encode编码,默认是false。最后我们看到,通过Call<T>构建成一个interface。Call<T>这个接口分别在OkHttpCallExecutorCallbackCall中做了具体的实现。

创建retrofit service

最最关键的一步来了。我们new好了retrofit对象,也写好了API interface,那怎么请求这个API呢,我们需要通过retrofit的create函数,创建一个service,然后调用API的接口方法进行请求并获得回传。使用当然很简单:

1
GitHubService service = retrofit.create(GitHubService.class);

是的你没有看错,就这么一句话就搞定了。具体是怎么实现的呢?我们先来看看create的代码:

12345678910111213141516171819202122232425
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.  public <T> T create(final Class<T> service) {    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的动态代理。什么是动态代理?就是代理类、委托类都要实现同一个接口,然后代理通过接口代理委托类。由于java的单继承特性,所以动态代理是基于接口的,只能针对接口创建代理类。所以这里create的时候,先判断了一下传进来的service是不是interface,如果不是接口类型或者包含多个接口,就会直接抛异常。然后就是重头戏:动态代理的invoke方法。这个方法是InvocationHandler接口中唯一的方法,这个接口是代理实例实现的接口。代理实例调用方法时,InvocationHandler接口会将对方法的调用指派到代理的invoke方法中,进行处理。这里当method是一个对象的method时,直接调用。如果是平台的默认方法(根据Platform代码中这种情况是java8)就直接执行调用默认方法。正常在android平台下,会把method加载到ServiceMethod对象中,这里用了缓存,如果缓存中有SeriveMethod就直接取出,如果没有接new一个,然后用过ServiceMethod初始化了一个OkHttpCall对象,最后通过callAdapteradapt方法返回一个代理了Call的实例。这个方法在这两个类中有具体的实现:
DefaultCallAdapterFactoryExecutorCallAdapterFactory。这两个工厂类又在哪里会用到?就在Retrofit对象的build方法中:

123
// 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));

那我们再看看platform.defaultCallAdapterFactory(callbackExecutor)里是什么:

123456
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {    if (callbackExecutor != null) {      return new ExecutorCallAdapterFactory(callbackExecutor);    }    return DefaultCallAdapterFactory.INSTANCE;  }

可以看到如果在创建Retrofit对象时callbackExecutor为空的话,就new一个ExecutorCallAdapterFactory对象作为CallAdapter,如果不为空就返回DefaultCallAdapterFactory的实例。

发起请求

这里我们按照retrofit官方文档中的例子构建Retrofit对象。
retrofit2中,同步和异步不再用interface的定义区分,统一都为Call<T>,但是在Call接口的方法中有区分,我们来看看:

1234567891011121314
/**   * Synchronously send the request and return its response.   *   * @throws IOException if a problem occurred talking to the server.   * @throws RuntimeException (and subclasses) if an unexpected error occurs creating the request   * or decoding the response.   */  Response<T> execute() throws IOException;

  /**   * Asynchronously send the request and notify {@code callback} of its response or if an error   * occurred talking to the server, creating the request, or processing the response.   */  void enqueue(Callback<T> callback);

execute()是同步请求,enqueue()是异步请求。我们先看异步请求是如何实现的。enqueue这个方法在以下两个地方有实现:
OkHttpCallExecutorCallbackCall。那么当发起了异步请求之后,就会调用ExecutorCallbackCall中的enqueue方法,ExecutorCallbackCall中一个对象叫

1
final Call<T> delegate;

这就是代理的Call对象,在这里就是我们在动态代理create方法中用过ServiceMethod对象构造的OkHttpCall对象,那么这里调用的enqueue方法,就是调用了代理的OkHttpCall对象的enqueue方法:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
@Override public void enqueue(final Callback<T> callback) {    ... //各种判空等代码,这里先省略了

    okhttp3.Call call;    Throwable failure;

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

    call.enqueue(new okhttp3.Callback() {      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)          throws IOException {        Response<T> response;        try {          response = parseResponse(rawResponse);        } catch (Throwable e) {          callFailure(e);          return;        }        callSuccess(response);      }

      @Override public void onFailure(okhttp3.Call call, IOException e) {        try {          callback.onFailure(OkHttpCall.this, e);        } catch (Throwable t) {          t.printStackTrace();        }      }

      private void callFailure(Throwable e) {        try {          callback.onFailure(OkHttpCall.this, e);        } catch (Throwable t) {          t.printStackTrace();        }      }

      private void callSuccess(Response<T> response) {        try {          callback.onResponse(OkHttpCall.this, response);        } catch (Throwable t) {          t.printStackTrace();        }      }    });  }

可以看到,这里其实是用了okhttp3的请求回调,做了一层封装,变成了retrofit2的Callback,也可以看出retrofit2和okhttp3是深度集成的。到这里,异步请求就一目了然了。我们还可以发现,retrofit2支持的可以取消请求,其实用的就是okhttp3的cancel方法。

同理,同步请求也是一样的,也是调用了代理的OkHttpCall的方法,只不过是execute方法,这个方法里面没有回调,和异步不同,是直接返回解析好的Response对象的,这就是同步请求啦。

至此,顺着构建请求对象→构建请求接口→发起同步/异步请求这个流程,我们分析了一遍retrofit2到底是如何实现的。

最后再说几句

  • retrofit2主模块源码其实并不是很多,我感觉用的最巧妙的就是create方法的动态代理,然后加上运行时注解来构建API,深度结合okhttp3,使得网络请求的构建变得非常简洁,并且功能强大,而且安全。
  • retrofit2同时支持与rxjava配合使用,是通过设置adapter来实现的,retrofit2把adapters和converters从主代码里拆分出来了,相当于组件化的意思,如果需要使用,就由开发者自己引用并定义,这种组件化的思想我觉得也非常棒。
  • retrofit2源码里是通过junit来写测试的,测试代码写的也非常好,更说明了优秀的代码离不开优秀的测试,这也是值得我们学习的地方。

这是我第一次自己去分析一个开源项目的源码,也许可能会有遗漏的地方,望指出,抛砖引玉,谢谢~

时间: 2024-10-31 04:41:32

Retrofit2源码分析(一)的相关文章

Retrofit2源码分析

例子 从简单的例子开始分析Retrofit2是怎么和其他的库一起合作的, 下边是一个很简单的例子,是rxjava2 + retrofit2 + okhttp3 + gson混合使用,是访问淘宝的ip地址查询服务,返回信息输出到EditText里. public static Retrofit getRetrofit() { if (retrofit == null) { synchronized (Retrofit.class) { if (retrofit == null) { retrofi

Retrofit2源码解读

综述 Retrofit2的用法在Retrofit2.0使用详解这篇文章中已经详细介绍过了.那么在这就来看一下Retrofit2它是如何实现的.Retrofit2中它的内部网络请求是依赖于OKHttp,所以Retrofit2可以看做是对OKHttp的一次封装,那么下面就开看下Retrofit2是如何对OKHttp进行封装的. 回顾Retrofit2的使用 在这里首先来回顾一下Retrofit2的使用.对于Retrofit2的使用可以分为三步. 首先,我们创建一个Java接口GitHubServic

Android Retrofit源码分析(一边用一边侃)

这几天空余时间总是想着写点什么,所以紧跟着之前android盒子模型FlexBoxLayout之后有写下了这篇Retrofit源码分析使用篇. 前言: Retrofit由于简单与出色的性能,使其安卓上最流行的HTTP Client库之一. Android Studio开发相关配置如下: compile "com.squareup.retrofit2:retrofit:2.0.2" compile "com.squareup.retrofit2:converter-gson:2

Retrofit2源码解析

最近项目将网络框架换成Retrofit2.0.2,文中说的Retrofit都是指的Retrofit2这里要说明一下,毕竟和Retrofit1差别还是蛮大的,结合Okhttp,RxJava还是比较好用的,网上有很多前辈介绍过使用方法,本文是想研究一下Retrofit的源码.关于Retrofit的介绍可以查阅Retrofit的官方网站 直接进入主题:(注本文是结合RxJava介绍的,最好可以了解一下RxJava不了解也没有关系,大部分的思想是一样的) Retrofit的基本使用 Retrofit使用

Retrofit源码分析以及MVP框架封装使用

阅读此文前请先阅读Retrofit+okhttp网络框架介绍 从上文中我们已经了解通过如下代码即可得到返回给我们call 以及 response对象,今天我们通过源码来分析这个过程是如何实现的. /** * 获取天气数据 * @param cityname * @param key * @return */ @GET("/weather/index") Call<WeatherData> getWeatherData(@Query("format") S

Retrofit2 源码解析

原文链接:http://bxbxbai.github.io/2015/12/13/retrofit2-analysis/ 公司里最近做的项目中网络框架用的就是Retrofit,用的多了以后觉得这个框架真的非常好用,然后抽了点时间debug了一下源码,觉得不光代码写的非常好,而且设计这个框架的思路都非常特别,收获很多,决定记录下来 本文的源码分析基于Retrofit 2.0,和Retrofit 1.0有较大的不同, 本文主要分为几部分:0.Retrofit 是什么,1.Retrofit 怎么用,2

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu