Retrofit源码分析

news2024/10/7 8:25:13

文章目录

  • 一、简介
  • 二、源码分析
    • 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是默认使用okhttp
  
   private 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指定了运行平台为Android
    return new Platform();
  }
...
}

<-- 步骤4 -->
// 用于接收服务器返回数据后进行线程切换在主线程显示结果

static class Android extends Platform {

    @Override
      CallAdapter.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) ->> 步骤2
      return 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对象实例的GsonConverterFactory
    return 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()后又放入了一个GsonConverterFactory
  public 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默认的callbackExecutor
      if (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.Request

  if (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>() {
            @Override
            public 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.call
    synchronized (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一起使用的情况进行分析
    @Override
      CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }

    @Override 
      public Executor defaultCallbackExecutor() {
      // 返回一个默认的回调方法执行器
      // 该执行器负责在主线程(UI线程)中执行回调方法
      return new MainThreadExecutor();
    }

    // 获取主线程Handler
    static 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请求

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决 NestHost requires ASM7 (shrink、kotlin metadata)

① 场景 Caused by: java.lang.RuntimeException: NestHost requires ASM7Failed to resolve class org/vigame/demo/CrashHandler$1.class[transform input:not foundproject input:not foundaar input:not found]Caused by: java.lang.UnsupportedOperationException: NestH…

flstudio21中文版下载安装图文教程

fl studio21中文版是一款免费的音乐编曲制作软件&#xff0c;有了它你可以制作出色的音乐。它为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您的工作会变得更有条理。同时FL Studio为用户提供了更先进和原创的音乐制作理念&#xff0c;用户可以轻…

简单学生管理系统

文章目录1. 学生类2. 学生管理类3. 运行结果1. 学生类 包含四个属性&#xff0c;学号、姓名、年龄及地址。类中包含一个无参构造、一个有参构造以及各属性的 get、set 方法。 package com.zxe;public class Student {private String id;private String name;private String a…

Vue+element ui遇到的一些疑难问题总结(一)

element ui 疑难总结1. el-date-picker时间区间控制2. el-cascader 获取name3. el-tree 搜索判断是否匹配到值1. el-date-picker时间区间控制 控制只能选区间&#xff08;7天&#xff0c;一个月&#xff0c;两个月等等&#xff09;重点为 :picker-options"pickerOptions&…

Python爬虫(10)selenium爬虫后数据,存入csv、txt并将存入数据并对数据进行查询

之前的文章有关于更多操作方式详细解答&#xff0c;本篇基于前面的知识点进行操作&#xff0c;如果不了解可以先看之前的文章 Python爬虫&#xff08;1&#xff09;一次性搞定Selenium(新版)8种find_element元素定位方式 Python爬虫&#xff08;2&#xff09;-Selenium控制浏览…

【原创】java+swing+mysql大学生竞赛管理系统设计与实现

上一篇文章我们介绍了使用swingtxt进行系统设计和数据存储&#xff0c;今天我们还是回归现实&#xff0c;使用javaswingmysql去设计开发一个大学生竞赛管理系统&#xff0c;以方便管理员对大学竞赛的一些信息进行管理。 功能分析&#xff1a; 大学生竞赛管理系统主要是提供给…

黄金短期陷入低位震荡颠簸

基本面&#xff1a; 周二&#xff08;2月21日)黄金价格维持1843-1830区间震荡&#xff0c;日线收带上下影线小阴线。 今日数据 无重要数据 技术面&#xff1a; 日线上&#xff0c;黄金日线收带上下影线小阴线&#xff0c;目前处于短期线附近及下方运行&#xff0c;5日与1…

CAN现场总线基础知识总结,看这一篇就理清了(CAN是什么,电气属性,CAN通协议等)

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…

FPGA纯Vhdl实现MIPI CSI2RX视频解码输出,OV13850采集,提供工程源码和技术支持

目录1、前言2、Xilinx官方主推的MIPI解码方案3、纯Vhdl方案解码MIPI4、vivado工程介绍5、上板调试验证6、福利&#xff1a;工程代码的获取1、前言 FPGA图像采集领域目前协议最复杂、技术难度最高的应该就是MIPI协议了&#xff0c;MIPI解码难度之高&#xff0c;令无数英雄竞折腰…

Vue3电商项目实战-商品详情模块3【07-★规格组件-SKUSPU概念、08-★规格组件-基础结构和样式、09-★规格组件-渲染与选中效果】

文章目录07-★规格组件-SKU&SPU概念08-★规格组件-基础结构和样式09-★规格组件-渲染与选中效果07-★规格组件-SKU&SPU概念 官方话术&#xff1a; SPU&#xff08;Standard Product Unit&#xff09;&#xff1a;标准化产品单元。是商品信息聚合的最小单位&#xff0…

二月天-课后程序(JAVA基础案例教程-黑马程序员编著-第五章-课后作业)

【案例5-5】 二月天 【案例介绍】 1.任务描述 二月是一个有趣的月份&#xff0c;平年的二月有28天&#xff0c;闰年的二月有29天。本例要求编写一个程序&#xff0c;从键盘输入年份&#xff0c;根据输入的年份计算这一年的2月有多少天。在计算二月份天数时&#xff0c;可以使…

【微信小程序】--创建第一个小程序项目项目文件的作用(二)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#…

华为OD机试 - 数组合并(C++) | 附带编码思路 【2023】

刷算法题之前必看 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:https://blog.csdn.net/hihell/category_12199283.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 华为OD机试题…

[业务逻辑] 订单超时怎么处理

文章目录1.订单的过程分析2.JDK自带的延时队列 (单机)3.RabbitMQ的延时消息 (消息队列方案)4.RocketMQ的定时消息 (消息队列方案)5.Redis过期监听 (Redis方案)6.定时任务分布式批处理 (扫表轮训方案)7.总结1.订单的过程分析 一个订单流程中有许多环节要用到超时处理 买家超时未…

echarts 饼状图 label 既在内部显示数值(百分比),又显示外部指示线

需求 项目开发中&#xff0c;产品经理绘制的原型图中&#xff0c;需要前端实现一个饼状图&#xff0c;且既在饼图内部中 显示 百分比&#xff0c;又显示 外部指示线及数值&#xff0c;效果如下图所示&#xff1a; 查了下 Echarts 官网文档&#xff0c;需要配置 series 下的 la…

使用vs2022编译yolov5+tensorRT+cuda+cudnn代码进行混合编译

首先依赖有cuda、cudnn、tensorrt、protobuf&#xff0c;从Linux的代码直接移植过来这些库是没法使用的&#xff0c;需要下载对应win的下的版本&#xff0c;其中cuda、cudnn和tensorrt直接从官方下载即可&#xff0c;但是protobuf需要自己编译一下&#xff08;protobuf3.11.4&a…

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

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

【数据挖掘实战】——基于水色图像的水质评价

项目地址&#xff1a;Datamining_project: 数据挖掘实战项目代码 目录 一、背景和挖掘目标 1、问题背景 2、水色分类 3、原始数据 4、挖掘目标 二、分析方法和过程 1、初步分析 2、总体流程 第1步&#xff1a;数据预处理——图像切割 第2步&#xff1a;特征提取 ​…

健身蓝牙耳机推荐,推荐五款适合健身的蓝牙耳机

出门运动健身&#xff0c;有音乐的陪伴是我们坚持运动的不懈动力&#xff0c;在健身当中佩戴的耳机&#xff0c;佩戴舒适度以及牢固程度是我们十分需要注意的&#xff0c;还不知道如何选择健身蓝牙耳机&#xff0c;可以看看下面这些运动蓝牙耳机分享。 1、南卡Runner Pro4骨传…