Retrofit 2.3.0 源码解析

前言

Retrofit A type-safe HTTP client for Android and Java

Retrofit,是一个基于http请求库二次封装的HTTP客户端,将 REST API 转换为 Java 接口。

基于注解,进一步解放了生产力,使得http请求就像调用方法一样简单,如丝般顺滑。

结构概览

architecture.png

项目结构整体分四个部分,Builder -> Proxy -> Invocation -> RawCall
这里我们把基于Retrofit的HTTP通信比做是邮递信件。

邮递信件

  • 信封:当我们准备好信件之后,要在信封上写邮寄地址,收件人,可能还要备注勿折(是的,我暴露了我的年龄,如今很多人可能都没有过写信寄信的体验)。
  • 邮递员:然后我们亲自去送信吗?No,我们把信投入邮箱,交给邮递员代为送信就行了。
  • 邮局:然后邮递员会根据信封上的信息对信件进行分拣,寄信或收信均经由邮局统一处理
  • 邮寄方式:最后就是交给运送单位送信了,空运或是陆运等。

基于Retrofit的HTTP通信

  • Builder:当我们准备好数据之后,要指定服务端的通信地址,处理接口地址,请求方法,可能还要备注是否有body、是否是multipart。
  • Proxy:然后通信的事交给代理去做,代理会帮你做好一系列的工作,比如注解解析,Call适配,以及请求调度等
  • Invocation:这里负责调度同步或异步请求,请求装配和响应解析
  • RawCall:这里就是具体的通信工具了,可选Okhttp等框架来做具体的Http通信。

来看看寄信和Retrofit之间的对比:

arch_flow.png

大概过程就是这样,邮递员会把信送出去,并在适合的时机把对方的回信取回来送给你,当然如果你的信件是表白情书,那也很可能会收不到回信的,毕竟表白成功的概率要看人品的。不要伤心,HTTP通信也会有时候收不到服务端的回信噢。

目录概览

官方 Javadoc

│  BuiltInConverters.java               # 内建Converter
│  Call.java                            # 发送请求接收响应的retrofit方法调用
│  CallAdapter.java                     # 适配Call的响应类型,将默认响应类型R转换为类型T
│  Callback.java                        # 返回服务端或离线请求的响应体
│  Converter.java                       # HTTP交互中,转换对象为数据 或 从数据转换为对象
│  DefaultCallAdapterFactory.java       # 默认CallAdapter工厂
│  ExecutorCallAdapterFactory.java      # http请求执行器工厂
│  HttpException.java                   # 非2xx HTTP响应的异常处理
│  OkHttpCall.java                      # 真正调用OkHttp3发送Http请求的类
│  package-info.java                    # 包描述
│  ParameterHandler.java                # 参数注解解析器
│  Platform.java                        # 平台适配(Java/Android)
│  RequestBuilder.java                  # 请求拼装
│  Response.java                        # 原汁原味的HTTP 响应体,所谓 T body
│  Retrofit.java                        # 组装工厂,基于建造者模式拼装自定义HTTP交互所需的组件,并作为总调度暴露接口
│  ServiceMethod.java                   # 框架核心处理类,注解解析器调度,生成请求(包含api url、path、http请求方法、请
                                        # 求头、是否是multipart等等),并返回用于发起http请求的Call对象
│  Utils.java                           # 工具类
│  
└─http                              # http注解定义 (直接引用了Javadoc中的描述,均为提高生产力的注解)

        Body.java                       # control the request body of a POST/PUT request
        DELETE.java                     # Make a DELETE request
        Field.java                      # Named pair for a form-encoded request
        FieldMap.java                       # Named key/value pairs for a form-encoded request
        FormUrlEncoded.java                 # Denotes that the request body will use form URL encoding
        GET.java                        # Make a GET request
        HEAD.java                       # Make a HEAD request
        Header.java                     # Replaces the header with the value of its target
        HeaderMap.java                      # Adds headers specified in the Map
        Headers.java                        # Adds headers literally supplied in the value
        HTTP.java                       # Use a custom HTTP verb for a request
        Multipart.java                      # Denotes that the request body is multi-part
        OPTIONS.java                        # Make an OPTIONS request
        package-info.java                   # Package description
        Part.java                       # Denotes a single part of a multi-part request
        PartMap.java                        # Denotes name and value parts of a multi-part request
        PATCH.java                      # Make a PATCH request
        Path.java                       # Named replacement in a URL path segment
        POST.java                       # Make a POST request
        PUT.java                        # Make a PUT request
        Query.java                      # Query parameter appended to the URL
        QueryMap.java                       # Query parameter keys and values appended to the URL
        QueryName.java                      # Query parameter appended to the URL that has no value
        Streaming.java                      # Treat the response body on methods returning Response as is, i.e. 
                                # without converting body() to byte[]
        Url.java                        # URL resolved against the base URL

Retrofit的基本用法

让我们从基本用法开始,先看如何使用,顺着这个藤,摸摸如何实现的瓜。

用 Java 接口的方式定义一个HTTP API.

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

Retrofit 类生成一个 GitHubService 接口的实现实例.

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

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

Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.
GitHubService实例的每一个方法调用都支持同步或异步HTTP请求.

Call<List<Repo>> repos = service.listRepos("octocat");

执行同步或异步HTTP请求,得到HTTP响应数据.

Response<List<Repo>> response = repos.execute();

Retrofit的源码解析

首先我们心里要有个概念,Retrofit的核心关键词:注解、动态代理、转换器、适配器

Retrofit就是基于这四个关键词搭建起来的充分解耦,灵活,可插拔的优秀框架。

下面我们结合Retrofit设计图流程来解读代码。 还记得流程吗? Builder -> Proxy -> Invocation -> RawCall.

Flow - Builder

Retrofit.Builder() .baseUrl("https://api.github.com/") ... .build();
Tips.设计模式之Builder模式

基于Builder模式,装配一系列零部件,比如base请求地址,gson转换器,Rxjava适配器,HTTP请求client(比如装配OKHTTP)等。

// Retrofit.java -> class Builder

public Retrofit build() {
      
      ...

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

返回一个装配了 callFactory,converterFactories,adapterFactories,callbackExecutor 和指定了 baseUrl 的 Retrofit 实例。
注:validateEagerly,用于指定是否预先解析注解,加速接口访问效率。

Flow - Proxy

GitHubService service = retrofit.create(GitHubService.class);
我们知道,Java 接口是不可以直接 new 实例的,那么这个 create 方法看起来又像是返回了一个 GitHubService 接口类型的实现实例,这是怎么回事呢?我们来看下 create 的实现。

// Retrofit.java

  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, @Nullable 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<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

create方法主要就一个return,返回了一个Proxy.newProxyInstance生成的动态代理对象。原来这里是通过动态代理的方式生成了 GitHubService 接口的代理实例,那么后续 GitHubService 接口的方法都可以通过代理去调用了。
为什么用动态代理?
这是Retrofit设计的核心思路,基于动态代理,可以为后续在调用 GitHubService 接口的相关方法时先拦截下来,做完一系列工作后(即注解解析,请求转换,适配等),再去完成方法本尊想要完成的工作,这就是动态代理的魅力。

Tips.动态代理

Call<List<Repo>> repos = service.listRepos("octocat");
通过代理对象 service 调用接口方法 listRepos ,会被动态代理拦截,调用Proxy.newProxyInstance方法中的InvocationHandler对象的 invoke 方法。

invoke中主要由ServiceMethod和CallAdapter完成了三件事:

  • 请求方法的注解解析
  • 创建OkHttpCall实例,为后续流程中的HTTP请求执行做准备,详见 Flow - Invocation.
  • 适配Call的响应类型,将默认响应类型R转换为类型T
ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);

ServiceMethod.java

// ServiceMethod.java

public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      
      ...

      responseConverter = createResponseConverter();

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

      ...

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

获取callAdapter、responseType、responseConverter接口对象

解析Method的注解

解析Method的参数注解

解析Method的参数中使用了依赖请求API的动态参数的注解,交由ParameterHandler处理

CallAdapter.java

public interface CallAdapter<R, T> {
 
  Type responseType();

  ...

  T adapt(Call<R> call);

  ...
  
  }

适配Call的响应类型,将默认响应类型R转换为类型T.比如官方的RxJavaCallAdapter可以结合Rxjava特性对Call的响应做RxJava观察者模式转换,进一步解放生产力。

注:未在Builder阶段指定CallAdapter(如 RxJavaCallAdapterFactory )的情况下,默认的 CallAdapter 不对Call做任何处理。
见 DefaultCallAdapterFactory:

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

  ...
      ...

      @Override public Call<Object> adapt(Call<Object> call) {
        return call;
      }
  }
}

Flow - Invocation

Response<List<Repo>> response = repos.execute();

这一步开始基于同步的方式执行HTTP请求,并得到返回的HTTP响应数据.

本质上是执行了 OkHttpCall 的 execute方法.

// OkHttpCall.java

@Override public Response<T> execute() throws IOException {

    synchronized (this) {
     
     ...
        ...
          call = rawCall = createRawCall();

    }

    ...

    return parseResponse(call.execute());
  }

如你所见,这里创建了RawCall,即真正的去执行HTTP请求任务的对象。
这里还负责HTTP请求的响应数据解析。
我们看下createRawCall()干了什么。

// OkHttpCall.java

private okhttp3.Call createRawCall() throws IOException {
    Request request = serviceMethod.toRequest(args);
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    ...
    return call;
  }

serviceMethod.toRequest()的功能:

// ServiceMethod.java

/** Builds an HTTP request from method arguments. */
  Request toRequest(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    ...

    return requestBuilder.build();
  }

toRequest 方法通过 RequestBuilder 创建了 okhttp3 做 HTTP 请求时需要的 Request 对象。

serviceMethod.callFactory.newCall(request)的功能:
建立一个请求通道,为执行HTTP请求做准备。
这里callFactory可以由使用者指定,默认为 OkHttpClient,见:

// Retrofit.java

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

回头看下 OkHttpCall 中 execute 方法最后一句: return parseResponse(call.execute());
这里调用真正的HTTP请求客户端的请求执行方法。也就是来到了接下来的一个流程。

Flow - RawCall

上个 Flow 中最后一步, call.execute(),开启了真正的HTTP请求,即通过 okhttp3 完成HTTP请求。
这个部分没什么代码可讲,属于面向接口开发的典范,要讲就该去讲 Okhttp 框架的源码了。

这个部分引出了 Retrofit 的开源拥有者-Square 公司的另一个优秀的开源项目 Okhttp,是不是也很想一探究竟?

End

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容