征服面试官:Retrofit 原理篇 掌握这篇面试题汇总,吊打面试官!
Carson带你学Android:这是一份详细的 Retrofit使用教程(含实例讲解)
Carson带你学Android:网络请求库Retrofit源码分析
目录
Okhttp使用中的缺陷:
1)用户网络请求的接口配置繁琐,尤其是需要配置复杂请求body,请求头,参数的时候;
2)数据解析过程需要用户手动拿到responsebody进行解析,不能复用;
3)无法适配自动进行线程的切换;
4)万一存在网络嵌套网络请求就会陷入“回调陷阱”
一、Retrofit是什么
准确来说,Retrofit是一个RESTful的HTTP网络请求框架的封装。
原因:网络请求的本质上是OkHttp完成的,而Retrofit仅负责网络请求接口的封装。
- App应用程序通过Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数、Header、Url等信息,之后由OkHttp完成后续的请求操作。
- 在服务端返回数据 之后,OkHttp将原始的结果交给Retrofit,Retrofit根据用户的需求对结果进行解析。
Okhttp的请求流程:
Retrofit创建实例详情:
Retrofit封装的点:
1)okhttp创建的是OkhttpClient,然而retrofit创建的是Retrofit实例;
2)okhttp构建Request的方案,retrofit是通过注解来进行的适配;
3)配置Call的过程中,retrofit是利用Adapter适配的Okhttp的call,为call的适配提供了多样性;
4)相对okhttp,retrofit对responseBody进行自动的Gson解析,提供了可复用、易拓展的数据解析方案;
5)相对okhttp,retrofit会自动的完成线程的切换。
Retrofit设计的任务:
主要解决以下四件事:
请求前:
1)统一配置网络请求头
2)一致适配请求request
结果返回:
3)数据适配
4)线程切换
二、Retrofit中的注解
1. 请求方法类
-
GET
get请求 -
POST
post请求 -
PUT请求
put请求 -
DELETE
delete请求 -
PATCH
patch请求,该请求是对put请求的补充,用于更新局部资源。 -
HEAD
head请求 -
OPTIONS
option请求 -
HTTP
通用注解,可以替换以上所有的注解,其拥有method,path,hasBody三个属性。
2. 参数类
分类 | 名称 | 备注 |
---|---|---|
作用于方法 | Headers | 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在 |
作用于方法参数(形参) | Header | 作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头 |
请求参数 | Body | 多用于post请求发送非表单数据,比如想要以post方式传递json格式数据 |
请求参数 | Field | 多用于post请求中表单字段,Field和FieldMap需要FormUrlEncoded结合使用 |
请求参数 | FieldMap | 表单字段,与Field、FormUrlEncoded配合;接受Map<String, String>类型,非String类型会调用toString()方法 |
请求参数 | Part | 用于表单字段,Part和PartMap与MultiPart注解结合使用,适合文件上传的情况 |
请求参数 | PartMap | 表单字段,与Part配合,适合文件上传情况,默认接受Map<String, RequestBody>类型,非RequestBody会通过Converter转换 |
请求参数 | HeaderMap | 用于URL,添加请求头 |
请求参数 | Path | 用于url中的占位符 |
请求参数 | Query | 用于Get中指定参数 |
请求参数 | QueryMap | 和Query使用类似 |
请求参数 | Url | 指定请求路径 |
3. 标记类
分类 | 名称 | 备注 |
---|---|---|
表单请求 | FormUrlEncoded | 表示请求实体是一个Form表单,每个键值对需要使用@Field注解 |
请求参数 | Multipart | 表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于有文件上传的场景 |
标记 | Streaming | 表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在下载大文件特别有用 |
三、注解的基础知识
1. 什么是注解
-
注解的定义
Java 注解用于为Java代码提供元数据,作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。 -
注解即标签
如果把代码想象成一个具有生命的个体,注解就是给这些代码的某些个体打标签。 -
在Java中的定义
注解通过@interface关键字进行定义
public @interface SimpleAnnotation {
}
2. 注解的属性
-
注解的属性
也叫做成员变量,注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。 -
属性的应用
无属性值
只有一个value属性值
多个属性值
属性值的默认值 -
可用的属性类型
byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组一维[]。
3. 注解的提取
注解通过反射获取。
- 首先通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解;
- 然后通过 getAnnotation() 方法来获取Annotation对象;
- 或者是 getAnnotations() 方法,返回注解到这个元素上的所有注解。
4. 元注解是什么
-
元注解的定义
可以注解到注解上的注解
元注解是一种基本注解,但是它能够应用到其他的注解上面 -
元注解即特殊标签
它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的 -
五种元注解
@Retention
@Target
@Inherited
@Repeatable
@Documented
5. 注解的生命周期
定义该注解的生命周期 ---- @Retention
- RetentionPolicy.SOURCE: 在源文件中有效,被编译器丢弃,用来提示开发者;
- RetentionPolicy.CLASS:在class文件有效,被虚拟机丢弃,用于自动生成代码;
- RetentionPolicy.RUNTIME:运行时有效,常用于自动注入
6. 注解的运用场景限定
运用的场景限定 ---- @Target
- Target是目标的以上,@Target指定了注解运用的地方
- 类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。
① ElementType.ANNOTATION_TYPE: 可以给一个注解进行注解
② ElementType.CONSTRUCTOR:可以给构造方法进行注解
③ ElementType.FIELD: 可以给属性进行注解
④ ElementType.LOCAL_VARIABLE:可以给局部变量进行注解
⑤ ELementType.METHOD:可以给方法进行注解
⑥ ElementType.PACKAGE:可以给一个包进行注解
⑦ ElementType.PARAMETED:可以给一个方法内的参数进行注解
⑧ ElementType.TYPE:可以给一个类型进行注解,比如类、接口、枚举
7. 多重标签
Repeatable 自然是可重复的意思,@Repeatable 是 Java 1.8才加进来的新特性。
8. 注解的使用场景
- 提供信息给编译器:编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理:软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理:某些注解可以在程序运行的时候接受代码的提取
- 值得注意的是,注解不是代码本身的一部分
9. 注解的应用实例
- JUnit
- ButterKnife
- ARouter
- Glide
- Dagger2
- GreenDao
- Retrofit
四、Retrofit 提供的子系统
- serviceMethodCache (自定义的接口映射对象集合)
- baseUrl (网络请求的url地址)
- callFactory (网络请求工厂,默认为OKHttpCall)
- converterFactories (数据解析/转换器工厂的集合)
- callAdapterFactories (网络请求/Call适配器工厂的集合)
- callbackExecutor (回调方法执行器,是一个线程管理,其他它是一个handler,用于线程切换,Android平台默认为MainThreadExecutor)
OkhttpCall: 在invoke中,为网络call的封装
ServiceMethod
baseUrl:网络请求的url地址
callFactory:
callAdapter:明确正确的callAdapter(callAdapter的目的:Call<T> -->Java对象)
responseType:确定call接口返回值类型
responseCoverter:明确对应的数据转化器
parameterHandlers:对接口中的注解参数进行解析配置
使用Builder模型构建(把对象依赖的事件创建、零件的组装封装起来;以使客户很方便的获取一个复杂对象)
-
Platform
Retrofit中用来管理多平台的方法,支持Android、Java8。通过findPlatform获取对应的平台,同时也初始化了defaultCallAdapterFactory工厂。 -
ServiceMethod
接口映射的网络请求对象,通过动态代理,将自定义接口的标注转换为该对象,将标注及参数生成OkHttp所需的Request对象。Retrofit的create通过动态代理拦截,将每一个自定义接口转换成为一个ServiceMethod对象,并通过serviceMethodCache进行缓存。
五、call相关接口
-
CallAdapter
采用适配器模式,为Call增强功能 -
CallAdapter.Factory
CallAdapter的静态工厂,包含get的抽象方法,用于生产CallAdapter对象 -
ExecutorCallAdapterFactory
Android平台默认的CallAdapter工厂,get方法使用匿名内部类实现CallAdapter,返回ExecutorCallbackCall,实现了Call -
RxJavaCallAdapterFactory
Rxjava平台的CallAdapter工厂,get方法返回RxjavaCallAdapter对象 -
RxjavaCallAdapter
Rxjava平台的适配器,返回observable对象
CallAdapter种类
网络请求适配器 | Gradle依赖 |
---|---|
guava | com.squareup.retrofit2:adapter-guava:version |
java8 | com.squareup.retrofit2:adapter-java8:version |
rxjava | com.squareup.retrofit2:adapter-rxjava:version |
六、Converter相关接口
-
Converter
数据转换接口,采用策略模式为不同数据做不同的转换 -
Converter.Factory
数据解析器工厂,用于生产Converter实例 -
GsonConverterFactory
数据解析工厂实例,返回GsonResponseBodyConverter数据解析器 -
GsonResponseBodyConverter
Gson的数据解析器,将服务端返回的json对象转换成对应的java模型 -
Response
Retrofit网络请求响应的Response
Convert种类
Retrofit支持多种数据解析方式,使用时需要在Gradle添加依赖。
数据解析器 | Gradle依赖 |
---|---|
Gson | com.squareup.retrofit2:converter-gson:version |
Jackson | com.squareup.retrofit2:converter-jackson:version |
Simple XML | com.squareup.retrofit2:converter-simplexml:version |
Protobuf | com.squareup.retrofit2:converter-protobuf:version |
Moshi | com.squareup.retrofit2:converter-moshi:version |
Wire | com.squareup.retrofit2:converter-wire:version |
Scalars | com.squareup.retrofit2:converter-scalars:version |
七、Retrofit流程
八、Retrofit中的设计模式
1. 建造者模式
Retrofit对象的创建、ServiceMethod对象创建都使用Build模式,将复杂对象的创建和表示分离,调用者不需要知道复杂的创建过程,使用Build的相关方法进行配置创建对象。
当构造函数的参数大于4个,且存在可选参数的时候就可以使用建造者设计模式
2. 工厂方法模式
Retrofit创建时的callFactory,使用工厂方法设计模式,但是似乎并不打算支持其他的工厂。
返回不同的适配器 CallAdapter.Factory 采用工厂模式
3. 外观模式
整个Retrofit采用的是外观模式。统一的调用创建网络请求接口实例和网络请求参数配置的方法。
4. 动态代理模式
Subject:抽象主题
RealSubject:真实主题类,也称为被委托类、被代理类
Proxy:代理类,也称委托类,代理类
Client:客户端类
Retrofit里面使用了动态代理来创建网络请求接口实例,这个是Retrofit对用户使用来说最大的复用,其他的代码都是为了支撑这个动态代理给用户带来便捷性的。
5. 策略模式
使用了策略模式对serviceMethod对象进行网络请求参数配置,即通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络的url地址、网络请求执行器、网络请求适配器和数据转换器。
6. 装饰者模式
ExecutorCallbackCall使用装饰者模式来装饰callbackExecutor,用来完成线程的切换
7. 静态代理模式
ExecutorCallbackCall使用静态代理(委托)代理了Call进行网络请求,真正的网络请求由okhttpCall执行,然而okHttpCall不是自己执行,它是okhttp提供call给外界(Retrofit)使用的唯一门户,其实这个地方就是门面模式。
8. 适配器模式
ExecutorCallbackCall的被初始化是在ExecutorCallAdapterFactory里面通过适配器模式被创建的。
CallAdapter采用了适配器模式为创建访问Call接口提供服务。默认不添加Rxjava则使用默认的
ExecutorCallAdapterFactory将okhttp3.call转变为retrofit中的call,如果有Rxjava则将okhttp3.call转化为observable.
9.策略模式
Converter采用策略模式,对不同的结果采用不同的解析器进行解析。
Retrofit采用这些模式可以把OkHttp请求进一步的解耦,可以把职责分离的更加清晰,进一步简化了OkHttp的使用。
九、Retrofit封装的点
OKHttp使用中的缺陷:
1)用户网络请求的接口配置繁琐,尤其是需要配置复杂请求body,请求头,参数的时候;
2)数据解析过程需要用户手动拿到responsebody进行解析,不能复用;
3)无法适配自动进行线程的切换;
4)万一存在嵌套网络请求就会陷入“回调陷阱”。
- okhttp创建的是OKhttpClient,然而retrofit创建的是Retrofit实例;
- 构建蓝色的Request的方案,retrofit 是通过注解来进行的适配;
- 配置Call的过程中,retrofit是利用Adapter适配的Okhttp的Call,为call的适配提供了多样性;
- 相对okhttp,retrofit会对responseBody进行自动的Gson解析,提供了可复用、易拓展的数据解析方案;
- 相对okhttp,retrofit会自动的完成线程的切换。
十、Retrofit实例创建过程
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.wanandroid.com")
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
ICoinRankService iCoinRankService = retrofit.create(ICoinRankService.class);
- 成功建立一个Retrofit对象的标准:配置好Retrofit类里的成员变量
- baseUrl:网络请求的url地址
- callFactory:网络请求工厂
- callbackExecutor:回调方法执行器
- adapterFactories:网络请求适配器工厂的集合
- converterFactories:数据转换器工厂的集合
十一、代理实例创建过程
导入相关包:
// Retrofit的依赖
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
// step1
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.wanandroid.com")
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
// step2
ISharedListService sharedListService = retrofit.create(ISharedListService.class);
ISharedListService.java
public interface ISharedListService {
@GET("user/{userid}/share_articles/{pageid}/json")
Call<SharedListBean> getSharedList(@Path("userid") int it, @Path("pageid") int pageid);
}
创建了一个ISharedListService 接口类的对象,create函数内部使用了动态代理来创建接口对象,这样的设计可以让所有的访问请求都被代理。
// step3
Call<SharedListBean> sharedListCall = sharedListService.getSharedList(2, 1);
调用getSharedList的时候,在动态代理里面,会存在一个函数getSharedList,这个函数里面会调用invoke,这个invoke函数也就是retrofit里面的invoke函数。
所以,动态代理可以代理所有的接口,让所有的接口都走invoke函数,这样就可以拦截调用函数的执行,从而将网络接口的参数配置归一化。
// step4
sharedListCall.enqueue(new Callback<SharedListBean>(){
@Override
public void onResponse(Call<SharedListBean> call, Response<SharedListBean> response) {
if (response.isSuccessful()) {
System.out.println(response.body().toString());
}
}
@Override
public void onFailure(Call<SharedListBean> call, Throwable t) {
t.printStackTrace();
}
})
step1: 创建 retrofit 对象,构建一个网络请求的载体对象,和 okhttp 构建OkhttpClient 对象有一样的意义,只不过 retrofit 在 build 的时候有非常多的初始化内容,这些内容可以为后面网络请求提供准备,如准备线程转换 Executor, Gson convert, RxjavaCallAdapter.
step2: Retrofit 的精髓,为统一配置网络请求完成动态代理的设置。
step3: 构建具体网络请求对象Request(service),在这个阶段要完成的任务:1)将接口中的注解翻译成对应的参数; 2)确定网络请求接口的返回值 response 类型以及对应的转换器; 3)将 Okhttp 的 Request 封装成为Retrofit 的 OkhttpCall。总结来说,就是根据请求 service 的 Interface 来封装 Okhttp 请求 Request。
step4: 后面就是进行网络请求了,然后进行网络请求的数据了。
网上一般推荐 RxJava + Retrofit + OkHttp 框架,Retrofit 负责请求的数据和请求的结果,使用接口的方式呈现,OkHttp 负责请求的过程,RxJava 负责异步,各种线程之间的切换。
十二、Retrofit & serviceMethod
Retrofit
Build阶段所做的事:
baseUrl:网络请求的url地址
callFactory:网络请求工厂
callbackExecutor:回调方法执行器
adapterFactories:网络请求适配器工厂的集合
converterFactories:数据转换器工厂的集合
OkHttpCall:在invoke中,为网络call的封装
serviceMethod
baseUrl:网络请求的url地址
callFactory:
callAdapter:明确正确的callAdapter
responseType:确定call接口返回值类型
responseConverter:明确对应的数据转化器
parameterHandlers:对接口中的注解参数进行解析配置
十三 设计模式
1)Retrofit 实列使用建造者模式通过Builder类构建。
当构造函数的参数大于4个,且存在可选参数的时候就可以使用建造者设计模式。
2)Retrofit创建时的callFactory,使用工厂方法设计模式,但是似乎并不打算支持其他的工厂。
3)整个Retrofit采用的是外观模式,统一的调用创建网络请求接口实例和网络请求参数配置的方法。
4)Retrofit里面使用了动态代理来创建网络请求接口实列,这个是retrofit对用户使用来说最大的复用,其他的代码都是为了支撑这个动态代理给用户带来便捷性的。
5)使用了策略模式对serviceMethod对象进行网络请求参数配置,即通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络的url地址、网络请求执行器、网络请求适配器和数据转换器。
6)ExecutorCallbackCall使用装饰者模式来封装callbackExecutor,用于完成线程的切换。
7)ExecutorCallbackCall使用静态代理(委托)代理了Call进行网络请求,真正的网络请求由okhttpCall执行,然而okHttpCall不是自己执行,它是okhttp提供call给外界(retrofit)使用的唯一门户,其实这个地方就是门面模式。
8)ExecutorCallbackCall的被初始化是在ExecutorCallAdapterFactory里面通过适配器模式被创建的CallAdapter采用了适配器模式为创建访问Call接口提供服务。默认不添加Rxjava则使用默认的ExecutorCallAdapterFactory将okhttp3.call转变成为retrofit中的call,如果有Rxjava则将okhttp3.call转化为observable。
十四 请求流程
补充知识点:
1、Retrofit 实现原理
Retrofit 通过 java 接口以及注解来描述网络请求,并用动态代理的方式生成网络请求的 request,然后通过 client 调用相应的网络框架(默认 okhttp)去发起网络请求,并将返回的 response 通过 converterFactorty 转换成相应的数据 model,最后通过 calladapter 转换成其他数据方式(如 rxjava Observable)
- 通过 Retrofit. create( class)方法创建出 Service interface 的实例,从而使得 Service 中配置的方法变得可用,这是 Retrofit 代码结构的核心;
- Retrofit.create()方法内部,使用的是 Proxy.newProxylnstance()方法来创建Service 实例。这个方法会为参数中的多个 interface 创建一个对象,这个对象实现了所有 interface 的每个方法,并且每个方法的实现都是雷同的:调用对象实例内部的一个工InvocationHandler 成员变量的 invoke()方法,并把自己的方法信息传递进去。这样就在实质上实现了代理逻辑:interface 中的方法全部由一个另外设定的 InvocationHandler 对象来进行代理操作。并且,这些方法的具体实现是在运行时生成 interface 实例时才确定的,而不是在编译时(虽然在编译时就已经可以通过代码逻辑推断出来)。这就是网上所说的「动态代理机制」的具体含义。
2、动态代理和静态代理的区别?
-
静态代理类由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
-
动态代理类是在程序运行期间由 JVM 根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
静态代理
静态代理业务类只需要关注业务逻辑本身,保证了业务类的重用性。代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,需要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
动态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler invoke)。这样,在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使类职责更加单一,复用性更强。
3、Retrofit 流程
1)通过解析网络请求接口的注解,配置网络请求参数
2)通过动态代理生成网络请求对象
3)通过网络请求适配器将网络请求对象进行平台适配
4)通过网络请求执行器发送网络请求
5)通过数据转换器解析服务器返回的数据
6)通过回调执行器切换线程(子线程 ->>主线程)
7)用户在主线程处理返回结果
4、Retrofit 优点
1)可以配置不同 HTTP client 来实现网络请求,如 okhttp、httpclient 等;
2)请求的方法参数注解都可以定制;
3)支持同步、异步和 RxJava;
4)超级解耦;
5)可以配置不同的反序列化工具来解析数据,如 json、xml 等
6)框架使用了很多设计模式
5、Retrofit 中的数据究竟是怎么处理的?它是怎么返回 RxJava.Observable 的?
Retrofit 中的数据其实是交给了 callAdapter 以及 converter 去处理,callAdapter 负责把 okHttpCall 转成我们所需的 Observable 类型, converter 负责把服务器返回的数据转成具体的实体类。
6、Retrofit为什么没有封装成单例?(出现性能的考虑,才会封装成单例)
答:封装成单例,可能会出现内存泄漏问题。如果封装成单例,那retrofit对象变量的生命周期会一直存在,retrofit携带很多资源,在build的时候,会构造retrofit对象,则那些变量都是要占内存的,没有封装成单例,就是为了避免内存泄漏。