当前位置: 首页 > news >正文

Retrofit源码分析

文章目录

  • 一、简介
  • 二、源码分析
    • 2.1Retrofit的本质流程
    • 2.2源码分析
      • 2.2.1 创建Retrofit实例
        • 步骤1
        • 步骤2
        • 步骤3
        • 步骤4
        • 步骤5
        • 总结
      • 2.2.2创建网络请求接口的实例
        • 外观模式 & 代理模式
          • 1.外观模式
          • 2. 代理模式
        • 步骤3
        • 步骤4
        • 总结
      • 2.2.3执行网络请求
        • 同步请求OkHttpCall.execute()
          • 1.发送请求过程
          • 2.具体使用
          • 3.源码分析
        • 异步请求OkHttpCall.enqueue()
          • 1.发送请求过程
          • 2.具体使用
          • 3.源码分析
  • 三、总结

一、简介

在这里插入图片描述
特别注意:

  • 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
  • 原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装

在这里插入图片描述

  • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
  • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析

二、源码分析

2.1Retrofit的本质流程

在这里插入图片描述
具体过程解释如下:

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

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

  1. 通过 网络请求执行器 发送网络请求
  2. 通过 数据转换器 解析服务器返回的数据
  3. 通过 回调执行器 切换线程(子线程 ->>主线程)
  4. 用户在主线程处理返回结果
    在这里插入图片描述

2.2源码分析

先来回忆Retrofit的使用步骤:

  1. 创建Retrofit实例
  2. 创建 网络请求接口实例 并 配置网络请求参数
  3. 发送网络请求

封装了 数据转换、线程切换的操作

  1. 处理服务器返回的数据

2.2.1 创建Retrofit实例

使用步骤

 Retrofit retrofit = new Retrofit.Builder().baseUrl("http://fanyi.youdao.com/").addConverterFactory(GsonConverterFactory.create()).build();

源码分析
Retrofit实例是使用建造者模式通过Builder类进行创建的

建造者模式:将一个复杂对象的构建与表示分离,使得用户在不知道对象的创建细节情况下就可以直接创建复杂的对象

接下来,我将分五个步骤对创建Retrofit实例进行逐步分析
在这里插入图片描述

步骤1

在这里插入图片描述

<-- Retrofit类 -->public final class Retrofit {private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();// 网络请求配置对象(对网络请求接口中方法注解进行解析后得到的对象)// 作用:存储网络请求相关的配置,如网络请求的方法、数据转换器、网络请求适配器、网络请求工厂、基地址等private final HttpUrl baseUrl;// 网络请求的url地址private final okhttp3.Call.Factory callFactory;// 网络请求器的工厂// 作用:生产网络请求器(Call)// Retrofit是默认使用okhttpprivate final List<CallAdapter.Factory> adapterFactories;// 网络请求适配器工厂的集合// 作用:放置网络请求适配器工厂// 网络请求适配器工厂作用:生产网络请求适配器(CallAdapter)// 下面会详细说明private final List<Converter.Factory> converterFactories;// 数据转换器工厂的集合// 作用:放置数据转换器工厂// 数据转换器工厂作用:生产数据转换器(converter)private final Executor callbackExecutor;// 回调方法执行器private final boolean validateEagerly; 
// 标志位
// 作用:是否提前对业务接口中的注解进行验证转换的标志位<-- Retrofit类的构造函数 -->
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,  List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,  Executor callbackExecutor, boolean validateEagerly) {  this.callFactory = callFactory;  this.baseUrl = baseUrl;  this.converterFactories = unmodifiableList(converterFactories); this.adapterFactories = unmodifiableList(adapterFactories);   // unmodifiableList(list)近似于UnmodifiableList<E>(list)// 作用:创建的新对象能够对list数据进行访问,但不可通过该对象对list集合中的元素进行修改this.callbackExecutor = callbackExecutor;  this.validateEagerly = validateEagerly;  ...// 仅贴出关键代码
}

成功建立一个Retrofit对象的标准:配置好Retrofit类里的成员变量,即配置好:

  • serviceMethod:包含所有网络请求信息的对象
  • baseUrl:网络请求的url地址
  • callFactory:网络请求工厂
  • adapterFactories:网络请求适配器工厂的集合
  • converterFactories:数据转换器工厂的集合
  • callbackExecutor:回调方法执行器

所谓xxxFactory、“xxx工厂”其实是设计模式中工厂模式的体现:将“类实例化的操作”与“使用对象的操作”分开,使得使用者不用知道具体参数就可以实例化出所需要的“产品”类。

这里详细介绍一下:CallAdapterFactory:该Factory生产的是CallAdapter,那么CallAdapter又是什么呢?

CallAdapter详细介绍

  • 定义:网络请求执行器(Call)的适配器

Call在Retrofit里默认是OkHttpCall
在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory

  • 作用:将默认的网络请求执行器(OkHttpCall)转换成适合被不同平台来调用的网络请求执行器形式

如:一开始Retrofit只打算利用OkHttpCall通过ExecutorCallbackCall切换线程;但后来发现使用Rxjava更加方便(不需要Handler来切换线程)。想要实现Rxjava的情况,那就得使用RxJavaCallAdapterFactoryCallAdapter将OkHttpCall转换成Rxjava(Scheduler):

// 把response封装成rxjava的Observeble,然后进行流式操作
Retrofit.Builder.addCallAdapterFactory(newRxJavaCallAdapterFactory().create()); 
// 关于RxJava的使用这里不作更多的展开

Retrofit还支持java8、Guava平台。

  • 好处:用最小代价兼容更多平台,即能适配更多的使用场景

步骤2

在这里插入图片描述

public static final class Builder {private Platform platform;private okhttp3.Call.Factory callFactory;private HttpUrl baseUrl;private List<Converter.Factory> converterFactories = new ArrayList<>();private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();private Executor callbackExecutor;private boolean validateEagerly;// 从上面可以发现, Builder类的成员变量与Retrofit类的成员变量是对应的
// 所以Retrofit类的成员变量基本上是通过Builder类进行配置
// 开始看步骤1<-- 步骤1 -->
// Builder的构造方法(无参)public Builder() {this(Platform.get());
// 用this调用自己的有参构造方法public Builder(Platform platform) ->>步骤5(看完步骤2、3、4再看)
// 并通过调用Platform.get()传入了Platform对象
// 继续看Platform.get()方法 ->>步骤2
// 记得最后继续看步骤5的Builder有参构造方法}
...
}<-- 步骤2 -->
class Platform {private static final Platform PLATFORM = findPlatform();// 将findPlatform()赋给静态变量static Platform get() {return PLATFORM;	// 返回静态变量PLATFORM,即findPlatform() ->>步骤3}<-- 步骤3 -->
private static Platform findPlatform() {try {Class.forName("android.os.Build");// Class.forName(xxx.xx.xx)的作用:要求JVM查找并加载指定的类(即JVM会执行该类的静态代码段)if (Build.VERSION.SDK_INT != 0) {return new Android(); // 此处表示:如果是Android平台,就创建并返回一个Android对象 ->>步骤4}} catch (ClassNotFoundException ignored) {}try {// 支持Java平台Class.forName("java.util.Optional");return new Java8();} catch (ClassNotFoundException ignored) {}try {// 支持iOS平台Class.forName("org.robovm.apple.foundation.NSObject");return new IOS();} catch (ClassNotFoundException ignored) {}// 从上面看出:Retrofit2.0支持3个平台:Android平台、Java平台、IOS平台
// 最后返回一个Platform对象(指定了Android平台)给Builder的有参构造方法public Builder(Platform platform)  --> 步骤5
// 说明Builder指定了运行平台为Androidreturn new Platform();}
...
}<-- 步骤4 -->
// 用于接收服务器返回数据后进行线程切换在主线程显示结果static class Android extends Platform {@OverrideCallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {return new ExecutorCallAdapterFactory(callbackExecutor);// 创建默认的网络请求适配器工厂// 该默认工厂生产的 adapter 会使得Call在异步调用时在指定的 Executor 上执行回调// 在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory// 采用了策略模式}@Override public Executor defaultCallbackExecutor() {// 返回一个默认的回调方法执行器// 该执行器作用:切换线程(子->>主线程),并在主线程(UI线程)中执行回调方法return new MainThreadExecutor();}static class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());// 获取与Android 主线程绑定的Handler @Override public void execute(Runnable r) {handler.post(r);// 该Handler是上面获取的与Android 主线程绑定的Handler // 在UI线程进行对网络请求返回数据处理等操作。}}// 切换线程的流程:
// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象
//2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程}// 下面继续看步骤5的Builder有参构造方法
<-- 步骤5 -->
//  Builder类的构造函数2(有参)public  Builder(Platform platform) {// 接收Platform对象(Android平台)this.platform = platform;// 通过传入BuiltInConverters()对象配置数据转换器工厂(converterFactories)// converterFactories是一个存放数据转换器Converter.Factory的数组
// 配置converterFactories即配置里面的数据转换器converterFactories.add(new BuiltInConverters());// BuiltInConverters是一个内置的数据转换器工厂(继承Converter.Factory类)
// new BuiltInConverters()是为了初始化数据转换器}

总结:Builder设置了默认的

  • 平台类型对象:Android
  • 网络请求适配器工厂:CallAdapterFactory

CallAdapter用于对原始Call进行再次封装,如Call到Observable

  • 数据转换器工厂: converterFactory
  • 回调执行器:callbackExecutor

特别注意,这里只是设置了默认值,但未真正配置到具体的Retrofit类的成员变量当中

步骤3

在这里插入图片描述

public Builder baseUrl(String baseUrl) {// 把String类型的url参数转化为适合OKhttp的HttpUrl类型HttpUrl httpUrl = HttpUrl.parse(baseUrl);		// 最终返回带httpUrl类型参数的baseUrl()// 下面继续看baseUrl(httpUrl) ->> 步骤2return baseUrl(httpUrl);}<-- 步骤2 -->public Builder baseUrl(HttpUrl baseUrl) {//把URL参数分割成几个路径碎片List<String> pathSegments = baseUrl.pathSegments();	// 检测最后一个碎片来检查URL参数是不是以"/"结尾// 不是就抛出异常	if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);}		this.baseUrl = baseUrl;return this;}

总结:baseUrl()用于配置Retrofit类的网络请求url地址

将传入的String类型url转化为适合OKhttp的HttpUrl类型的url

步骤4

在这里插入图片描述
我们从里往外看,即先看GsonConverterFactory.creat()

public final class GsonConverterFactory extends Converter.Factory {<-- 步骤1 -->public static GsonConverterFactory create() {// 创建一个Gson对象return create(new Gson()); ->>步骤2}<-- 步骤2 -->public static GsonConverterFactory create(Gson gson) {// 创建了一个含有Gson对象实例的GsonConverterFactoryreturn new GsonConverterFactory(gson); ->>步骤3}private final Gson gson;<-- 步骤3 -->private GsonConverterFactory(Gson gson) {if (gson == null) throw new NullPointerException("gson == null");this.gson = gson;}
  • 所以,GsonConverterFactory.creat()是创建了一个含有Gson对象实例的GsonConverterFactory,并返回给addConverterFactory()
  • 接下来继续看:addConverterFactory()
// 将上面创建的GsonConverterFactory放入到 converterFactories数组
// 在第二步放入一个内置的数据转换器工厂BuiltInConverters()后又放入了一个GsonConverterFactorypublic Builder addConverterFactory(Converter.Factory factory) {converterFactories.add(checkNotNull(factory, "factory == null"));return this;}
  • 至此,分析完毕
  • 总结:步骤4用于创建一个含有Gson对象实例的GsonConverterFactory并放入到数据转换器工厂converterFactories里

步骤5

在这里插入图片描述

 <--  配置网络请求执行器(callFactory)-->okhttp3.Call.Factory callFactory = this.callFactory;// 如果没指定,则默认使用okhttp// 所以Retrofit默认使用okhttp进行网络请求if (callFactory == null) {callFactory = new OkHttpClient();}<--  配置回调方法执行器(callbackExecutor)-->Executor callbackExecutor = this.callbackExecutor;// 如果没指定,则默认使用Platform检测环境时的默认callbackExecutor// 即Android默认的callbackExecutorif (callbackExecutor == null) {callbackExecutor = platform.defaultCallbackExecutor();}<--  配置网络请求适配器工厂(CallAdapterFactory)-->List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);// 向该集合中添加了步骤2中创建的CallAdapter.Factory请求适配器(添加在集合器末尾)adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));// 请求适配器工厂集合存储顺序:自定义1适配器工厂、自定义2适配器工厂...默认适配器工厂(ExecutorCallAdapterFactory)<--  配置数据转换器工厂:converterFactory -->// 在步骤2中已经添加了内置的数据转换器BuiltInConverters()(添加到集合器的首位)// 在步骤4中又插入了一个Gson的转换器 - GsonConverterFactory(添加到集合器的首二位)List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);// 数据转换器工厂集合存储的是:默认数据转换器工厂( BuiltInConverters)、自定义1数据转换器工厂(GsonConverterFactory)、自定义2数据转换器工厂....// 注:
//1. 获取合适的网络请求适配器和数据转换器都是从adapterFactories和converterFactories集合的首位-末位开始遍历
// 因此集合中的工厂位置越靠前就拥有越高的使用权限// 最终返回一个Retrofit的对象,并传入上述已经配置好的成员变量return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,callbackExecutor, validateEagerly);}

至此,步骤5分析完毕,通过前面步骤设置的变量,将Retrofit类的所有成员变量都配置完毕。
所以,成功创建了Retrofit的实例

总结

  • 平台类型对象(Platform - Android)
  • 网络请求的url地址(baseUrl)
  • 网络请求工厂(callFactory)

默认使用OkHttpCall

  • 网络请求适配器工厂的集合(adapterFactories)

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

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

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

  • 回调方法执行器(callbackExecutor)

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

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

2.2.2创建网络请求接口的实例

外观模式 & 代理模式

使用外观模式进行访问,里面用了代理模式

1.外观模式
  • 外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。

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

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

大大降低了系统的耦合度

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

分为静态代理 & 动态代理:

  1. 静态代理:代理类在程序运行前已经存在的代理方式
  2. 动态代理:代理类在程序运行前不存在、运行时由程序动态生成的代理方式
  • return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)通过代理模式中的动态代理模式,动态生成网络请求接口的代理类,并将代理类的实例创建交给InvocationHandler类 作为具体的实现,并最终返回一个动态代理对象。

生成实例过程中含有生成实现类的缓存机制(单例模式),下面会详细分析

使用动态代理的好处:

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

使用步骤

<-- JavaBean.java -->
public class JavaBean {.. // 这里就不介绍了}<-- 步骤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();

源码分析

  • 下面主要分析步骤3和步骤4:
<-- 步骤3:在MainActivity创建接口类实例  -->
AccessApi NetService = retrofit.create(NetService.class);<-- 步骤4:对发送请求的url进行封装,即生成最终的网络请求对象  --> Call<JavaBean> call = NetService.getCall();

步骤3

 public <T> T create(final Class<T> service) {if (validateEagerly) {  // 判断是否需要提前验证eagerlyValidateMethods(service); // 具体方法作用:// 1. 给接口中每个方法的注解进行解析并得到一个ServiceMethod对象// 2. 以Method为键将该对象存入LinkedHashMap集合中// 特别注意:如果不是提前验证则进行动态解析对应方法(下面会详细说明),得到一个ServiceMethod对象,最后存入到LinkedHashMap集合中,类似延迟加载(默认)}  // 创建了网络请求接口的动态代理对象,即通过动态代理创建网络请求接口的实例 (并最终返回)// 该动态代理是为了拿到网络请求接口实例上所有注解return (T) Proxy.newProxyInstance(service.getClassLoader(),      // 动态生成接口的实现类 new Class<?>[] { service },    // 动态创建实例new InvocationHandler() {     // 将代理类的实现交给 InvocationHandler类作为具体的实现(下面会解释)private final Platform platform = Platform.get();// 在 InvocationHandler类的invoke()实现中,除了执行真正的逻辑(如再次转发给真正的实现类对象),还可以进行一些有用的操作// 如统计执行时间、进行初始化和清理、对接口调用进行检查等。@Override public Object invoke(Object proxy, Method method, Object... args)throws Throwable {// 下面会详细介绍 invoke()的实现// 即下面三行代码ServiceMethod serviceMethod = loadServiceMethod(method);	 OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);return serviceMethod.callAdapter.adapt(okHttpCall);}});}// 特别注意
// return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler invocationHandler)
// 可以解读为:getProxyClass(loader, interfaces) .getConstructor(InvocationHandler.class).newInstance(invocationHandler);
// 即通过动态生成的代理类,调用interfaces接口的方法实际上是通过调用InvocationHandler对象的invoke()来完成指定的功能
// 先记住结论,在讲解步骤4的时候会再次详细说明<-- 关注点1:eagerlyValidateMethods() -->
private void eagerlyValidateMethods(Class<?> service) {  Platform platform = Platform.get();  for (Method method : service.getDeclaredMethods()) {  if (!platform.isDefaultMethod(method)) {  loadServiceMethod(method); } // 将传入的ServiceMethod对象加入LinkedHashMap<Method, ServiceMethod>集合// 使用LinkedHashMap集合的好处:lruEntries.values().iterator().next()获取到的是集合最不经常用到的元素,提供了一种Lru算法的实现}  
}  

步骤4

Call call = NetService.getCall();

  • 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对象

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

总结

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

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

2.2.3执行网络请求

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

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

  • OkHttpCall提供了两种网络请求方式:
    • 同步请求:OkHttpCall.execute()
    • 异步请求:OkHttpCall.enqueue()

同步请求OkHttpCall.execute()

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

上面简单的一行代码,其实包含了整个发送网络同步请求的三个步骤。

3.源码分析
@Override 
public Response<T> execute() throws IOException {okhttp3.Call call;// 设置同步锁synchronized (this) {call = rawCall;if (call == null) {try {call = rawCall = createRawCall();// 步骤1:创建一个OkHttp的Request对象请求 -->关注1} catch (IOException | RuntimeException e) {creationFailure = e;throw e;}}}return parseResponse(call.execute());// 步骤2:调用OkHttpCall的execute()发送网络请求(同步)// 步骤3:解析网络请求返回的数据parseResponse() -->关注2
}<-- 关注1:createRawCall()  -->
private okhttp3.Call createRawCall() throws IOException {Request request = serviceMethod.toRequest(args);// 从ServiceMethod的toRequest()返回一个Request对象okhttp3.Call call = serviceMethod.callFactory.newCall(request);// 根据serviceMethod和request对象创建 一个okhttp3.Requestif (call == null) {throw new NullPointerException("Call.Factory returned null.");}return call;
}<--  关注2:parseResponse()-->
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {ResponseBody rawBody = rawResponse.body();rawResponse = rawResponse.newBuilder().body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())).build();// 收到返回数据后进行状态码检查// 具体关于状态码说明下面会详细介绍int code = rawResponse.code();if (code < 200 || code >= 300) {}if (code == 204 || code == 205) {return Response.success(null, rawResponse);}ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);try {T body = serviceMethod.toResponse(catchingBody);// 等Http请求返回后 & 通过状态码检查后,将response body传入ServiceMethod中,ServiceMethod通过调用Converter接口(之前设置的GsonConverterFactory)将response body转成一个Java对象,即解析返回的数据// 生成Response类return Response.success(body, rawResponse);} catch (RuntimeException e) {... // 异常处理}
}

特别注意:

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

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

  • 关于状态码检查时的状态码说明:
    在这里插入图片描述

异步请求OkHttpCall.enqueue()

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

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

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

指定的线程此处是指主线程(UI线程)

2.具体使用
call.enqueue(new Callback<JavaBean>() {@Overridepublic void onResponse(Call<JavaBean> call, Response<JavaBean> response) {System.out.println(response.isSuccessful());if (response.isSuccessful()) {response.body().show();}else {try {System.out.println(response.errorBody().string());} catch (IOException e) {e.printStackTrace();} ;}}
  • 从上面分析有:call是一个静态代理
  • 使用静态代理的作用是:在okhttpCall发送网络请求的前后进行额外操作

这里的额外操作是:线程切换,即将子线程切换到主线程,从而在主线程对返回的数据结果进行处理

3.源码分析
<--  call.enqueue()解析  -->
@Override 
public void enqueue(final Callback<T> callback) {delegate.enqueue(new Callback<T>() {// 使用静态代理 delegate进行异步请求 ->>分析1// 等下记得回来@Override public void onResponse(Call<T> call, final Response<T> response) {// 步骤4:线程切换,从而在主线程显示结果callbackExecutor.execute(new Runnable() {// 最后Okhttp的异步请求结果返回到callbackExecutor// callbackExecutor.execute()通过Handler异步回调将结果传回到主线程进行处理(如显示在Activity等等),即进行了线程切换// 具体是如何做线程切换 ->>分析2@Override public void run() {if (delegate.isCanceled()) {callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));} else {callback.onResponse(ExecutorCallbackCall.this, response);}}});}@Override public void onFailure(Call<T> call, final Throwable t) {callbackExecutor.execute(new Runnable() {@Override public void run() {callback.onFailure(ExecutorCallbackCall.this, t);}});}});}<-- 分析1:delegate.enqueue()解析 -->
@Override 
public void enqueue(final Callback<T> callback) {okhttp3.Call call;Throwable failure;// 步骤1:创建OkHttp的Request对象,再封装成OkHttp.call// delegate代理在网络请求前的动作:创建OkHttp的Request对象,再封装成OkHttp.callsynchronized (this) {if (executed) throw new IllegalStateException("Already executed.");executed = true;call = rawCall;failure = creationFailure;if (call == null && failure == null) {try {call = rawCall = createRawCall();	// 创建OkHttp的Request对象,再封装成OkHttp.call// 方法同发送同步请求,此处不作过多描述	} catch (Throwable t) {failure = creationFailure = t;}}// 步骤2:发送网络请求// delegate是OkHttpcall的静态代理// delegate静态代理最终还是调用Okhttp.enqueue进行网络请求call.enqueue(new okhttp3.Callback() {@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)throws IOException {Response<T> response;try {// 步骤3:解析返回数据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();}}});}// 请回去上面分析1的起点<-- 分析2:异步请求后的线程切换-->
// 线程切换是通过一开始创建Retrofit对象时Platform在检测到运行环境是Android时进行创建的:(之前已分析过)
// 采用适配器模式
static class Android extends Platform {// 创建默认的回调执行器工厂// 如果不将RxJava和Retrofit一起使用,一般都是使用该默认的CallAdapter.Factory// 后面会对RxJava和Retrofit一起使用的情况进行分析@OverrideCallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {return new ExecutorCallAdapterFactory(callbackExecutor);}@Override public Executor defaultCallbackExecutor() {// 返回一个默认的回调方法执行器// 该执行器负责在主线程(UI线程)中执行回调方法return new MainThreadExecutor();}// 获取主线程Handlerstatic class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());@Override public void execute(Runnable r) {// Retrofit获取了主线程的handler// 然后在UI线程执行网络请求回调后的数据显示等操作。handler.post(r);}}// 切换线程的流程:
// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象
// 2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程处理返回结果(如显示在Activity等等)}

以上便是整个以 异步方式发送网络请求的过程。


三、总结

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

在这里插入图片描述

相关文章:

Retrofit源码分析

文章目录一、简介二、源码分析2.1Retrofit的本质流程2.2源码分析2.2.1 创建Retrofit实例步骤1步骤2步骤3步骤4步骤5总结2.2.2创建网络请求接口的实例外观模式 & 代理模式1.外观模式2. 代理模式步骤3步骤4总结2.2.3执行网络请求同步请求OkHttpCall.execute()1.发送请求过程2…...

Mybatis-Plus入门系列(20) -兼容多种数据库

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录前言方案分析1. 分页2. XML自定义SQL案例演示1. 配置2. 简单分页查询3. 带方言的分页查询参考前言 在我们实际开发软件产品过程中&#xff0c;数据库的类型可能不是确定的&#xff0c;也有客户…...

JetPack板块—Android X解析

Android Jetpack简述 AndroidX 是Android团队用于在Jetpack中开发&#xff0c;测试&#xff0c;打包&#xff0c;发布和版本管理的开源项目。相比于原来的Android Support库,AndroidX 可以称得上是一次重大的升级改进。 和Support库一样&#xff0c;AndroidX与Android 操作系…...

C++学习笔记-数字

当我们使用数字时&#xff0c;通常我们使用原始数据类型&#xff0c;例如 int&#xff0c;short&#xff0c;long&#xff0c;float 和 double 等。数字数据类型&#xff0c;它们的可能值和取值范围在讨论 C 数据类型时已经解释了。 C 定义数字 我们已经在之前笔记的各种实例…...

Nginx——Nginx的基础原理

摘要 Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器,是一个高性能的HTTP和反向代理服务器&#xff0c;同时也是一个 IMAP/POP3/SMTP 代理服务器。Nginx 是由俄罗斯人 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的&#xff0c;它已经在该站点运行超过两年半了。…...

服务端开发Java之备战秋招面试篇1

在这个面试造火箭工作拧螺丝的时代背景下&#xff0c;感觉不是很好&#xff0c;不过还好也是拿到了还行的offer&#xff0c;准备去实习了&#xff0c;接下来就是边实习边准备秋招了&#xff0c;这半年把&#xff08;技术栈八股文面经算法题项目&#xff09;吃透&#xff0c;希望…...

【C++的OpenCV】第三课-OpenCV图像加载和显示

我们开始学习OpenCV一、OpenCV加载图片和显示图片1.1 imread()函数的介绍1.2 cv::namedWindow()函数的介绍1.4 imshow()函数介绍1.5 Mat容器介绍二、 代码实例&#xff08;带注释&#xff09;2.1 代码2.2 执行结果一、OpenCV加载图片和显示图片 本章节中&#xff0c;将会学习到…...

【面试1v1实景模拟】Spring事务 一文到底

老面👴:小伙子,了解Spring的事务吗? 解读🔔:这个必须了解,不了解直接挂~😂😂😂,但面试官肯定不是想听你了解两个字,他是想让你简单的介绍下。 笑小枫🍁:了解,事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的,比如说 MySQL。为…...

Neuron Selectivity Transfer 原理与代码解析

paper&#xff1a;Like What You Like: Knowledge Distill via Neuron Selectivity Transfercode&#xff1a;https://github.com/megvii-research/mdistiller/blob/master/mdistiller/distillers/NST.py本文的创新点本文探索了一种新型的知识 - 神经元的选择性知识&#xff0c…...

vue项目关闭子页面,并更新父页面的数据

今天下午是一个非常痛苦的&#xff0c;想要实现一个功能&#xff1a; 父页面打开了一个新的页面&#xff08;浏览器打开一个新的窗口&#xff09;&#xff0c;并在子页面提交数据之后&#xff0c;父页面的数据要同步更新。 难点&#xff1a;父页面是一个表格列表&#xff0c;…...

第五次作业:修改redis的配置文件使得windows的图形界面客户端可以连接redis服务器

1. 安装 Redis 依赖 Redis 是基于 C语言编写的&#xff0c;因此首先需要安装 Redis 所需要的 gcc 依赖&#xff1a; yum install -y gcc tcl 2、上传安装文件 将下载好的 redis-6.2.7.tar.gz 安装包上传到虚拟机的任意目录&#xff08;一般推荐上传到 /usr/local/src目录&am…...

【11】FreeRTOS的延时函数

目录1.延时函数-介绍2.相对延时函数-解析2.1函数prvAddCurrentTaskToDelayedList-解析2.3滴答定时器中断服务函数xPortSysTickHandler()-解析2.4函数taskSWITCH_DELAYED_LISTS() -解析3.延时函数-实验4.总结1.延时函数-介绍 函数描述vTaskDelay()相对延时xTaskDelayUntil()绝对…...

Vue页面组成及常用属性

一、Vue页面组成 目前的项目中&#xff0c;Vue页面都是采用组件套娃的形式&#xff0c;由一个一个的组件拼接而成整个页面。一个组件就是一个.vue文件。组件通常由template和script两部分组成&#xff1a; template部分&#xff1a;页面展示的具体元素内容&#xff0c;比如文字…...

j6-IO流泛型集合多线程注解反射Socket

IO流 1 JDK API的使用 2 io简介 输入流用来读取in 输出流用来写出Out 在Java中&#xff0c;根据处理的数据单位不同&#xff0c;分为字节流和字符流 继承结构 java.io包&#xff1a; File 字节流&#xff1a;针对二进制文件 InputStream --FileInputStream --BufferedInputStre…...

创业能否成功?这几个因素很重要!

创业能否成功&#xff1f;这几个因素很重要&#xff01; 2023-02-22 19:06:53 大家好&#xff0c;我是你们熟悉而又陌生的好朋友梦龙&#xff0c;一个创业期的年轻人 上周末跟朋友一起钓鱼&#xff0c;他跟吐槽现在生意越来越难做。他是我身边可以说是创业很成功的例子&#…...

Bmp图片格式介绍

Bmp图片格式介绍 介绍 BMP是英文Bitmap&#xff08;位图&#xff09;的简写&#xff0c;它是Windows操作系统中的标准图像文件格式&#xff0c;能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的开发&#xff0c;BMP位图格式理所当然地被…...

Day4 leetcode

Day4 啊啊啊啊&#xff0c;什么玩意&#xff0c;第一次因为测评没过&#xff0c;约好的面试取消了&#xff0c;好尴尬呀&#xff0c;还有一家厦门的C/C电话面&#xff0c;是一家我还挺喜欢的公司&#xff0c;面的稀烂&#xff0c;只能安慰自己我现在手上至少有一个offer 有效括…...

Java设计模式-原型模式

1、定义 原型模式是一种创建型模式&#xff0c;用于创建重复的对象&#xff0c;并且保证性能。原型模式创建的对象是由原型对象自身创建的&#xff0c;是原型对象的一个克隆&#xff0c;和原型对象具有相同的结构和相同的值。 2、适用场景 创建对象时我们不仅仅需要创建一个新…...

2023年度最新且最详细Ubuntu的安装教程

目录 准备ISO镜像 1.去官网下载镜像&#xff0c;或者找有镜像源的网站下载 阿里云镜像站 2. 如果服务器是打算直接把底层系统安装为Ubuntu的话还需制作系统U盘 安装 1.新建虚拟机调整基础配置 2.打开电源&#xff0c;进入安装界面&#xff08;到这一步就跟u盘安装步骤一致…...

unix高级编程-fork之后父子进程共享文件

~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件. 这里我看到的是centos的操作&#xff0c;但我用的是debian系的ubuntu&#xff0c;百度了一下发现debian的在这里…...

vue+echarts:柱状图横向展示和竖向展示

第021个点击查看专栏目录本示例是显示柱状图&#xff0c;分别是横向展示和纵向展示。关键是X轴和Y轴的参数互换。 文章目录横向示例效果横向示例源代码&#xff08;共81行&#xff09;纵向示例效果纵向示例源代码&#xff08;共81行&#xff09;相关资料参考专栏介绍横向示例效…...

SealOS 一键安装 K8S

环境 # 查看系统发行版 $ cat /etc/os-release NAME"CentOS Linux" VERSION"7 (Core)" ID"centos" ID_LIKE"rhel fedora" VERSION_ID"7" PRETTY_NAME"CentOS Linux 7 (Core)" ANSI_COLOR"0;31" CPE_NA…...

python网络编程详解

最近在看《UNIX网络编程 卷1》和《FREEBSD操作系统设计与实现》这两本书&#xff0c;我重点关注了TCP协议相关的内容&#xff0c;结合自己后台开发的经验&#xff0c;写下这篇文章&#xff0c;一方面是为了帮助有需要的人&#xff0c;更重要的是方便自己整理思路&#xff0c;加…...

ICRA 2023 | 首个联合暗光增强和深度估计的自监督方法STEPS

原文链接&#xff1a;https://www.techbeat.net/article-info?id4629 作者&#xff1a;郑宇鹏 本文中&#xff0c;我们提出了STEPS&#xff0c;第一个自监督框架来联合学习图像增强和夜间深度估计的方法。它可以同时训练图像增强网络和深度估计网络&#xff0c;并利用了图像增…...

基于react+nodejs+mysql开发用户中心,用于项管理加入的项目的用户认证

基于reactnodejsmysql开发用户中心&#xff0c;用于项管理加入的项目的用户认证用户中心功能介绍页面截图后端采用架构user表projects表project_user表仓库地址用户中心功能介绍 用户中心项目&#xff0c;用于统一管理用户信息、登录、注册、鉴权等 功能如下&#xff1a; 用…...

mapreduce与yarn

文章目录一、MapReduce1.1、MapReduce思想1.2、MapReduce实例进程1.3、MapReduce阶段组成1.4、MapReduce数据类型1.5、MapReduce关键类1.6、MapReduce执行流程1.6.1、Map阶段执行流程1.6.2、Map的shuffle阶段执行流程1.6.3、Reduce阶段执行流程1.7、MapReduce实例WordCount二、…...

鲲鹏云服务器上使用 traceroute 命令跟踪路由

traceroute 命令跟踪路由 它由遍布全球的几万局域网和数百万台计算机组成&#xff0c;并通过用于异构网络的TCP/IP协议进行网间通信。互联网中&#xff0c;信息的传送是通过网中许多段的传输介质和设备&#xff08;路由器&#xff0c;交换机&#xff0c;服务器&#xff0c;网关…...

代码随想录算法训练营第47天 || 198.打家劫舍 || 213.打家劫舍II || 337.打家劫舍III

代码随想录算法训练营第47天 || 198.打家劫舍 || 213.打家劫舍II || 337.打家劫舍III 198.打家劫舍 题目介绍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&…...

JVM调优方式

对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。 1.Full GC 会对整个堆进行整理&#xff0c;包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收&#xff0c;所以比较慢&#xff0c;因此应该尽可能减少Full GC的次数。 2.导致Full GC的原因 1)年老…...

机器学习模型监控的 9 个技巧

机器学习 (ML) 模型是非常敏感的软件&#xff1b;它们的成功使用需要进行仔细监控以确保它们可以正常工作。当使用所述模型的输出自动做出业务决策时尤其如此。这意味着有缺陷的模型通常会对终端客户的体验产生真正的影响。因此&#xff0c;监控输入数据&#xff08;和输出&…...