第一眼看,跟我之前印象中的有点区别(也不知道是什么版本),return
的时候居然没有adapt
方法了。开始还以为有什么重大的改变,其实也没什么,只是将之前的adapt
方法封装到invoke
方法中。
相关的method注解解析都放到ServiceMethod
中,有两个关键函数调用,分别是RequestFactory
与HttpServiceMethod
的parseAnnotations()
方法。
static
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
首先RequestFactory
中的parseAnnotations()
最终通过build()
方法来构建一个RequestFactory
,用来保存解析出来的方法信息。
RequestFactory build() {
//1.解析方法上的注解
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
...
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler>[parameterCount];
//2.循环遍历方法中的各个参数,解析参数的注解
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
...
return new RequestFactory(this);
}
可以看到主要分为两步:
- 通过
parseMethodAnnotation
来解析出请求的方式,例如GET
、POST
与PUT
等等;同时也会验证一些注解的合规使用,例如Multipart
与FormUrlEncoded
只能使用一个。 - 通过
parseParameter
来解析出请求的参数信息,例如Path
、Url
与Query
等等;同时也对它们的合规使用做了验证,例如QueryMap
与FieldMap
等注解它们的key
都必须为String
类型。这些注解的解析都是在parseParameterAnnotation()
方法中进行的。
上面的p == lastParameter
需要特别注意下,为何要专门判断该参数是否为最后一个呢?请继续向下看。
协程的判断条件
下面我们来着重看下parseParameter
的源码,因为从这里开始就涉及到协程的判断。
private @Nullable ParameterHandler> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
ParameterHandler> result = null;
if (annotations != null) {
for (Annotation annotation : annotations) {
//1.解析方法参数的注解,并验证它们的合法性
ParameterHandler> annotationAction =
parseParameterAnnotation(p, parameterType, annotations, annotation);
if (annotationAction == null) {
continue;
}
//每个参数都只能有一个注解
if (result != null) {
throw parameterError(method, p,
"Multiple Retrofit annotations found, only one allowed.");
}
result = annotationAction;
}
}
//2.判断是否是协程
if (result == null) {
if (allowContinuation) {
try {
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
throw parameterError(method, p, "No Retrofit annotation found.");
}
return result;
}
第一点没什么好说的,里面没什么逻辑,就是一个纯注解解析与Converter
的选取。
第二点是关键点,用来判断该方法的调用是否使用到了协程。同时有个allowContinuation
参数,这个是什么呢?我们向上看,发现它是方法中的一个参数,如果我们继续追溯就会发现它就是我们之前特意需要注意的p == lastParameter
。
所以判断是否是使用了协程有三步:
result
为空,即该参数没有注解allowContinuation
为true
,即是最后一个参数Continuation.class
,说明该参数的类型为Continuation
只有符合上述三点才能证明使用了协程,但脑海里回想一下协程的写法,发现完全对不到这三点...
到这里可能有的读者已经开始蒙圈了,如果你没有深入了解协程的话,这个是正常的状态。
别急,要理解这块,还需要一点协程的原理知识,下面我来简单说一下协程的部分实现原理。
suspend原理
我们先来看下使用协程是怎么写的:
@GET("/v2/news")
suspend fun newsGet(@QueryMap params: Map
这是一个标准的协程写法,然后我们再套用上面的条件,发现完全匹配不到。
因为,这是不协程的本来面目。我们思考一个问题,为什么使用协程要添加suspend
关键字呢?这是重点。你可以多想几分钟。
(几分钟之后...)
不吊大家胃口了,我这里就直接说结论。
因为在代码编译的过程中会自动为带有suspend
的函数添加一个Continuation
类型的参数,并将其添加到最后面。所以上面的协程真正的面目是这样的:
@GET("/v2/news")
fun newsGet(@QueryMap params: Map
现在我们再来看上面的条件,发现能够全部符合了。
由于篇幅有限,有关协程的原理实现就点到为止,后续我会专门写一个协程系列,希望到时能够让读者们认识到协程的真面目,大家可以期待一下。
现在我们已经知道了Retrofit
如何判断一个方法是否使用了协程。那么我们再进入另一个点:
Retrofit
如何将Call
直接转化为NewResonse
,简单的说就是支持使newsGet
方法返回NewsResponse
。而这一步的转化在HttpServiceMethod
中。
HttpServiceMethod
上面已经分析完RequestFactory
的parseAnnotations()
,现在再来看下HttpServiceMethod
中的parseAnnotations()
。
static
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
// 1. 是协程
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
// 2. 判断接口方法返回的类型是否是Response
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
// TODO figure out if type is nullable or not
// Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
// Find the entry for method
// Determine if return type is nullable or not
}
// 3. 注意:将方法返回类型伪装成Call类型,并将SkipCallbackExecutor注解添加到annotations中
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
// 4. 创建CallAdapter,适配call,将其转化成需要的类型
CallAdapter
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
// 5. 创建Converter,将响应的数据转化成对应的model类型
Converter
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
// 6. 接口方法不是协程
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
// 7. 接口方法是协程,同时返回类型是Response类型
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod
callFactory, responseConverter, (CallAdapter
} else {
// 8. 接口方法是协程,同时返回类型是body,即自定义的model类型
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod
callFactory, responseConverter, (CallAdapter
continuationBodyNullable);
}
}
代码中已经解析的很清楚了,需要注意3,如果是协程会做两步操作,首先将接口方法的返回类型伪装成Call
类型,然后再将SkipCallbackExecutor
手动添加到annotations
中。字面意思就在后续调用callAdapter.adapt(call)
时,跳过创建Executor
,简单理解就是协程不需要Executor
来切换线程的。为什么这样?这一点先放这里,后续创建Call
的时候再说。
我们直接看协程的7,8部分。7也不详细分析,简单提一下,它就是返回一个Response
的类型,这个Retrofit
最基本的支持了。至于如何在使用协程时将Call
转化成Response
原理与8基本相同,只是比8少一步,将它的body
转化成对应的返回类型model
。所以下面我们直接看8。
将Call转化成对应的Model
static final class SuspendForBody
private final CallAdapter
private final boolean isNullable;
SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Fac
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整资料开源分享
tory callFactory,
Converter
CallAdapter
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
this.isNullable = isNullable;
}
@Override protected Object adapt(Call
// 1. 获取适配的Call
call = callAdapter.adapt(call);
//noinspection unchecked Checked by reflection inside RequestFactory.
// 2. 获取协程的Continuation
Continuation
return isNullable
? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);
}
}
我们的关注点在adapt
,文章开头已经说了,新版的Retrofit
将adapt
隐藏到invoke
中。而invoke
中调用的就是这个adapt
。
首先第一步,适配Call
,如果是RxJava
,这里的callAdapter
就是RxJava2CallAdapter
,同时返回的就是Observable
,这个之前看过源码的都知道。
但现在是协程,那么这个时候的callAdapter
就是Retrofit
默认的DefaultCallAdapterFactory
@Override public @Nullable CallAdapter, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
// 1. 注意: 如果是协程,因为接口方法返回没有使用Call,之前3的第一步伪装成Call的处理就在这里体现了作用
if (getRawType(returnType) != Call.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call
}
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
// 2. 之前3的第二部就在这里体现,由于之前已经将SkipCallbackExecutor注解添加到annotations中,所以Executor直接为null
final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
return new CallAdapter
写在最后
本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!
最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:
对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!
Android架构师之路很漫长,一起共勉吧!
如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。