Retrofit

Posted by pxfile Blog on June 12, 2018

Retrofit

Android:手把手带你深入剖析 Retrofit 2.0 源码

Retrofit 的具体使用

这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)

网络请求的处理流程

Retrofit的本质流程

一般从网络通信过程如下图:

网络请求的过程

  • 其实Retrofit的本质和上面是一样的套路
  • 只是Retrofit通过使用大量的设计模式进行功能模块的解耦,使得上面的过程进行得更加简单 & 流畅

如下图:

Retrofit的本质

具体过程解释如下:

  1. 通过解析 网络请求接口的注解 配置 网络请求参数
  2. 通过 动态代理 生成 网络请求对象
  3. 通过 网络请求适配器 将 网络请求对象 进行平台适配

平台包括:Android、Rxjava、Guava和java8

  1. 通过 网络请求执行器 发送网络请求

  2. 通过 数据转换器 解析服务器返回的数据
  3. 通过 回调执行器 切换线程(子线程 -»主线程)
  4. 用户在主线程处理返回结果

下面介绍上面提到的几个角色

角色说明

源码分析

先来回忆Retrofit的使用步骤: 1. 创建Retrofit实例 2. 创建 网络请求接口实例 并 配置网络请求参数 3. 发送网络请求 封装了 数据转换、线程切换的操作 4. 处理服务器返回的数据

创建Retrofit实例

分析步骤

Retrofit 使用建造者模式通过Builder类建立了一个Retrofit实例,具体创建细节是配置了:

  • 平台类型对象(Platform - Android)
  • 网络请求的url地址(baseUrl)
  • 网络请求工厂(callFactory) 默认使用OkHttpCall
  • 网络请求适配器工厂的集合(adapterFactories)

    本质是配置了网络请求适配器工厂- 默认是ExecutorCallAdapterFactory

  • 数据转换器工厂的集合(converterFactories)

    本质是配置了数据转换器工厂

  • 回调方法执行器(callbackExecutor) 默认回调方法执行器作用是:切换线程(子线程 - 主线程) 由于使用了建造者模式,所以开发者并不需要关心配置细节就可以创建好Retrofit实例,建造者模式get。

在创建Retrofit对象时,你可以通过更多更灵活的方式去处理你的需求,如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能

创建 网络请求接口实例 并 配置网络请求参数

<-- 步骤2:定义网络请求的接口类 -->
<-- AccessApi.java -->
public interface AccessApi {
    // 注解GET:采用Get方法发送网络请求
    // Retrofit把网络请求的URL分成了2部分:1部分baseurl放在创建Retrofit对象时设置;另一部分在网络请求接口设置(即这里)
    // 如果接口里的URL是一个完整的网址,那么放在创建Retrofit对象时设置的部分可以不设置
    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")

    // 接受网络请求数据的方法
    Call<JavaBean> getCall();
    // 返回类型为Call<*>,*是解析得到的数据类型,即JavaBean
}

<-- 步骤3:在MainActivity创建接口类实例  -->
AccessApi NetService = retrofit.create(AccessApi.class);

<-- 步骤4:对发送请求的url进行封装,即生成最终的网络请求对象  --> 
        Call<JavaBean> call = NetService.getCall();
  • Retrofit是通过外观模式 & 代理模式 使用create()方法创建网络请求接口的实例(同时,通过网络请求接口里设置的注解进行了网络请求参数的配置)

    1. 外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。具体请看:外观模式(Facade Pattern) - 最易懂的设计模式解析

    2. 代理模式:通过访问代理对象的方式来间接访问目标对象。具体请看:代理模式(Proxy Pattern)- 最易懂的设计模式解析
    3. 下面主要分析步骤3和步骤4:

      1. 外观模式

  • 外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。具体请看:外观模式(Facade Pattern) - 最易懂的设计模式解析

  • Retrofit对象的外观(门店) = retrofit.create()

  • 通过这一外观方法就可以在内部调用各个方法创建网络请求接口的实例配置网络请求参数

    大大降低了系统的耦合度

3. 优缺点

在全面解析完后,我来分析下其优缺点:

3.1 优点

  • 降低了客户类与子系统类的耦合度,实现了子系统与客户之间的松耦合关系
  1. 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类
  2. 减少了与子系统的关联对象,实现了子系统与客户之间 的松耦合关系,松耦合使得子系统的组件变化不会影响到它的客户。
  • 外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。
  1. 引入外观角色之后,用户只需要与外观角色交互;
  2. 用户与子系统之间的复杂逻辑关系由外观角色来实现
  • 降低原有系统的复杂度和系统中的编译依赖性,并简化了系统在不同平台之间的移植过程

因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

3.2 缺点

  • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”
  • 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。

4. 应用场景
  • 要为一个复杂的子系统对外提供一个简单的接口
  • 提供子系统的独立性
  • 客户程序与多个子系统之间存在很大的依赖性

引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。

  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口

层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。


5. 与适配器模式的区别
  • 外观模式的实现核心主要是——由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。
  • 这样的实现方式非常类似适配器模式,然而外观模式与适配器模式不同的是:适配器模式是将一个对象包装起来以改变其接口,而外观是将一群对象 ”包装“起来以简化其接口。它们的意图是不一样的,适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口

2. 代理模式

  • 代理模式:通过访问代理对象的方式来间接访问目标对象

    分为静态代理 & 动态代理: 1. 静态代理:代理类在程序运行前已经存在的代理方式 2. 动态代理:代理类在程序运行前不存在、运行时由程序动态生成的代理方式 具体请看文章代理模式(Proxy Pattern)- 最易懂的设计模式解析

  • return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)通过代理模式中的动态代理模式,动态生成网络请求接口的代理类,并将代理类的实例创建交给InvocationHandler类 作为具体的实现,并最终返回一个动态代理对象。 生成实例过程中含有生成实现类的缓存机制(单例模式),下面会详细分析 使用动态代理的好处:

  • NetService对象调用getCall()接口中方法时会进行拦截,调用都会集中转发到 InvocationHandler#invoke (),可集中进行处理
  • 获得网络请求接口实例上的所有注解
  • 更方便封装ServiceMethod

创建ServiceMethod

上面AccessApi对象其实是一个动态代理对象,并不是一个真正的AccessApi接口的implements产生的对象,当api对象调用getCall方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象, 创建一个ServiceMethod对象

  • NetService对象实际上是动态代理对象Proxy.newProxyInstance()(步骤3中已说明),并不是真正的网络请求接口创建的对象
  • NetService对象调用getCall()时会被动态代理对象Proxy.newProxyInstance()拦截,然后调用自身的InvocationHandler # invoke()
  • invoke(Object proxy, Method method, Object... args)会传入3个参数:Object proxy:(代理对象)、 Method method(调用的getCall()Object... args(方法的参数,即getCall(*)中的*)
  • 接下来利用Java反射获取到getCall()的注解信息,配合args参数创建ServiceMethod对象

最终创建并返回一个OkHttpCall类型的Call对象

  1. OkHttpCall类是OkHttp的包装类 2. 创建了OkHttpCall类型的Call对象还不能发送网络请求,需要创建Request对象才能发送网络请求

总结

Retrofit采用了 外观模式 统一调用创建网络请求接口实例和网络请求参数配置的方法,具体细节是:

  • 动态创建网络请求接口的实例(代理模式 - 动态代理)
  • 创建 serviceMethod 对象(建造者模式 & 单例模式(缓存机制))
  • serviceMethod 对象进行网络请求参数配置:通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络请求的url地址、网络请求执行器、网络请求适配器 & 数据转换器。(策略模式)
  • serviceMethod 对象加入线程切换的操作,便于接收数据后通过Handler从子线程切换到主线程从而对返回数据结果进行处理(装饰模式)
  • 最终创建并返回一个OkHttpCall类型的网络请求对象

执行网络请求

  • Retrofit默认使用OkHttp,即OkHttpCall类(实现了 retrofit2.Call<T>接口)

    但可以自定义选择自己需要的Call类

  • OkHttpCall提供了两种网络请求方式:

    1. 异步请求:OkHttpCall.execute()
    2. 同步请求:OkHttpCall.enqueue()

下面将详细介绍这两种网络请求方式。 对于OkHttpCall的enqueue()、execute()此处不往下分析,有兴趣的读者可以看OkHttp的源码

3.1 同步请求OkHttpCall.enqueue()

3.1.1 发送请求过程

  • 步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler进行解析,再根据ServiceMethod对象创建一个OkHttpRequest对象
  • 步骤2:使用OkHttpRequest发送网络请求;
  • 步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,最终得到一个Response<T>对象

特别注意

  • ServiceMethod几乎保存了一个网络请求所需要的数据
  • 发送网络请求时,OkHttpCall需要从ServiceMethod中获得一个Request对象
  • 解析数据时,还需要通过ServiceMethod使用Converter(数据转换器)转换成Java对象进行数据解析

    为了提高效率,Retrofit还会对解析过的请求ServiceMethod进行缓存,存放在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();对象中,即第二步提到的单例模式

    异步请求OkHttpCall.execute()

3.2.1 发送请求过程
  • 步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler进行解析,再根据ServiceMethod对象创建一个OkHttpRequest对象
  • 步骤2:使用OkHttpRequest发送网络请求;
  • 步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,最终得到一个Response<T>对象
  • 步骤4:进行线程切换从而在主线程处理返回的数据结果

    若使用了RxJava,则直接回调到主线程

异步请求的过程跟同步请求类似,唯一不同之处在于:异步请求会将回调方法交给回调执行器在指定的线程中执行。 指定的线程此处是指主线程(UI线程)

总结

Retrofit 本质上是一个 RESTfulHTTP 网络请求框架的封装,即通过 大量的设计模式 封装了 OkHttp ,使得简洁易用。具体过程如下:

  1. RetrofitHttp请求 抽象 成 Java接口
  2. 在接口里用 注解 描述和配置 网络请求参数
  3. 用动态代理 的方式,动态将网络请求接口的注解 解析 成HTTP请求
  4. 最后执行HTTP请求

最后贴一张非常详细的Retrofit源码分析图:

Retrofit源码分析图

OKhttp 拦截器

private void initHttpClient(Context context) {
   NetworkingConfig config = new NetworkingConfig()
         .appContext(context.getApplicationContext())
         .baseUrl(ApiService.getBaseUrl() + "/")
         .requestInterceptor(new RequestInterceptorImpl())
         .responseInterceptor(new HttpResponseInterceptorImpl())
         .cookieInterceptor(new CookieInterceptorImpl());
  RestClient.getInstance().init(config);
}

http通用参数拦截器

Interceptor requestInterceptor

  • 统计信息:当前App包名,当前APP版本,平台标示(android/ios),设备IMEI(device_id),设备系统版本,屏幕分辨率,经纬度,渠道,城市信息,UUID等。

  • addHeader .addHeader("User-Agent", "com.wanmeizhensuo.zhensuo/" + BuildConfig.VERSION_NAME + " AsyncHttpClient/1.4.5 Android/" + DeviceUtils.DEVICE_RELEASE_VERSION)

数据结果拦截器

HttpResponseInterceptor responseInterceptor

同步网络库Cookie

CookieInterceptor cookieInterceptor

用来将本地的cookie追加到http请求头中,将Http返回的cookie存储到本地