Retrofit 源码阅读笔记(一)
Retrofit
相信每一个 Android
开发者都对它非常熟悉,它使我们调用 Http
请求变得非常的简单(内部实现是 OkHttp
),我们只需要定义一个接口,接口的每一个方法就表示一个 Http
请求,方法的注解,参数的注解和参数他们共同来描述了这一个 Http
请求的 Request
,而方法的返回值描述了如何处理 Http
的 Response
。通过 Retrofit#create()
方法就能够生成一个上面接口的实现类(通过动态代理实现),通过调用这个实现类的方法就能够完成一次 Http
请求,一次网络请求就好像调用了一次我们的本地方法一样。刚开始接触 Retrofit
的我,感觉这个真的是太神奇,工作多年后渐渐地我也知道了它的原理,所以这次文章来基于 Retrofit
的源码来记录一下它的实现方式。
本篇文章是系列文章的第一篇,我阅读的 Retrofit
源码版本是 2.9.0
。
动态代理创建接口实现类
创建我们定义的接口实现类通过 Retrofit#create()
方法传一个接口的 Class
对象。我们来看看它的源码:
Java
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
// 校验 Class 对象是否合法
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];
// inovke() 方法会在每一个方法的调用时,调用这个方法,对应被调用的对象就是 proxy,方法是 method,方法的参数是 args。
@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.
// 如果是 Object 中的方法,直接通过反射调用 Object 中对应的方法。
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
// 判断是否是接口中的 default 方法,如果是通过 platform 对象调用,如果不是通过 loadServiceMethod() 方法获取一个对象,然后调用 invoke() 方法。
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
上面代码做了两件事,通过 validateServiceInterface()
方法检查 Class
对象是否合法,然后通过动态代理构建一个代理对象(如果对动态代理没有一点了解的同学,建议先去查查相关资料)。
代理对象的每一个方法调用都会触发 InvocationHandler#invoke()
的方法,proxy
是代理的对象,method
是对应的方法的 Method
(常写反射的同学可能很熟悉),args
就是对应方法的参数。invoke()
方法的处理主要做了以下事情:
- 判断是否是
Object
中的方法,如果是直接通过反射的方式调用对应的方法。 - 判断是否是接口中的
default
方法,如果是直接通过Platform#invokeDefaultMethod()
方法去调用。 - 如果第一点和第二点的条件都不满足,通过
loadServiceMethod()
方法加载一个对象,然后调用它的invoke()
方法来执行调用。(这也是我们分析的主要逻辑)
validateServiceInterface()
我们简单介绍一下是如何校验 Class
对象的:
Java
private void validateServiceInterface(Class<?> service) {
// 判断是否是接口
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
Deque<Class<?>> check = new ArrayDeque<>(1);
check.add(service);
// 接口不能够有泛型的参数
while (!check.isEmpty()) {
Class<?> candidate = check.removeFirst();
if (candidate.getTypeParameters().length != 0) {
StringBuilder message =
new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
if (candidate != service) {
message.append(" which is an interface of ").append(service.getName());
}
throw new IllegalArgumentException(message.toString());
}
Collections.addAll(check, candidate.getInterfaces());
}
// 是否提前加载所有的方法
if (validateEagerly) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
loadServiceMethod(method);
}
}
}
}
上面主要验证两点:
Class
对象必须是接口。Class
对象和其对应的父类接口都不能有泛型参数。
后续还通过 validateEagerly
参数来判断是否要提前加载对应的请求 Method
,默认是不提前加载,只有在方法调用时才会去判断加载。
loadServiceMethod()
加载方法的实现是通过 loadServiceMethod()
,看看代码实现:
Java
ServiceMethod<?> loadServiceMethod(Method method) {
// 从缓存中去获取 ServiceMethod
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
类封装,首先从本地缓存中去获取 ServiceMethod
对象,如果没有再通过 ServiceMethod#parseAnnotaions()
方法去创建,然后存放在缓存中,下次再使用就不需要再创建。
Java
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// 构建 http 请求
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);
}
上面的代码主要做了以下事情:
- 通过
RequestFactory.parseAnnotations()
方法来解析方法的注解和参数的注解来构建Http
的请求。 - 校验方法返回值的类型,返回类型中不能够有不确定的泛型,也不能够是返回空。
- 通过
HttpServiceMethod.parseAnnotations()
方法来构建Http
的请求任务。
RequestFactory.parseAnnotations()
和 HttpServiceMethod.parseAnnotations()
这两个方法可以说是核心代码中的核心,后续的分析也都从它们两个开始。
方法注解解析
我们接着看前面一节提到的 RequestFactory.parseAnnotations()
方法:
Java
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
朴实无华的代码,直接创建一个 Builder()
对象,然后调用其 build()
方法,我们看看 它的构造函数和 build()
方法的源码实现:
Java
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
// 获取方法的注解
this.methodAnnotations = method.getAnnotations();
// 获取参数的类型
this.parameterTypes = method.getGenericParameterTypes();
// 获取参数的注解
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
RequestFactory build() {
// 方法注解解析
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
// 如果不知道 Http 请求的类型报错
if (httpMethod == null) {
throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
// 如果 Multipart 和 FormEncoded 没有 Body 报错。
if (!hasBody) {
if (isMultipart) {
throw methodError(
method,
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError(
method,
"FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
// 解析参数的注解,不同的参数注解处理方式通过 ParameterHandler 类来封装
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
// 如果没有获取到 URL 和 请求的 Path,报错。
if (relativeUrl == null && !gotUrl) {
throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
}
// 不允许有 Body 的请求,但是有 Body,报错
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError(method, "Non-body HTTP method cannot contain @Body.");
}
// FormEncoded 请求没有获取到 Field, 报错
if (isFormEncoded && !gotField) {
throw methodError(method, "Form-encoded method must contain at least one @Field.");
}
// Multipart 请求,没有获取到 Part Body,报错
if (isMultipart && !gotPart) {
throw methodError(method, "Multipart method must contain at least one @Part.");
}
return new RequestFactory(this);
}
构造函数中首先获取到方法的注解(可以有多个注解,所以是一个数组);获取方法中参数的类型;获取方法参数中的注解(因为有多个参数,一个参数又可以有多个注解,所以是一个二维数组)。
build()
方法中主要做了两件事,通过 parseMethodAnnotation()
来解析方法的注解;通过 parseParameter()
来解析参数和对应的注解,解析成功后对应的处理方式用 ParameterHandler
类来封装。
在 build()
函数中还判断了各种请求参数是否正确,有以下几点:
- 如果不知道
Http
请求的类型(就是GET
,POST
啥的没有明确)报错。 - 如果
Multipart
和FormEncoded
没有Body
报错。 - 如果没有获取到
Url
和 请求的Path
,报错 (也就是Url
和 请求的Path
取其一即可) 。 - 不允许有
Body
的请求,但是有Body
,报错(比如GET
请求就不允许有Body
)。 FormEncoded
请求没有获取到Field
, 报错。Multipart
请求,没有获取到Part
Body
,报错。
我们看看 parseMethodAnnotation()
方法的源码:
Java
private void parseMethodAnnotation(Annotation annotation) {
// 首先通过 parseHttpMethodAndPath() 方法取解析 Http 请求的方法和请求的相对路径
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
// 解析 Headers
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError(method, "@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
// Multipart
// 和 FormUrlEncoded 冲突
if (isFormEncoded) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
// FormUrlEncoded
// 和 Multipart 冲突
if (isMultipart) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}
如果是请求方法类的注解通过 parseHttpMethodAndPath()
方法去解析它的请求方法和解析请求的相对路径,请求方法类的注解包括 @DELETE
、@GET
、 @HEAD
、 @PATCH
、 @POST
、 @PUT
、 @OPTIONS
、 和 @HTTP
等等。
@Headers
注解表示请求的 Header
,通过 parseHeaders()
方法完成解析。
@Multipart
标记请求 Body
为 Multipart
,它和 FormUrlEncoded
冲突。
@FormUrlEncoded
标记请求 Body
为 Form
表单,它和 Multipart
冲突。
我们看看 parseHttpMethodAndPath()
方法的实现:
Java
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
// 多次设置 HttpMethod 报错
if (this.httpMethod != null) {
throw methodError(
method,
"Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod,
httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody;
// 相对路径可以为空,为空直接返回
if (value.isEmpty()) {
return;
}
// Get the relative URL path and existing query string, if present.
// 如果在请求测 Query 参数后添加参数化的 Path 报错。
int question = value.indexOf('?');
if (question != -1 && question < value.length() - 1) {
// Ensure the query string does not have any named parameters.
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
throw methodError(
method,
"URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.",
queryParams);
}
}
// 记录相对路径 Path
this.relativeUrl = value;
// 获取参数化的 Path 的名字。
this.relativeUrlParamNames = parsePathParameters(value);
}
这里主要做了以下的事情:
- 如果已经设置过了
HttpMethod
,报错。 - 如果
Query
参数后有参数化的Path
设置,报错。 - 记录相对路径
Path
和解析Path
参数化的名字。
可能你有点忘了什么是参数化的 Path
,我这里简单介绍一下,parseHttpMethodAndPath()
中只能处理相对路径 Path
,比如 /a/b/c
就是一个相对路径,参数化就是比如我定义的 Path
是这样的 /a/{name}/c
,其中的 {name}
就是一个占位符,可以通过参数中的注解 @Path
来替换它(后续的文章中会看到它),比如我的参数 name
是 tans
,那么请求是的 Path
就是 /a/tans/c
。
上面的 Path
是可以为空的,如果为空就需要参数的注解中有 @Url
(后续的文章中会看到它),它是来表示一个绝对路径的,比如 https://www.tans.com/a/b/c
。相对路径和绝对路径只能有一个存在。
我们再看看 parseHeaders()
方法的实现:
Java
private Headers parseHeaders(String[] headers) {
Headers.Builder builder = new Headers.Builder();
for (String header : headers) {
int colon = header.indexOf(':');
if (colon == -1 || colon == 0 || colon == header.length() - 1) {
throw methodError(
method, "@Headers value must be in the form \"Name: Value\". Found: \"%s\"", header);
}
// 获取 Header 的名字和对应的 Value
String headerName = header.substring(0, colon);
String headerValue = header.substring(colon + 1).trim();
// 如果是 Content-Type 记录一下
if ("Content-Type".equalsIgnoreCase(headerName)) {
try {
contentType = MediaType.get(headerValue);
} catch (IllegalArgumentException e) {
throw methodError(method, e, "Malformed content type: %s", headerValue);
}
} else {
// 添加到 Header.Builder 中。
builder.add(headerName, headerValue);
}
}
return builder.build();
}
Http
协议的 Header
的 Name
和 Value
都是以 :
分割开,上面的解析代码也是非常的简单,就不多说了。
最后
本篇文章中介绍了 Retrofit
的动态代理实现、方法注解解析等等内容,后续的文章还会继续介绍方法参数和参数注解的解析,请求任务的解析等等逻辑。