前言
在实际项目中往往是使用Retrofit
来做网络请求工作。Retrofit
采用RESTful
风格,本质上只是对OkHttp
进行封装,今天我们根据几个问题来进一步学习一下Retrofit
的源码与设计思想。
1. 使用方法
直接看一下官方介绍的使用方法。
public final class SimpleService {
public static final String API_URL = "https://api.github.com";
public static class Contributor {
public final String login;
public final int contributions;
public Contributor(String login, int contributions) {
this.login = login;
this.contributions = contributions;
}
}
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(@Path("owner") String owner, @Path("repo") String repo);
}
public static void main(String... args) throws IOException {
// Create a very simple REST adapter which points the GitHub API.
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl(API_URL)
.client(new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS).build())
.addConverterFactory(GsonConverterFactory.create())
.build();
// Create an instance of our GitHub API interface.
GitHub github = retrofit.create(GitHub.class);
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");
// Fetch and print a list of the contributors to the library.
List<Contributor> contributors = call.execute().body();
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
可以简单的概括成三步:
- 构建
retrofit
实例。 - 构建
API
接口实例。 - 执行请求,解析响应。
2. 流程解析
我们按照它的使用方法来分析一下它的流程。
2.1 构建 Retrofit 实例
从使用方法可以看出是使用建造者模式来构建实例。
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl(API_URL)
.client(new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS).build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
这一步就不具体展开了,看几个参数。
public static final class Builder {
//实际的请求调用,如 okhttp3.OkHttpClient
private @Nullable okhttp3.Call.Factory callFactory;
//基础URL,如:域名
private @Nullable HttpUrl baseUrl;
//数据转换器列表
private final List<Converter.Factory> converterFactories = new ArrayList<>();
//请求适配器列表
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
2.2 构建 API 接口实例
按照官方的使用方法介绍,我们会将我们的API方法
放在一个接口中,然后通过注解来设置请求参数。在使用时,通过retrofit.create(Class<T>)
方法将这个接口实例化,然后调用其方法。 如:
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(@Path("owner") String owner, @Path("repo") String repo);
}
//实例化API接口
GitHub github = retrofit.create(GitHub.class);
//调用接口中某条API
Call<List<Contributor>> call = github.contributors("square", "retrofit");
看一下源码
public <T> T create(final Class<T> service) {
//验证 api service
validateServiceInterface(service);
return (T)
//这里采用了动态代理模式, service 就是被代理类
//todo 为什么要采用动态代理,有什么好处吗?用别的行不行?
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
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;
Platform platform = Platform.get();
//如果不是系统默认方法,通过loadServiceMethod()方法返回一个ServiceMethod,并调用invoke方法
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
做了两件事:
- 验证我们的API接口类。
- 利用动态代理在运行期间实例化API接口。
private void validateServiceInterface(Class<?> service) {
//service 必须是 interface,否则抛出异常
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
...省略代码...
//是否立即验证API接口中的所有方法,由用户设置,默认为false
if (validateEagerly) {
Platform platform = Platform.get();
//遍历 service 中定义的所有方法
for (Method method : service.getDeclaredMethods()) {
//如果该方法不是系统默认方法且方法修饰符不是静态方法就执行loadServiceMethod方法
if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
//加载请求方法。
loadServiceMethod(method);
}
}
}
}
从这我们也可以看出,我们的API方法
必须方法接口中。如果开始验证接口,会遍历其声明的所有方法,过滤掉系统默认方法与静态方法,然后执行loadServiceMethod(method)
。
扩充一下:
getMethods()
: 返回由类或接口声明的以及从超类和超接口继承的所有公共方法。getDeclaredMethods()
: 返回类声明的方法,包括 public, protected, default (package),但不包括继承的方法。所以,相对比于 getMethods 方法,getDeclaredMethods速度更快,尤其是在复杂的类中,如在Activity类中。
最终都是通过loadServiceMethod(method)
方法来加载一个ServiceMethod
。
看一下HttpServiceMethod.parseAnnotations()
方法,我将其简化了一下,如下:
HttpServiceMethod.java
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
//获取方法的注解信息
Annotation[] annotations = method.getAnnotations();
//适配器类型,就是Retrofit.addCallAdapterFactory()添加的类型。
Type adapterType;
//方法的返回类型
adapterType = method.getGenericReturnType();
//实例化一个 CallAdapter 对象
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
//检查 responseType,如果不合格则抛出异常
Type responseType = callAdapter.responseType();
//实例化一个Converter对象,将 okhttp3.ResponseBody 转换成 ResponseT 类型
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
//不是kotlin挂起方法,返回 CallAdapted,其实也就是调用 callAdapter.adapter 方法
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
实例化了 ServiceMethod
后,调用invoke
方法。
HttpServiceMethod.java
@Override
final @Nullable ReturnT invoke(Object[] args) {
//新建一个 OkHttpCall 请求
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
//然后调用 adapt 方法,CallAdapted 有重写 adapt 方法,然后调用 callAdapter.adapt(call) 方法
return adapt(call, args);
}
protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);
从上述代码中可以看出,invoke
方法就是实例化一个Call
请求,然后调用adapter
方法,在这里adapter
是一个抽象方法,所以具体实现方法就需要看它的具体实现类CallAdapter
。 这里的 CallAdapter
就是通过.addCallAdapterFactory()
方法所添加的CallAdapter
,以及根据平台默认提供的DefaultCallAdapterFactory
中的CallAdapter
,执行其adapter
方法,最终返回Call<Object>
。
2.3 执行请求,解析响应
在上一步中,我们对API接口
进行了实例化,通过CallAdapter
对请求进行适配,最终得到一个Call<Object>
对象。
接着下一步,就是执行这个Call<Object>
请求,最终得到我们想要的Object
对象。
例如一开始使用方法中所介绍的:
//已经得到了Call<List<Contributor>>对象,执行call,得到List<Contributor>
List<Contributor> contributors = call.execute().body();
调用 execute
执行同步请求获取到Response
,然后获取其请求体。
OkHttpCall.java
@Override
public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
//判断请求是否已经被执行,如果已被执行则抛出异常
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
//获取最原始的请求,通过createRawCall()创建okhttp3.Call
call = getRawCall();
}
if (canceled) {
call.cancel();
}
//执行请求,并且解析响应,将okhttp3.response 转换成 retrofit2.response
return parseResponse(call.execute());
}
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;
}
/**
* 解析响应,就是就okhttp3.response 转换成 retrofit2.response
*/
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
...省略代码...
try {
//利用converter转换成我们期望的类型
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
...省略代码...
}
从源码中也可以看出,请求的实际工作还是通过okhttp
来完成的,这边Retrofit
就是负责请求与响应转换工作,将retrofit2.Call
转换成okhttp3.Call
,将okhttp3.response
转换成retrofit2.response
。
3. 为什么要引入CallAdapter与Converter?
如果你熟悉okHttp
的话,你应该知道,当我们请求的时候,要先通过OkHttpClient.newCall(request)
方法将request
转换成Call
对象,然后再执行这个Call
对象拿到response
。
但是Retrofit
不光光只支持Call
,他还可以将请求适配成Observable
类型,方便与RxJava2
结合起来一起使用。这就是通过CallAdapter
来进行适配工作的,例如通过默认的DefaultCallAdapterFactory
将请求转换成Call<Object>
,通过RxJava2CallAdapter
将请求转换成Observable<Object>
。
回到okHttp
,大部分业务情况下,我们在拿到响应体后都会将其进行反序列化成对象,方便调用。显然,Retrofit
就考虑到了这一点,所以他默认提供了GsonConverterFactory
,来帮助我们做这一步反序列化工作。这就是通过Converter
来完成的,同时它也支持用户进行自定义。
4. CallAdapter 是如何工作的?
作为请求适配器,我们将CallAdapter
工作流程分为三步:添加、匹配、工作。
添加
可以通过addCallAdapterFactory(CallAdapter.Factory)
方法来添加请求适配器工厂类,添加成功后会被保存在callAdapterFactories
列表中。另外,Retrofit
会根据Platform
来添加默认的请求适配器,例如:DefaultCallAdapterFactory
等等,同样也加入到callAdapterFactories
列表中。
匹配
思考一下:所有添加的请求适配器都会被保存在callAdapterFactories
列表中,那在实际请求中是如何匹配出相对应的适配器的呢?
在HttpServiceMethod.parseAnnotations()
方法中,我们有实例化一个CallAdapter
对象。(具体流程就不再次展开了,请回头看 2.2 构建 API 接口实例 中所介绍内容。)
HttpServiceMethod.java
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
//实例化一个 CallAdapter 对象
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
···省略代码···
//不是kotlin挂起方法,返回 CallAdapted,其实也就是调用 callAdapter.adapter 方法
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
匹配工作其实就在createCallAdapter()
方法中,一步步走下来,最终到Retrofit.nextCallAdapter()
方法中:
Retrofit.java
public CallAdapter<?, ?> nextCallAdapter(
@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
//通过方法的返回值类型与注解信息来找到匹配的CallAdapter
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
···省略代码···
//如果找不到匹配的CallAdapter,则抛出异常
throw new IllegalArgumentException(builder.toString());
}
简单概括一下,就是通过方法的返回值类型与注解信息,遍历callAdapterFactories
列表,找到匹配的CallAdapter
,如果找不到则抛出IllegalArgumentException
异常。
工作
找到匹配的CallAdapter
后,剩下就是看看他是如何工作的。
如上一步匹配过程所介绍,在找到匹配的callAdapter
后,会通过它来实例化一个CallAdapted
对象。
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
private final CallAdapter<ResponseT, ReturnT> callAdapter;
CallAdapted(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, ReturnT> callAdapter) {
//将responseConverter传给父类。
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
}
CallAdapted
很简单,就是继承了HttpServiceMethod
,然后复写了adapt
方法。也就是说,最终执行的,其实就是我们上一步匹配到的CallAdapter
对象的adapt
方法。
比如匹配到的是DefaultCallAdapterFactory
中的CallAdapter
,最终执行的就是其adapt
方法,具体代码细节这边就不展示了,有兴趣同学请自行查阅。
另外,我这边展示的是不支持kotlin
挂起函数的情况,当然即使是kotlin
挂起函数,过程也是一样的,也是执行其子类的adapt
方法。
5. Converter 是如何工作的?
作为数据转换器,我们同样将Converter
工作流程分为三步:添加、匹配、工作。
添加
可以通过addConverterFactory(Converter.Factory)
方法来添加数据装换器工厂类,添加成功后会被保存在converterFactories
列表中。另外,Retrofit
会根据Platform
来添加默认的数据转换器,例如OptionalConverterFactory
,同样也加入到converterFactories
列表中。
匹配
跟上述所介绍的 4. CallAdapter 是如何工作的 一样,同样在HttpServiceMethod.parseAnnotations()
方法中,会实例化一个Converter
对象。
匹配工作其实就在createResponseConverter()
方法中,一步步走下来,最终到Retrofit.nextResponseBodyConverter()
方法中:
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
//通过转换类型与注解信息来找到匹配的Converter
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
···省略代码···
//如果找不到匹配的Converter,则抛出异常
throw new IllegalArgumentException(builder.toString());
}
简单概括一下,就是通过转换类型与注解信息,遍历converterFactories
列表,找到匹配的Converter
,如果找不到则抛出IllegalArgumentException
异常。
工作
跟上述所介绍的 4. CallAdapter 是如何工作的 一样,我们找到匹配的Converter
后,通过它来实例化一个CallAdapted
。但不同的是,我们会将responseConverter
传给父类,也就是HttpServiceMethod
,然后当其调用invoke
方法时,我们通过responseConverter
来实例化一个OkHttpCall
对象,最终将这个OkHttpCall
对象传给adapter
方法执行。
最终当执行请求时,OkHttpCall
执行parseResponse
来解析响应,调用responseConverter.convert()
方法,将ResponseBody
数据转换我们想要的类型。
6. 说说使用到了哪些设计模式
动态代理模式
Retrofit
内部通过动态代理+反射来拿到用户定义在接口中的请求参数,从而来构建实际请求。具体细节这边就不再次展开了,可以回头查看 2.2 构建 API 接口实例 这一部分内容。
为什么要使用动态代理来获取API方法?
不知道你们有没有这个疑问,为什么我们的API方法
需要定义在interface
中呢?又为什么要通过动态代理+反射的形式来拿到请求参数呢?
Retrofit
按照RESTful
风格设计并通过注解来定义API方法
的请求参数,并将这些API方法
放到interface
中。因为interface
是不能被实例化的,所以这里采用动态代理在运行期间实例化API接口
,获取到方法的请求参数。
再进一步:
解耦,将实际业务与Retrofit
隔离开来。用户只需通过注解方法来定义请求参数,而实际请求的构建则通过Retrofit
内部来实现。
此时再反过来看为何放在interface
中?相信你心中已有答案了吧。
策略模式
当针对同一类型问题有多种处理方式,仅仅是具体行为有差别时,就可以使用策略模式。
例如:Retrofit
中的请求适配器
- 抽象策略:
CallAdapter
。 - 策略具体实现:
DefaultCallAdapterFactory.get()
、RxJava2CallAdapter
。
即提供默认的请求适配器,也支持用户自定义,符合开闭原则,达到很好的可扩展性。
适配器模式
Retrofit
会帮我们构建实际请求,内部通过默认的DefaultCallAdapterFactory
来将请求转换成Call<Object>
,同时Retrofit
也支持其它平台,比如为了适配RxJava
特性,将请求转换成Observable<Object>
。
- Target(目标角色):
Call<Object>
,Observable<Object>
。 - adaptee(需要适配的对象):
OkHttpCall
。 - adapter(适配器):
DefaultCallAdapterFactory.get()
、RxJava2CallAdapter
。
工厂方法模式
我们以Converter
来举例。
- 抽象工厂:
Converter.Factory
。 - 具体工厂:
GsonConverterFactory
、BuiltInConverters
等等。 - 抽象产品:
Converter
。 - 具体产品:
GsonResponseBodyConverter
、GsonRequestBodyConverter
、ToStringConverter
等等。
这边就不具体展开分析各个类了,有兴趣的同学可自行查阅。
建造者模式
在构建Retrofit
实例的时候,就用到了建造者模式。建造者模式在开源库中的出现的次数真的很频繁,为了适配不同的用户的各种需求,需提供各种各样的参数与方法来供用户自行选择,所以使用建造者模式,之所以很常见,是因为这样很合理。
7. 使用过程中踩过什么坑?
关于BaseUrl
的使用曾经踩过坑,某天我将baseUrl
改了一下,然后发现请求接口时服务器一直返回404
,但是当我尝试用Postman
去调试接口的时候,发现接口是好的,也就推测出来是我的代码出问题了。
最终发现,问题出在:拼接成完整的URL
时api
被删除了。
Base URL: http://example.com/api/
Endpoint: /foo/bar/
Result: http://example.com/foo/bar/
正确的使用方式为:Endpoint
不以斜杠开头。
Base URL: http://example.com/api/
Endpoint: foo/bar/
Result: http://example.com/api/foo/bar/
总结
本文,我们以几个问题的形式展开来对Retrofit
源码及设计思想进行解析,相信你对源码有了进一步的了解。Retrofit
本质只是对okHttp
进行封装,出发点肯定是让网络请求变得更加容易,考虑适配各种用户需求,Jake Wharton
大神用了很多设计模式,真的太让人膜拜了。
到此,关于Retrofit
的源码解析就结束啦。文章来源:https://www.toymoban.com/news/detail-682031.html
推荐更多Android学习笔记参考
Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap
文章来源地址https://www.toymoban.com/news/detail-682031.html
到了这里,关于【面试 反思】Retrofit源码与设计 7 连问的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!