Retrofit 源码阅览笔记(一)
Retrofit
信任每一个 Android
开发者都对它十分了解,它使咱们调用 Http
恳求变得十分的简略(内部完结是 OkHttp
),咱们只需求界说一个接口,接口的每一个办法就表明一个 Http
恳求,办法的注解,参数的注解和参数他们共同来描绘了这一个 Http
恳求的 Request
,而办法的返回值描绘了如何处理 Http
的 Response
。经过 Retrofit#create()
办法就能够生成一个上面接口的完结类(经过动态署理完结),经过调用这个完结类的办法就能够完结一次 Http
恳求,一次网络恳求就好像调用了一次咱们的本地办法相同。刚开端触摸 Retrofit
的我,感觉这个真的是太神奇,工作多年后渐渐地我也知道了它的原理,所以这次文章来根据 Retrofit
的源码来记载一下它的完结办法。
本篇文章是系列文章的第一篇,我阅览的 Retrofit
源码版本是 2.9.0
。
动态署理创立接口完结类
创立咱们界说的接口完结类经过 Retrofit#create()
办法传一个接口的 Class
目标。咱们来看看它的源码:
@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
目标的:
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()
,看看代码完结:
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()
办法去创立,然后存放在缓存中,下次再运用就不需求再创立。
这儿有一个有意思的问题便是这儿的锁用的是 serviceMethodCache
,用的锁范围略微大了一点点,也就相当于所有的 Service
创立都会竞赛这个锁,这儿有相关的 Issue,这个锁的竞赛还造成了 ANR
。有一个大佬把上面的锁的目标修改成了 Service
的 Class
目标,这个问题就得到了很大的缓解。
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()
办法:
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
朴实无华的代码,直接创立一个 Builder()
目标,然后调用其 build()
办法,咱们看看 它的结构函数和 build()
办法的源码完结:
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()
办法的源码:
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()
办法的完结:
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()
办法的完结:
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
的动态署理完结、办法注解解析等等内容,后续的文章还会持续介绍办法参数和参数注解的解析,恳求使命的解析等等逻辑。