源码分析Retrofit请求流程
Retrofit
是 square
公司的另一款广泛流行的网络请求框架。前面的一篇文章《源码分析OKHttp执行过程》已经对 OkHttp
网络请求框架有一个大概的了解。今天同样地对 Retrofit
的源码进行走读,对其底层的实现逻辑做到心中有数。
0x00 基本用法
Retrofit
的项目地址为:https://github.com/square/retrofit
打开项目目录下的 samples
文件夹,从这里可以浏览 Retrofit
项目的使用范例。
在本文中打开SimpleService.java
这个类作为源码走读的入口。这个类很简单,展示了 Retrofit
的基本用法
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 {
//这个是请求github项目代码贡献者列表的接口
//使用@GET注解指定GET请求,并指定接口请求路径,使用大括号{}定义的参数,是形参,retrofit会把方法中的
//@Path 传入到请求路径中
@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 {
// 创建一个retrofit,并且指定了接口的baseUrl
// 然后设置了一个gson转换器,用于将接口请求下来的json字符串转换为Contributor实体类。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// 这里是魔法所在,retrofit将程序猿定义的接口变成“实现类”
GitHub github = retrofit.create(GitHub.class);
//通过retrofit这个“实现类”执行contributors方法
Call<List<Contributor>> call = github.contributors("square", "retrofit");
// 执行Call类中的execute方法,这是一个同步方法
// 当然跟okhttp一样,异步方法是enqueue,这个下文会提到
List<Contributor> contributors = call.execute().body();
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
通过上面代码的阅读,知道 retrofit
使用流程
- 定义
API
- 构造接口数据实体类
- 构造
retrofit
对象,指定baseUrl
和数据转换器(即接口数据解析器,如对json
、xml
、protobuf
等数据类型的解析) - 通过
retrofit
将程序猿定义的API
接口变成"实现类" - 执行“实现类”的方法
- 执行网络请求,获取接口请求数据
这个流程关键点是4、5、6,下文将详细对这几个步骤的源码进行阅读。
在继续下文之前,我们先看看这个SimpleService
的执行结果,它打印了retrofit
这个项目的代码贡献者
JakeWharton (928)
swankjesse (240)
pforhan (48)
eburke (36)
dnkoutso (26)
NightlyNexus (26)
edenman (24)
loganj (17)
Noel-96 (16)
rcdickerson (14)
rjrjr (13)
kryali (9)
adriancole (9)
holmes (7)
swanson (7)
JayNewstrom (6)
crazybob (6)
Jawnnypoo (6)
danrice-square (5)
vanniktech (5)
Turbo87 (5)
naturalwarren (5)
guptasourabh04 (4)
artem-zinnatullin (3)
codebutler (3)
icastell (3)
jjNford (3)
f2prateek (3)
PromanSEW (3)
koalahamlet (3)
0x01 构造过程
从上文的源码阅读中,可以看出程序猿只是定义了一个接口,但是现在实现接口的工作是由 retrofit
来实现的
GitHub github = retrofit.create(GitHub.class);
Call<List<Contributor>> call = github.contributors("square", "retrofit");
create
打开 retrofit.create
方法
public <T> T create(final Class<T> service) {
//对接口进行校验
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//通过Proxy创建了一个代理
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 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);
}
//判断是否为默认方法,Java8中接口也可以有默认方法,所以这里有这个判断
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//关键点
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
这个方法很短,关键是通过 Proxy
创建了一个 Github
接口的代理类并返回该代理。
newProxyInstance
方法需要3个参数:ClassLoader
、Class<?>
数组、InvocationHandler
回调。
这个 InvocationHandler
非常关键,当执行接口 Github
的contributors
方法时,会委托给InvocationHandler
的invoke
方法来执行。即Github
将接口代理给了Proxy
来执行了。
InvocationHandler
接着看InvocationHandler
接口的实现。
在 invoke
方法中有三个参数,其中proxy
就是代理对象,而 method
就是程序猿定义的那个网络请求接口,顾名思义 args
就是方法的参数。
此方法最终是调用了
loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
loadServiceMethod
打开 loadServiceMethod
方法
ServiceMethod<?> loadServiceMethod(Method method) {
// 判断是否有缓存
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
//同步处理
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
//没有获取到缓存则使用`ServiceMethod`方法来创建
result = ServiceMethod.parseAnnotations(this, method);
//最后缓存起来
serviceMethodCache.put(method, result);
}
}
return result;
}
这个方法就是通过 method
来获取一个 ServiceMethod
对象。
ServiceMethod
打开 ServiceMethod
发现它是一个抽象类,有一个静态方法 parseAnnotations
和一个抽象方法 invoke
。
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.");
}
//最终使用到HttpServiceMethod类
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract T invoke(Object[] args);
}
parseAnnotations
方法就是对程序猿定义的接口中使用的注解进行解析。
最后是使用了HttpServiceMethod.parseAnnotations
方法
HttpServiceMethod
/** Adapts an invocation of an interface method into an HTTP call. */
final class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method);
//...省略部分代码
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
return new HttpServiceMethod<>(requestFactory, callFactory, callAdapter, responseConverter);
}
//...省略部分代码
@Override ReturnT invoke(Object[] args) {
return callAdapter.adapt(
new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
}
HttpServiceMethod
是 ServiceMethod
的子类。而在parseAnnotations
方法中构造了HttpServiceMethod
实例并返回。
因此,loadServiceMethod
方法返回的是HttpServiceMehod
对象
这样下面代码的执行实际上是执行了 HttpServiceMehod
的 invoke
方法。
loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
再次翻看上文中HttpServiceMethod
类
@Override ReturnT invoke(Object[] args) {
return callAdapter.adapt(
new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
invoke
方法里有执行了callAdapter.adapt
方法,参数为OkHttpCall
,这个类实际上就是对okhttp
网络请求的封装,这里也可以看出retrofit
内部是使用了okhttp
来执行网络请求的
CallAdapter
public interface CallAdapter<R, T> {
//..省略部分代码
T adapt(Call<R> call);
//CallAdapter抽象工厂类
abstract class Factory {
//返回CallAdapter实例
public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
//..省略部分代码
}
}
这是一个接口,内部有一个Factory
抽象工厂类,用于获取CallAdapter
对象。
CallAdapter
有很多子类,那 callAdapter.adapt
方法执行的是哪个具体类的方法呢?实际上,从调试代码中可以发现是调用DefaultCallFactory
中的内部实现类
DefaultCallAapterFactory
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();
@Override public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
//返回一个CallAapter实例
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
//将参数返回,而这个参数就是OKHttpCall的实例
return call;
}
};
}
}
可以发现,在adapt
方法中就是将参数call
返回。
所以下面代码返回的是OkHttpCall
对象。
loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
综上
//创建了Github接口的代理类
GitHub github = retrofit.create(GitHub.class);
//执行接口的方法,其实就是调用了代理类的方法,并最终返回了一个OKhttpCall对象
//而这个对象就是对Okhttp的封装
Call<List<Contributor>> call = github.contributors("square", "retrofit");
0x02 执行结果
上文中获取到OKhttpCall
对象,它只是把接口请求过程进行了封装,并没有真正的获取到接口数据。要获取到接口数据还需要调用OkHttpCall.execute
方法
List<Contributor> contributors = call.execute().body();
Call.execute 或 Call.enqueue
这里的请求过程与前文中《源码分析OKHttp执行过程》介绍的是类似的。接一下
打开OkHttpCall.execute
方法
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else if (creationFailure instanceof RuntimeException) {
throw (RuntimeException) creationFailure;
} else {
throw (Error) creationFailure;
}
}
call = rawCall;
if (call == null) {
try {
call = rawCall = createRawCall();
} catch (IOException | RuntimeException | Error e) {
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}
这里的执行逻辑也很简单
- 使用
synchronized
进行同步操作 - 进行异常处理
- 调用
createRawCall
创建okhttp3.Call
对象 - 执行
okhttp
的Call.execute
方法,并解析response
后返回请求结果
同样地,异步请求操作也是类似的
打开OkHttpCall.enqueue
方法
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
//创建okhttp网络请求
call = rawCall = createRawCall();
} catch (Throwable t) {
throwIfFatal(t);
failure = creationFailure = t;
}
}
}
if (failure != null) {
callback.onFailure(this, failure);
return;
}
if (canceled) {
call.cancel();
}
//最终是执行了OkHttp中的call.enqueue方法
//并回调相应的接口
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
这个方法其实最终都是执行了okhttp
的相应方法。
0x03 总结
Retrofit
其实一种更加高级的网络应用框架,通过代理模式简化了接口的定义,无需提供接口的具体实现就可以完成网络接口请求的执行。它的底层实际上是封装了 okhttp
的执行过程,也把对网络的操作进行了封装,而对于程序猿来说只需要关注业务逻辑,对网络请求的具体实现不必关心。
例如在本文开头的实例中我们只需要定义接口,定义实体类,其他工作都交给了 Retrofit
,接下来就是Magic
。
源码分析Retrofit请求流程的更多相关文章
- Okhttp源码分析--基本使用流程分析
Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Re ...
- apiserver源码分析——处理请求
前言 上一篇说道k8s-apiserver如何启动,本篇则介绍apiserver启动后,接收到客户端请求的处理流程.如下图所示 认证与授权一般系统都会使用到,认证是鉴别访问apiserver的请求方是 ...
- nodejs的Express框架源码分析、工作流程分析
nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...
- openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)
这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的f ...
- [旧][Android] Retrofit 源码分析之执行流程
备注 原发表于2016.04.23,资料已过时,仅作备份,谨慎参考 前言 由于是第一次自己翻看源代码进行学习,加上基础不好,在看源代码的过程中简直痛苦不堪,但同时也暴露出了自己的许多问题.我觉得学习源 ...
- SpringMVC源码分析-400异常处理流程及解决方法
本文涉及SpringMVC异常处理体系源码分析,SpringMVC异常处理相关类的设计模式,实际工作中异常处理的实践. 问题场景 假设我们的SpringMVC应用中有如下控制器: 代码示例-1 @Re ...
- TOMCAT8源码分析——处理请求分析(下)
前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...
- springmvc源码分析系列-请求处理流程
接上一篇-springmvc源码分析开头片 上一节主要说了一下springmvc与struts2的作为MVC中的C(controller)控制层的一些区别及两者在作为控制层方面的一些优缺点.今天就结合 ...
- DRF框架(一)——restful接口规范、基于规范下使用原生django接口查询和增加、原生Django CBV请求生命周期源码分析、drf请求生命周期源码分析、请求模块request、渲染模块render
DRF框架 全称:django-rest framework 知识点 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码 - 基于restful规范下的CBV接口 3.请求组件 ...
随机推荐
- 【题解】射击-C++
Description 不难发现,豆豆能从很多事情中去思考数学,于是豆豆父母决定让他去练习射击,这是项需要集中注意力的运动,相信 能够让豆豆暂时脱离数学.学习射击的第一天就让豆豆产生 了浓厚的兴趣,射 ...
- 【基本数据结构之堆】-C++
注意:这篇博客讲的是手写堆,喜欢用C++自带数据结构模拟的慎入 今天我们来聊一聊一种奇怪 的数据结构: 堆 为什么说这个数据结构有点奇怪呢? 先看看其他的在我眼里是正常的数据结构: 队列(近似于排队) ...
- drop、truncate和delete的区别 [转载]
drop.truncate和delete的区别 本文转载自: https://www.cnblogs.com/zhizhao/p/7825469.html (1)DELETE语句执行删除的过程 ...
- 一些学习js的算法题目
1.排序 问题描述 编写一个程序,输入3个整数,然后程序将对这三个整数按照从大到小进行排列. 输入格式:输入只有一行,即三个整数,中间用空格隔开. 输出格式:输出只有一行,即排序后的结果. 输入输出样 ...
- 如何优雅关闭 Spring Boot 应用
## 前言 随着线上应用逐步采用 SpringBoot 构建,SpringBoot应用实例越来多,当线上某个应用需要升级部署时,常常简单粗暴地使用 kill 命令,这种停止应用的方式会让应用将所有处理 ...
- 《VR入门系列教程》之6---VR硬件介绍及DK1
第二章 VR硬件介绍 本章主要介绍当前比较流行的消费版VR设备,包括VR头显以及应用运行的PC和手机平台. 即使是在这工业高速发展的时代,一些大厂(比如Facebook的Oculus ...
- Python_我的学习笔记 (博客停更------)
贡献一张PyCharm快捷键图(图片是借用他人的)----------建议最大化查看,因为这样不会破坏布局 注:部分内容引用小甲鱼,其他等网页,网站内容.如有冒犯,请联系我. 2019.07.21 ...
- configASSERT( uxCriticalNesting == ~0UL );问题
今天在单步调试FreeRTOS时,一直进入port.c 中的configASSERT( uxCriticalNesting == ~0UL ):函数.照片如下 上网一查,并且结合这个英文注释,才发现, ...
- java - try catch finally 用法
try { //执行的代码,其中可能有异常.一旦发现异常,则立即跳到catch执行.否则不会执行catch里面的内容 } catch { //除非try里面执行代码发生了异常,否则这里的代码不会执行 ...
- jquery 操作HTML data全局属性缓存的坑
data-* 全局属性 是一类被称为自定义数据属性的属性,它赋予我们在所有 HTML 元素上嵌入自定义数据属性的能力,并可以通过脚本(一般指JavaScript) 与 HTML 之间进行专有数据的交换 ...