Retrofit 源码分析

494 阅读8分钟

1、简介

Retrofit不是一个网络库,它只是网络请求的搬运工,封装了简化了请求参数处理,结果处理;这个简化,让使用贼爽;虽然它只是一个简化的工具,但它的源码还是很有艺术的,也给我们再简化操作时提供了一个方向,注解技术;

利用注解封装了各种请求Request,利用配置对流解析(常见gson格式流解析为类);那么它的关键就是利用注解收集请求信息;那么下面我们先介绍个大概脉络,然后介绍各种注解的处理

之前已经写过一篇了,但是作者一不小心就把那篇文章删除了;不过这件事,也让我自己反省了,文章结构内容,写的不是很好;在这里向大家道个谦,也在这里再次强调,我分享文章的想要的重点是:基础知识;我的计划内容分三步:

  1. 整体框架
  2. 用到了哪些知识做到了哪些事情
  3. 框架的一些体悟

2、整体框架

先简单说下使用方式吧,定义一个接口

public interface LaunRequest {
    @GET("https://xxxxx")
    Call<SwitchConfig> getSwitchConfig(@QueryMap Map<String, String> params);
}

去请求:

        LaunRequest api = RetrofitNetwork.retrofit.create(LaunRequest.class);
        final Call<SwitchConfig> call = api.getSwitchConfig(queryParams);
        call.enqueue(callback);

对应上述过程的整个流程图如下:

Retrofit是请求入口,也是配置入口

主要使用了构建者、工厂、动态代理、适配器模式;涉及主要灵魂拷问的方面有:

  1. 动态代理实现
  2. 网络请求注解的处理
  3. 网络响应结果处理

有些细小的基础知识,中间穿插就说了,如果没有,那就是我也不曾注意;下面就接着灵魂拷问,深入细节

3、基础技术

3.1 动态代理

代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

动态代理它可以直接给某一个目标(被代理 对象)对象(实现了某个或者某些接口)生成一个代理对象,而不需要代理类存在。直接通过反射生成了一个代理对象;

Java.lang.reflect.Proxy类可以直接生成一个代理对象,使用静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

  • ClassLoader loader 代理对象的类加载器 一般使用被代理对象的类加载器
  • Class<?>[] interfaces 代理对象的要实现的接口 一般使用的被代理对象实现的接口
  • InvocationHandler h (接口)执行处理类;调用代理类的任何方法时,执行invoke方法

InvocationHandler.invoke(Object proxy, Method method, Object[] args)

  • Object proxy:代理对象(慎用)
  • Method method:当前执行的方法
  • Object[] args:当前执行的方法运行时传递过来的参数
  • 返回值:当前方法执行的返回值

在Retrofit用动态代理做了一个通用流程,返回的结果可以进行实际的网络请求的对象;默认返回内容是OkHttpCall 的一个实例,这个类是对请求的封装;

3.2 注解收集

注解是什么? 大家可以到百度上搜索以下,也可以到我的这篇文章上,看看里面关于注解的基本介绍

那么Retrofit规定了哪些注解呢?我细数了下20多种

Retrofit注解是从ServiceMethod.parseAnnotations方法开始的,实际在RequestFactory.Build中的build方法进行的;这里分为两种,方法注解、参数注解

    this.methodAnnotations = method.getAnnotations();  // 方法注解
    this.parameterTypes = method.getGenericParameterTypes(); // 参数类型
    this.parameterAnnotationsArray = method.getParameterAnnotations(); // 参数注解,每一个参数可以对应多个注解,结果是个二维数组

RequestFactory.Build类完成了主要的注解初始化

  1. parseHttpMethodAndPath解析请求行信息:方法、主机、路径、请求query信息,parseHeaders解析请求头值对
  2. parseParameter解析参数注解需要处理者ParameterHandler实例,以及参数key值;(其value值在请求时处理生成)

3.2.1 请求行

请求行解析部分使用注解HTTP,或者DELETE、GET、HEAD、PATCH、POST、OPTIONS等常见http请求方法;只有这些注解要确保path值中仅含有URI中的path部分

注意:uri的file部分有path部分和query部分组成;

具体请求方法注解,值即是uri中的file内容,如下:

@POST("/foo/bar/")

其等同于 HTTP注解

@HTTP(method = "POST", path = "/foo/bar", hasBody = true)

这几个请求方法中只有POST、PUT、PATCH,默认请求体是由的,HTTP可以定制;

3.2.2 请求头

请求头内容由方法注解HEADS和参数HEAD注解组成

HEADS 是请求头数组,格式如下:

@Headers({"ping: pong", "kit: kat"})
  • 内容是字符串数组,格式:{}包裹所有信息,“,”分隔
  • 每个部分的格式由Retrofit自定义,以:分隔,冒号有无空格均可

HEAD 是参数主机,参数注解比较复杂,首先根据注解类型和参数类型,找到相应的注解处理类ParameterHandler,并存储key信息,在请求的时候生成value信息;HEAD的数据解析,由ParameterHandler.Header来处理

参数注解只能一个参数一个注解,多个注解Retrofit直接报错

3.2.3 参数注解

3.2.1和3.2.2中的主机除了HEAD,其它都是方法注解;参数注解,就是找到注解处理类,和key值即可,在请求的时候解析value信息

ParameterHandler类 抽象类,这个类的实现没有提供注入,只能使用默认的;实现类均在ParameterHandler.java文件中;抽象方法

abstract void apply(RequestBuilder builder, @Nullable T value)

apply方法调用时,解析value值,并生成信息设置到RequestBuilder中,解析value由Converter来处理;有些value不需要转换,比如url参数注解,其它需要转换的基本使用的BuiltInConverters.ToStringConverter,使value转换为字符串

参数注解结果,在哪一步应用呢?

这个步骤在eqeue方法或者execute方法,也就是最后一步同步/异步执行请求的使用,创建实际请求的okhttp3.Call对象时,由RequestFactory的create方法创建okhttp3.Request依据参数值处理的

3.2.4 一些特殊的注解

Streaming方法注解,其实并不对应请求的任何信息,对应相应结果内容的处理策略;BuiltInConverters类可以看到,由Streaming注解且响应内容是ResponseBody时,请求的响应体不做任何处理,无的使用Okio进行了封装;默认使用的okhttp请求,其流已经使用了okio的流封装,我从这点来看,没有什么鸟用啊

3.3 适配器模式

适配器模式是作为两个不兼容的接口之间的桥梁。目的是将一个类的接口转换成客户希望的另外一个接口。

在这里有一下的适配;这些适配器都是由工厂模式来创建的

  • 注解转换为OkHttp中的请求对象

  • OkHttp响应结果转换为Retrofit结果

  • 回调处理

3.3.1 请求转换

请求转化主要涉及注解解析的过程、请求体的转换过程;涉及的工厂类

  • Converter.Factory:请求体转换,常见使用GsonConverterFactory
  • RequestFactory:完成参数解析转换http请求报文

3.3.2 响应转换

涉及响应体转换工厂类Converter.Factory,常见使用GsonConverterFactory

3.3.3 OkHttpCall转换

工厂类CallAdapter.Factory来创建Adapter对请求OkHttpCall对象进行包装处理;一般来说,我们不进行任何设置;默认实现类DefaultCallAdapterFactory、CompletableFutureCallAdapterFactory;

  • DefaultCallAdapterFactory: 通过Retrofit设置线程池,决定是否使用线程池回调请求结果;对OkHttpCall使用了静态代理模式,对请求回调进行了线程处理
  • CompletableFutureCallAdapterFactory:对Okhttp响应内容使用CompletableFuture进行包装,可以执行异步操作;使用此工厂的前提条件:返回结果必须是CompletableFuture的泛型,而且Dalvik虚拟机且sdk>=24 或者非Dalvik、RoboVM虚拟机

总结

这是第一次尝试这种思路写作;写的时候,发现自己对Retrofit用的有点陌生;有些竟然还可以这么用;另外对有些技术不是很扎实,比如反射,特别是泛型相关反射处理、另一种反射处理方式MethodHandles;这些基础涉及了JVM的理解;所以我现在对他们认知和使用,都来源于他人博客、书籍;后续也会向这方面学习写作。

一些注解的细节也没有讲,但是解析的地方是提供了,如果需求有需要的,可以对那些注解再研究下,相信大部分开发,半个小时内,就能搞定了。

最后谢谢大家的阅读,并十分期望大家对这种写法提供宝贵的意见,作者会吸纳改进写作;其它常见使用的第三方系列,有了解的,可以进入

  1. Gson库源码分析 (2.8.5版本)
  2. Okio库源码分析 (1.7.2版本)
  3. Retrofit库源码分析 (2.9.0版本)
  4. 常用的图片下载库 Picasso (2.71828)

巧妇难为无米之炊,基础技术才能构建优美的代码;作者希望在余后的生活中,对基础技术;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!