美文网首页漫漫Android路
Retrofit 2.3.0 源码解析

Retrofit 2.3.0 源码解析

作者: osan | 来源:发表于2017-07-03 20:41 被阅读125次

    前言

    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

    相关文章

      网友评论

        本文标题:Retrofit 2.3.0 源码解析

        本文链接:https://www.haomeiwen.com/subject/stvzcxtx.html