Retrofit原理-程序员宅基地

技术标签: java  http  https  Android  

一、前言

Retrofit是基于OkHttp封装的一个框架,使其更加方便使用。目前也是使用较多的网络框架,这里对其原理进行简单记录。这里需要注意的是Retrofit框架只是对OkHttp的二次封装,实际的网络请求依然还是用OkHttp进行的。因为OkHttp的网络请求只处理网络请求,对于实际开发中涉及的其余逻辑并不关心,比如线程切换,数据解析等等。另外使用Retrofit还可以对api的接口进行良好的管理,因此在此基础上研发了Retrofit框架。

二、简单使用

​ 这里简单记录一个使用例子,更多的例子可以查看官方文档和demo。

添加依赖

    implementation 'com.squareup.okhttp3:okhttp:4.9.3'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

使用方式:

interface ServerApi {
    
    @POST("s?cl=3&tn=baidutop10&fr=top1000&wd=战火中的中国驻乌克兰大使馆&rsv_idx=2&rsv_dl=fyb_n_homepage&sa=fyb_n_homepage&hisfilter=1")
    @FormUrlEncoded
    fun login(@Field("id") userId: String): Call<String>
  
    @GET("s?cl=3&tn=baidutop10&fr=top1000&wd=战火中的中国驻乌克兰大使馆&rsv_idx=2&rsv_dl=fyb_n_homepage&sa=fyb_n_homepage&hisfilter=1")
    fun loginObserver(): Observable<String>
}
class MainActivity : AppCompatActivity() {
      
  private val httpUrl = "http://www.baidu.com"
	private fun startRetrofitNet(){
    
        val retrofit = Retrofit.Builder()
            .baseUrl(httpUrl)
            .addConverterFactory(ScalarsConverterFactory.create())//添加gson解析
            .addConverterFactory(GsonConverterFactory.create())//添加字符串解析
      			//.addCallAdapterFactory() 添加适配器转换
            .build()
        val serverApi = retrofit.create(ServerApi::class.java)
        val loginCall = serverApi.login("123")
//        loginCall.execute()//同步方法
        loginCall.enqueue(object : Callback<String>{
    
            override fun onResponse(call: Call<String>, response: Response<String>) {
    
                val result = response.body()
                Log.e("YM--->","获取的网络内容:${
      result}")
                Toast.makeText(this@MainActivity,"--->",Toast.LENGTH_SHORT).show()
            }

            override fun onFailure(call: Call<String>, t: Throwable) {
    
            }
        })
    }
}

三、整体结构

​ 从上文的使用方式可以看到,Retrofit的使用方式和OkHttp有很多不同的地方:

  1. 网络请求采用RESTful风格和注解的方式进行配置。这种类似于配置文件式的写法使网络请求更好的维护
  2. 在进行网络请求中不像OkHttp那样涉及到其他部分,比如Request。这里都可以使用Retrofit创建的对象进行链式调用进行请求。这是一种基于装饰模式的写法(跟建造者模式不是一回事,区别在于一个是根据多个参数不一样进行动态配置)。
  3. 在创建过程中可以通过配置ConverterFactory进行数据转换。因为有时候传入的数据格式不支持,比如传入一个对象,这时候可以使用转换器转换成字符串,或者将一个字符串在返回的时候转换为一个对象。
  4. 在创建对象的时候通过配置CallAdapterFactoryOkHttpCall对象进行适配。在OkHttpCall是用来对RequestResponse进行处理的。这里可以看上述接口定义了两个不同的返回值,一个是Call<String>,另外一个是RxJava的Observable<String>。使用RxJava的话就可以调用不同的特性,这里就是使用CallAdapterFactory进行转换的。也可以转换成kotlin这些使用方式。Retrofit通过重新处理Call将返回值根据实现的不同的CallAdapterFactory做不同的转换,提高了强大的拓展性。
  5. 可以看到上文的响应值里面直接输出了Toast。要知道请求是子线程的,Toast显示是在主线程的,所以这里自己做了线程切换。也是一个很大的不同点。这个线程切换是在Retrofit默认的DefaultCallAdapterFactory中做的,其它适配器里面没写的话是没有线程切换的。
  6. 还有一个重要的不同点从使用上看的话不好直接看出来,那就是定义接口的实例对象都被缓存起来了,防止多次调用同一个对象都去创建新的接口对象,其它的对象也有类似的操作。这里减少了部分内存占用,提高了部分内存性能(因为这个缓存是定义在Retrofit类里面的,所以实例对象不一样的话等于没有缓存。)。

上面就是跟OkHttp的一些区别,那么对于Retrofit来说,主要有以下几部分组成:

  • Retrofit
  • ConverterFactory
  • CallAdapterFactory
  • Call<T>
  • 定义的网络请求接口类

对于他们的主要功能上文已经解释过了,这里就不再进行解释,下面从最基本的网络请求来记录Retrofit的运行流程。

四、运行流程

​ 使用Retrofit的时候首先要通过建造者模式创建一个实例对象,最终实例对象还是调用的Retrofit的构造函数,所以这里看下构造函数即可知道有哪些参数:

  Retrofit(
      okhttp3.Call.Factory callFactory,
      HttpUrl baseUrl,
      List<Converter.Factory> converterFactories,
      List<CallAdapter.Factory> callAdapterFactories,
      @Nullable Executor callbackExecutor,
      boolean validateEagerly) {
    
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
    this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }

这里简单说下这几个参数的含义:

  • callFactory: 这个是okhttp的一个接口,OkHttpClient就是实现了这个接口,所以可以传入自定义的OkHttpClient
  • baseUrl:故名思义,就是一个网络请求的基础域名
  • converterFactories:数据转换器集合
  • callAdapterFactories: 适配器集合
  • callbackExecutor: 一个用来调度返回值的回调的线程池,默认是主线程
  • validateEagerly: 这个用的不多,源码的意思是检查配置是否有效。通过源码知道定义的接口类是通过动态代理实现具体类的,但是里面的接口需要在调用的时候才进行检查,而这个参数的配置会提前进行检查,即不通过动态代理就可以执行。

当创建完Retrofit的实例化对象后,需要通过Retrofit::create(final Class<T> service)来创建接口的实例化对象。这里看下这个对象是如何创建的。

public final class Retrofit {
      
  ... 
  private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
  ...
	public <T> T create(final Class<T> service) {
    
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {
    service},
            new InvocationHandler() {
    
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable 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);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }
  ...
    ServiceMethod<?> loadServiceMethod(Method method) {
    
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
    
      result = serviceMethodCache.get(method);
      if (result == null) {
    
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }
  ...
}

这里可以看到是使用动态代理的方式创建的,如果是系统默认函数就执行默认函数,如果是自己写的,就执行ServiceMethod<?> loadServiceMethod(Method method)函数。然后可以看到通过ServiceMethod.parseAnnotations(this, method);得到结果,然后添加进缓存里面去。这里看下这个写法:

abstract class ServiceMethod<T> {
    
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
    
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
    
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

这里拿到相关方法后进行解析注解然后得到相对应的实体,然后在动态代理里面调用loadServiceMethod(method).invoke(args);invoke(args)函数就是这里面的函数。看下具体实现。

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

通过这里就知道在具体实现的适配器里面将数据返回,这个数据结构类型由具体的适配器进行构造。默认的就返回默认的Call对象,RxJava就返回Observer<T>对象

五、参考链接

  1. Retrofit
  2. 设计模式-代理模式:
  3. OkHttp原理
  4. restful接口和普通接口有啥区别_RESTful接口
  5. Retrofit 2.0中的validateEagerly()方法(beta 2)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Mr_Tony/article/details/123203634

智能推荐

docker删除重装,以及极其重要的/etc/docker/key.json文件-程序员宅基地

文章浏览阅读8.9k次,点赞2次,收藏2次。先说以下我为何要删除docker的原因吧:因为我感觉Docker Hub有点慢,就配置了阿里云的镜像加速器,可是按阿里云的官方配置完后我怎么感觉它更慢了,对比昨天配置阿里的maven镜像仓库后快到起飞的速度,我认为是此次配置没有生效。多次确认新加入的/etc/docker/demon.json文件无误后又多次systemctl了未果,随即我怀疑阿里给出的以下方案中的“修改”的/etc/dock...

空间物理——概述_空间物理概论-程序员宅基地

文章浏览阅读1.9k次,点赞3次,收藏4次。文章目录空间物理的研究对象太阳风能量向地球传输的三种方式和所需要的时间太阳内部结构、太阳活动太阳内部结构太阳活动太阳风速度从太阳表面到地球轨道附近变化参考空间物理的研究对象大气层:10KM以上,分成平流层、中层、低热层、热层、逃逸层电离层:60-90KM以上,一直到1000KM左右,部分电离气体,中性成风碰撞的影响不可忽略地球磁层:完全电离的气体,1000KM以上,可忽略碰撞,有太阳风和..._空间物理概论

BQ4050学习笔记(二)-程序员宅基地

文章浏览阅读2.9k次,点赞5次,收藏25次。BQ4050学习笔记(二)永久失效:如果发⽣严重故障,该设备可以永久禁⽤电池组。永久故障检查(IFC 和 DFW 除外)可以通过设置Settings:Enabled PFA、 Settings:Enabled PF B、 Settings:Enabled PF C 和Settings:Enabled PF D 中的相应位单独启⽤或禁⽤。所有永久在设置ManufacturingStatus()[PF]之前,故障检查(IFC 和 DFW 除外)被禁⽤。当任何PFStatus()位置位时,器件进⼊ PER_bq4050

SpringCloudAlibaba-Nacos注册中心的使用_spring-cloud-alibaba 使用nacos 2.1版本-程序员宅基地

文章浏览阅读152次。第二步:填写配置文件参数,这里定义了一个名字为application-user-dev.yaml的配置,使用的是YAML格式。DataID : 非常重要,可以看做是配置的文件的名字,在程序中拉取配置文件的时候需要指定Data ID。如果不使用默认的public命名空间,那么需要指定namespace配置为要使用的命名空间的Id值。第一步:打开Nacos监控面板,进入配置列表,新增一个user服务的配置文件。进入配置列表 ,切换到新建立的命名空间,创建配置文件。修改Nacos,添加命名空间。_spring-cloud-alibaba 使用nacos 2.1版本

CVE-2023-21716 Microsoft Word远程代码执行漏洞Poc_cve-2023-21716复现-程序员宅基地

文章浏览阅读293次。受害者打开python代码生成的RTF文件,RTF解析器解析恶意代码,触发堆溢出,Microsoft Word会闪退,用户其它Word中未保存的内容会丢失。_cve-2023-21716复现

c语言程序设计让a变成A,c语言程序设计a期末模拟试题.docx-程序员宅基地

文章浏览阅读451次。文件排版存档编号:[UYTR-OUPT28-KBNTL98-UYNN208]文件排版存档编号:[UYTR-OUPT28-KBNTL98-UYNN208]C语言程序设计A期末模拟试题C语言程序设计A期末模拟试题一一、单项选择题(每小题2分,共20分)由C++目标文件连接而成的可执行文件的缺省扩展名为( )。A. cpp B. exe C. obj D. li..._c语言如何将a转换成a

随便推点

利用beef和msf实现session远程命令_msf如何切换一个session-程序员宅基地

文章浏览阅读534次。笔记beef启动 beef 的方法msfbeef工具目录 /usr/share/beef-xss配置文件 config.yaml启动 beef 的方法1.beef-xss2./usr/share/beef-xss/beef(使用前需要修改默认的用户名称和密码)Web 管理界面 http://127.0.0.1:3000/ui/panelShellcode http://127.0.0.1:3000/hook.js测试页面 http://127.0.0.1:3000/demos/butch_msf如何切换一个session

python判断丑数_丑数问题及变种小结-程序员宅基地

文章浏览阅读503次。丑数问题及变种小结声明1 判断丑数因子只包含2,3,5的数称为丑数(Ugly Number),习惯上把1当作第一个丑数面试lintcode 517 ugly numbersegmentfault剑指offer 面试题34 丑数数组解法:参考剑指offer,将待判断目标依次连续整除2,3,5,若是最后获得1,证实该数为丑数;优化/*** 依次整除2,3,5判断(2,3,5顺序判断时间最优)* htt..._编写python来证明一个数是丑数

python自动化测试实战 —— WebDriver API的使用_python webdriver api-程序员宅基地

文章浏览阅读1.9k次,点赞30次,收藏11次。Selenium 简介: WebDriver是Selenium Tool套件中最重要的组件。Selenium 2.0之后已经将Selenium和WebDriver进行合并,作为一个更简单、简洁、有利于维护的API提供给测试人员使用。 它提供了一套标准的接口,可以用多种编程语言调用,并且和浏览器进行交互。 WebDriver可以对浏览器进行控制,包括输入URL,点击按钮,填写表单,滚动页面,甚至是执行JavaScript代码。同时,它也能够获取网页中的信息,如文本,标签,属_python webdriver api

Nodejs crypto模块公钥加密私钥解密探索_crypto.publicencrypt-程序员宅基地

文章浏览阅读1w次。1.什么是公钥加密私钥解密 简单一点来说一般加密解密都用的是同一个秘钥或者根本不用,而这里采用的是加密用一个秘钥,解密用另一个秘钥且能解密成功.这就属于不对称加密解密算法的一种了.2.公钥秘钥的生成 由于这种加密方案,公钥秘钥是成对的,所以需要一些工具生成 利用 openssl 生成公钥私钥 生成公钥: openssl genrsa -out rsa_private_key...._crypto.publicencrypt

Maven简明教程(5)---依赖关系(实例篇)_依赖关系怎么写-程序员宅基地

文章浏览阅读1.7k次。[工欲善其事,必先利其器]上文中,我们简单介绍了依赖关系的基本理论与配置方式。但是由于这个知识点在我们日后的开发过程中会经常使用到,因此,我们在本篇中通过演示实例来说明依赖关系,请各位看官一定跟着步骤,亲自尝试一番。仔细观察通过这种方式对我们程序架构造成的影响。特别的,这里以一份已经调试完成的工程为例,因此,与前文说的工程命名不一致,敬请谅解。准备工作:a.操作系统:win7 x6_依赖关系怎么写

2017多校联合第五场1006/hdu6090Rikka with Graph(思维公式)-程序员宅基地

文章浏览阅读343次。Rikka with GraphTime Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 592 Accepted Submission(s): 353Problem DescriptionAs we know,