学习Retrofit后,你还需要明白这些……

news2024/11/25 1:03:27

在学习Retrofit后,由于它本身就是OKHttp的封装,面试中也经常会被一起问到;单纯的解析它的源码学习难免会有点无从下手,往往让人抓不住重点,学习效率并不是很高,本文从提出几个问题出发,带着问题去思考学习Retrofit源码,从而快速理解它的核心知识点

下面我将从以下几个问题来梳理Retrofit的知识体系,方便自己理解

  • Retrofitcreate为什么使用动态代理?
  • 谈谈Retrofit运用的动态代理及反射?
  • Retrofit注解是怎么进行解析的?
  • Retrofit如何将注解封装成OKHttpCall?
  • Rretrofit是怎么完成线程切换和数据适配的?

Retrofitcreate为什么使用动态代理

我们首先可以看Retrofit代理实例创建过程,通过一个例子来说明

    val retrofit = Retrofit.Builder()
            .baseUrl("https://www.baidu.com")
            .build()
        val myInterface = retrofit.create(MyInterface::class.java)

创建了一个MyInterface接口类对象,create函数内使用了动态代理来创建接口对象,这样的设计可以让所有的访问请求都给被代理,这里我简化了下它的create函数,简单来说它的作用就是创建了一个你传入类型的接口实例

/**
     *
     * @param loader 需要代理执行的接口类
     * @return 动态代理,运行的时候生成一个loader对象类型的类,在调用它的时候走
     */
    @SuppressWarnings("unchecked")
    public <T> T create(final Class<T> loader) {
        return (T) Proxy.newProxyInstance(loader.getClassLoader(),
                new Class<?>[]{loader}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("具体方法调用前的准备工作");
                        Object result = method.invoke(object, args);
                        System.out.println("具体方法调用后的善后事情");
                        return result;
                    }
                });
    }

那么这个函数为什么要使用动态代理呢,这样有什么好处?

我们进行Retrofit请求的时候构建了许多接口,并且都要调用接口中的对象;接下来我们再调用这个对象中的getSharedList方法

val sharedListCall:Call<ShareListBean> = myService.getSharedList(2,1)

在调用它的时候,在动态代理里面,在运行的时候会存在一个函数getSharedList,这个函数里面会调用invoke,这个invoke函数就是Retrofit里的invoke函数;并且也形成了一个功能拦截,如下图所示:

所以,相当于动态代理可以代理所有的接口,让所有的接口都走invoke函数,这样就可以拦截调用函数的值,相当于获取到所有的注解信息,也就是Request动态变化内容,至此不就可以动态构建带有具体的请求的URL了么,从而就可以将网络接口的参数配置归一化

这样也就解决了之前OKHttp存在的接口配置繁琐问题,既然都是要构建Request,为了自主动态的来完成,所以Retrofit使用了动态代理

谈谈Retrofit运用的动态代理及反射

那么我们在读Retrofit源码的时候,是否都有这样一个问题,为什么我写个接口以及一些接口Api,我们就可以完成相应的http请求呢?Retrofit到底在这其中做了什么工作?简单来说,其核心就是通过反射+动态代理来解决的,那么动态代理和反射的原理是怎么样的?

代理模式梳理

首先我们要明白代理模式到底是怎么样的,这里我简单梳理下

  • 代理类与委托类有着同样的接口
  • 代理类主要为委托类预处理消息,过滤消息,然后把消息发送给委托类,以及事后处理消息等等
  • 一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,为提供特定的服务

以上是普通代理模式(静态代理)它是有一个具体的代理类来实现的

动态代理+反射

那么Retrofit用到的动态代理呢?答案不言而喻,所谓动态就是没有一个具体的代理类,我们看到Retrofitcreate函数中,它是可以为委托类对象生成代理类, 代理类可以将所有的方法调用分派到委托对象上反射执行,大致如下

  • 接口的classLoader
  • 只包含接口的class数组
  • 自定义的InvocationHandler()对象, 该对象实现了invoke() 函数, 通常在该函数中实现对委托类函数的访问

这就是在create函数中所使用的动态代理及反射

扩展:通过这些文章,了解更多动态代理与反射

反射,动态代理在Retrofit中的运用:https://www.jianshu.com/p/d4892eea71b8

Retrofit的代理模式解析:https://juejin.cn/post/7027839465104080932

Retrofit注解是怎么进行解析的?

在使用Retrofit的时候,或者定义接口的时候,在接口方法中加入相应的注解(@GET,@POST,@Query@FormUrlEncoded等),然后我们就可以调用这些方法进行网络请求了;那么就有了问题,为什么注解可以完整的覆盖网络请求?我们知道,注解大致分为三类,通过请求方法、请求体编码格式、请求参数,大致有22种注解,它们基本完整的覆盖了HTTP请求方案 ;通过它们我们确定了网络请求request的具体方案;

此时,就抛出了开始的问题,Retrofit注解是怎么被解析的呢?这里就要熟悉Retrofit中的ServiceMethod类了,总的来说,它首先选择Retrofit里提供的工具(数据转换器converter,请求适配器adapter),相当于就是具体请求Request的一个封装,封装完成之后将注解进行解析;

下面通过官方提供例子来说明下ServiceMethod组成的部分

其中 @GET("users/{user}/repos"是由parseMethodAnnotation负责解析的;@Path参数注解就由对应ParameterHandler进行处理,剩下的Call<List<Repo>毫无疑问就是使用CallAdapter将这个Call 类型适配为用户定义的 service method 的返回类型。

那么ServiceMethod是怎么对注解进行解析的呢,来简单梳理下它的源码

  • 首先,在loadService方法中进行检测,禁止静态方法,这里Retrofit笔者使用的是2.9.0版本,不再是直接调用ServiceMethod.Builder(),而是使用缓存的方式调用ServiceMethod.parseAnnotations(this, method),将它转为RequestFactory对象,其实本地大同小异,原理是差不多的
final class RequestFactory {
  static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }
  ...
  • 同样在RequestFactory中也是使用Builder模式,其实就是封装了一层,传入retrofit-method两个参数,在这里面我们调用了Method类获取了它的注解数组methodAnnotations,型参的类型数组parameterTypes,型参的注解数组parameterAnnotationsArray
     Builder(Retrofit retrofit, Method method) {
          this.retrofit = retrofit;
          this.method = method;
          this.methodAnnotations = method.getAnnotations();
          this.parameterTypes = method.getGenericParameterTypes();
          this.parameterAnnotationsArray = method.getParameterAnnotations();
        }
  • 然后在build方法中,它会创建一个ReqeustFactory对象,最终解析它通过HttpServiceMethod又转换成ServiceMethod实例,这个方法里主要就是针对注解的解析过程,由于源码非常长,感兴趣的同学可以去详细阅读下,这里大概概括下几个重要的解析方法
  1. parseMethodAnnotation

该方法就是确定网络的传输方式,判断加了哪些注解,下面借用一张网络上的图表达会更直观点

  1. parseHttpMethodAndPath,parseHeaders

我们通过上图也可以看到,其实就是解析httpMethodheaders,它们都是在parseMethodAnnotation方法中被调用的,从而进行细化。前者确定的是请求方法(get,post,delete等),后者顾名思义确定的是headers头部;前者会检测httpMethod,它不允许有多个方法注解,会使用正则表达式进行判断,url中的参数必须用括号占位,最终提取出了真实的urlurl中携带的参数名称;后者就是解析当前Http请求的头部信息

  • 经过以上方法注解处理以及验证,在build方法中还要对参数注解进行处理
    int parameterCount = parameterAnnotationsArray.length;
          parameterHandlers = new ParameterHandler<?>[parameterCount];
          for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
            parameterHandlers[p] =
                parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
          }

它循环进行参数的验证处理,通过parseParameter方法最后一个参数判断是否继续,参考下网络上的图示

简单说下ParameterHandler是怎么处理这个参数注解的?它会通过进行两项校验,分别是对不确定的类型合法校验路径名称的校验,然后就是一堆参数注解的处理,分析源码后可以看到ParameterHandler最终都组装成了一个RequestBuilder,那么它是用来干神马的?答案是生成OKHttpRequest,网络请求还是交给OKHttp来完成

以上简单分析了下Retrofit注解的解析过程,需要深入了解的同学请自行探索。

如果同学对注解不太熟悉,想要了解Java注解的相关知识点可以阅读这篇文章—>(Retrofit注解:https://www.jianshu.com/p/f1a8356c615f

Retrofit如何将注解封装成OKHttpcall

上个问题已经知道了Retrofit中的ServiceMethod对会注解进行解析封装,这时候各种网络请求适配器,请求头,参数,转换器等等都准备好了,最终它会将ServiceMethod转为Retrofit提供的OkHttpCall,这个就是对okhttp3.Call的封装,答案已经呼之欲出了。

 @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

换种说法,相当于在ServiceMethod中已经将注解转变为url +请求头+参数等等格式,对照OKHttp请求流程,是不是已经完成了构建Request请求了,它最终要变成Okhttp3.call才能进行网络请求,所以OkHttpCall基本上就是做了这么一件事情,下面有张图可以直观看下ServiceMethod大概做了哪些事情

接着我们看下okhttpCall中的enqueue方法,它会去创建一个真实的Call,这个其实就是OKHttp中的call,接下来的网络请求工作就交给OKHttp来完成

private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }

到这里,说白了Retrofit其实没有任何与网络请求相关的东西,它最终还是通过统一解析注解Request去构建OkhttpCall执行,通过设计模式去封装执行OkHttp

Rretrofit是怎么完成线程切换和数据适配的

Retrofit在网络请求完成后所做的就只有两件事,自动线程切换和数据适配;那么它是如何完成这些操作的呢?

关于数据适配

其实这个在上文注解解析问题中已经回答了一部分了,这里我大概总结下流程,具体的数据解析适配过程细节需要大家私下去深入探讨;在封装的OkhttpCall调用OKHttp进行网络请求后会拿到接口响应结果response,这时候就要进行数据格式的解析适配了,会调用parsePerson方法,里面最终还是会调用RetrofitconverterFactories拿到数据转换器工厂的集合其中的一个,所以当我们创建Retrfoit中进行addConvetFactory的时候,它保存在了Retrofit当中,交给了responseConverter.convert(responsebody),从而完成了数据转换适配的过程

关于线程切换

首先我们要知道线程切换是发生在什么时候?毫无疑问,肯定是在最后一步,当网络请求回来后,且进行数据解析后,那这样我们向上寻根,发现最终数据解析由HttpServiceMethod之后它会调用callAdapter.adapt()进行适配

 protected Object adapt(Call<ResponseT> call, Object[] args) {
      call = callAdapter.adapt(call);
      ....
 }

这意味着callAdapter会去包装OkhttpCall,那么这个callAdapter是来自哪里的,追本朔源,它其实在Retrofit中的build会去添加defaultCallAdapterFactories,这个方法里就调用了DefaultCallAdapterFactory,真正的线程切换就在这里

 Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }     
// Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

这个默认的defaultCallAdapterFactories会传入平台的defaultCallbackExecutor(),由于我们平台是Android,所以它里面存放的就是主线程的Executor,它里面就是一个Handler

    static final class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());
​
      @Override
      public void execute(Runnable r) {
        handler.post(r);
      }
    }

到这来看下DefaultCallAdapterFactory中的enqueue(代理模式+装饰模式), 这里面使用了一个代理类delegate,它其实就是Retrofit中的OkhttpCall,最终请求结果完成后使用callbackExecutor.execute()将线程变为主线程,最终又回到了MainThreadExecutor当中

callbackExecutor.execute(
                  () -> {
                    if (delegate.isCanceled()) {
                      // Emulate OkHttp's behavior of throwing/delivering an IOException on
                      // cancellation.
                      callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                    } else {
                      callback.onResponse(ExecutorCallbackCall.this, response);
                    }
                  });

所以总的来说,这其实是一个层层叠加的过程,Retrofit的线程切换原理本质上就是Handler消息机制;到这关于数据适配和线程切换的回答就告一段落了,有很多细节的东西没有提到,有时间的话,需要自己去补充,用一张草图来展示下Retrofit对它们进行的封装

最后这里整理了一套比这上面更全面的 OKhttp 源码解析的学习文档,供给大家进行学习参考:https://qr21.cn/CaZQLo?BIZ=ECOMMERCE

更多Android 知识点归整

Android 性能调优系列https://0a.fit/dNHYY

Android 车载学习指南https://0a.fit/jdVoy

Android Framework核心知识点笔记https://0a.fit/acnLL

Android 音视频学习笔记https://0a.fit/BzPVh

Jetpack全家桶(含Compose)https://0a.fit/GQJSl

Kotlin 入门到精进https://0a.fit/kdfWR

Flutter 基础到进阶实战https://0a.fit/xvcHV

Android 八大知识体系https://0a.fit/mieWJ

Android 中高级面试题锦https://0a.fit/YXwVq

后续如有新知识点,将会持续更新,尽请期待……

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

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

相关文章

[附源码]SSM计算机毕业设计校园新闻管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

SpringBoot、EasyPoi、Echarts 实现文档导入、出、图表显示 (饼状图、柱状图) 保姆级教程

一、介绍环境 EasyPOI: 现在我们就来介绍下EasyPoi,首先感谢EasyPoi 的开发者​。EasyPoi开源 easypoi 是为了让开发者快速的实现excel&#xff0c;word,pdf的导入导出&#xff0c;基于Apache poi基础上的一个工具包。easypoi教程 Echarts: …

c++ - 第15节 - 二叉树进阶

1. 二叉搜索树 1.1.二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节…

一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接原因分析

背景 我们线上有一个 dubbo 的服务&#xff0c;出现大量的 CLOSE_WAIT 状态的连接&#xff0c;这些 CLOSE_WAIT 的连接出现以后不会消失&#xff0c;这就有点意思了&#xff0c;于是做了一下分析记录如下。 首先从 TCP 的角度看一下 CLOSE_WAIT CLOSE_WAIT 状态出现在被动关闭…

数据分析之金融数据分析

一 前言 金融业是一个持续发展的行业&#xff0c;金融业正在使用数据分析进行金融&#xff0c;以最大程度地减少管理各种金融活动所需的精力和时间。这些公司正在利用数据分析和机器学习原理的力量。这有助于他们发现金融行业各个领域所需的进步&#xff0c;以重塑其业务战略。…

虹科分享 | 网络仿真器 | 预测云中对象存储系统的实际性能

对象存储是一种在云中存储非结构化数据的方法&#xff0c;从理论上讲&#xff0c;它使得以其原始格式存储几乎无限量的数据成为可能。在这种存储架构中&#xff0c;数据被作为对象进行管理&#xff0c;而传统的系统则将数据作为块或分层文件进行处理。对象存储可以在内部使用&a…

一条Select语句在MySQL-Server层的执行过程

select customer_id,first_name,last_name from customer where customer_id14;先连接到数据库&#xff0c;连接器 负责跟客户端建立连接、获取权限、维持和管理连接。 客户端再次发送请求&#xff0c;就会使用同一个连接&#xff0c;客户端如果长时间没动静&#xff0c;就会断…

用R Shiny生态快速搭建交互Web网页APP应用

什么是Shiny&#xff1f; Shiny包可以快速搭建基于R的交互网页应用。对于web的交互&#xff0c;之前已经有一些相关的包&#xff0c;不过都需要开发者熟悉网页编程语言&#xff08;html,CSS,JS&#xff09;。最近我们被客户要求撰写关于R Shiny的研究报告&#xff0c;包括一些…

使用 Huggingface Trainer 对自定义数据集进行文本分类

文本分类是一项常见的 NLP 任务&#xff0c;它根据文本的内容定义文本的类型、流派或主题。Huggingface&#x1f917; Transformers 提供 API 和工具来轻松下载和训练最先进的预训练模型。Huggingface Transformers 支持 PyTorch、TensorFlow 和 JAX 之间的框架互操作性。模型还…

JAVA学习-java基础讲义01

java基础讲义一 java语言1.1 java语言介绍1.1.1 什么是java1.1.2 java之父1.1.3 java语言发展史1.2 java语言的特点二 java环境搭建相关2.1 Java环境介绍2.1.1 虚拟机介绍2.1.2 JVM介绍2.2 Java跨平台2.2.1 跨平台2.2.2 跨平台原理2.3 java运行过程2.4 JDK、JRE、JVM关系图2.4.…

JaVers:自动化数据审计

在开发应用程序时&#xff0c;我们经常需要存储有关数据如何随时间变化的信息。此信息可用于更轻松地调试应用程序并满足设计要求。在本文中&#xff0c;我们将讨论 JaVers 工具&#xff0c;该工具允许您通过记录数据库实体状态的更改来自动执行此过程。 Javers如何工作&#x…

RT-thread lts-v3.1.x版本,GD32F450以太网,上电之后有一定概率ping不通问题处理。

先给结论 官方驱动没有按照GD32F4XX手册要求&#xff0c;等待ENET_DMA_CTL第20bit清0后再写 synopsys_emac.c 文件&#xff0c;void EMAC_FlushTransmitFIFO(struct rt_synopsys_eth * ETHERNET_MAC)函数&#xff0c;增加一句判断即可解决。 /*** Clears the ETHERNET transm…

Kotlin高仿微信-第4篇-主页-消息

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点&#xff0c;包括&#xff1a;注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

Android Studio / IDEA 调试金手指:live template自动打印方法名以及所有变量

ctrl alt s 搜设置&#xff0c;template&#xff0c;结果是在 live template 区域设置代码模板的&#xff0c;不知这功能和直播有何关系&#xff0c;live stream&#xff1f; live template 就是自动完成一段代码。比如输入 fori&#xff0c;然后ctrl空格补全循环体&#xf…

Apache-DButils以及Druid(德鲁伊) 多表连接查询的解决方案:两种

Apache-DButils以及Druid(德鲁伊) 多表连接查询的问题 每博一文案 张爱玲说&#xff0c;于千万人之中&#xff0c;遇到你所要遇到的人&#xff0c;于千万年之中&#xff0c;时间的无涯的荒野里&#xff0c;没有 早一步&#xff0c;也没有晚一步&#xff0c;刚巧赶上了。 人生海…

iPhone开机密码什么时候会用到?忘记了怎么办?

iPhone的开机密码也是屏幕解锁密码&#xff0c;它的作用还是很重要的。一般用在&#xff1a; 解锁手机手机重启后解锁手机系统更新后第一次解锁手机手机连接电脑需要信任设备Face ID或指纹解锁失败三次后连接Apple Watch后第一次解锁手机 虽然我们现在经常使用其他的解锁方式&…

马斯克特斯拉内部邮件火了:痛恨开会,少说黑话

金磊 羿阁 发自 凹非寺量子位 | 公众号 QbitAI马斯克给员工的一封内部邮件火了。鼓励员工拒绝开会、公司规定不合理可以不遵守……俨然一个为员工着想的好老板。一开始人们还奇怪马斯克的画风怎么变这么快&#xff0c;后来才发现原来这是他6年前写的。对象也不是推特员工&#…

BCN点击试剂:1516551-46-4,BCN-succinimidylester,BCN NHS

●中文名&#xff1a;丙烷环辛炔-活性酯&#xff0c;BCN-琥珀酰亚胺酯 ●英文名&#xff1a;BCN-NHS&#xff0c; BCN-NHS 酯&#xff0c;BCN-活性酯&#xff0c;BCN-succinimidylester 【产品理化指标】&#xff1a; CAS号&#xff1a; 1516551-46-4 分子式&#xff1a;C15H17…

58 - 类模板的概念和意义

---- 整理自狄泰软件唐佐林老师课程 1. 思考 在C中是否能够将泛型的思想应用于类&#xff1f; 1.1 类模板 一些类主要用于存储和组织数据元素类中数据组织的方式和数据元素的具体类型无关 如&#xff1a;数组类、链表类、Stack类、Queue类&#xff0c;等 C中模板的思想应用于…

【LeetCode】No.103. Binary Tree Zigzag Level Order Traversal -- Java Version

题目链接&#xff1a;https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/ 1. 题目介绍&#xff08;Binary Tree Zigzag Level Order Traversal&#xff09; Given the root of a binary tree, return the zigzag level order traversal of its nodes’…