共计 11349 个字符,预计需要花费 29 分钟才能阅读完成。
最近回归看了一下 Retrofit
的源码,次要是因为我的项目接入了协程,所以想钻研一下 Retorift
是如何反对协程的。Retrofit
是在 Version 2.6.0
开始反对协程的,所以本篇文章无关 Retrofit
的源码都是基于 2.6.0
的。
舒适提醒,如果有
Retrofit
的源码浏览教训,浏览这篇文章将会轻松很多。
<!– 释怀你没有进错房间,这不是剖析协程的文章,只是刚好谈到协程,所以还是简略说下 Retrofit
的实现。
不感兴趣的能够间接跳过上面的小插曲,释怀不影响后续的浏览。–>
Retrofit
置信老鸟都应该很分明,Retrofit
外围局部是 create()
办法返回的动静代理(这里就不具体阐明了,之后会有专门的文章剖析动静代理)。就是上面这段代码:
public <T> T create(final Class<T> service) {Utils.validateServiceInterface(service);
if (validateEagerly) {eagerlyValidateMethods(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];
@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);
}
if (platform.isDefaultMethod(method)) {return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
第一眼看,跟我之前印象中的有点区别 (也不晓得是什么版本),return
的时候竟然没有 adapt
办法了。开始还认为有什么重大的扭转,其实也没什么,只是将之前的 adapt
办法封装到 invoke
办法中。
相干的 method 注解解析都放到 ServiceMethod
中,有两个要害函数调用,别离是 RequestFactory
与HttpServiceMethod
的 parseAnnotations()
办法。
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.");
}
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<String, String>): NewsResponse
这是一个规范的协程写法,而后咱们再套用下面的条件,发现齐全匹配不到。
因为,这是不协程的本来面目。咱们思考一个问题,为什么应用协程要增加 suspend
关键字呢?这是重点。你能够多想几分钟。
(几分钟之后 …)
不吊大家胃口了,我这里就间接说论断。
因为在代码编译的过程中会主动为带有 suspend
的函数增加一个 Continuation
类型的参数,并将其增加到最初面。所以下面的协程真正的面目是这样的:
@GET("/v2/news")
fun newsGet(@QueryMap params: Map<String, String>, c: Continuation<NewsResponse>): NewsResponse
当初咱们再来看下面的条件,发现可能全副合乎了。
因为篇幅无限,无关协程的原理实现就点到为止,后续我会专门写一个协程系列,心愿到时可能让读者们意识到协程的真面目,大家能够期待一下。
当初咱们曾经晓得了 Retrofit
如何判断一个办法是否应用了协程。那么咱们再进入另一个点:
Retrofit
如何将 Call
间接转化为 NewResonse
,简略的说就是反对使newsGet
办法返回 NewsResponse
。而这一步的转化在HttpServiceMethod
中。
HttpServiceMethod
下面曾经剖析完 RequestFactory
的parseAnnotations()
,当初再来看下 HttpServiceMethod
中的parseAnnotations()
。
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(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<T>.
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<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
// 5. 创立 Converter,将响应的数据转化成对应的 model 类型
Converter<ResponseBody, ResponseT> responseConverter =
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<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
// 8. 接口办法是协程,同时返回类型是 body,即自定义的 model 类型
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
代码中曾经解析的很分明了,须要留神 3,如果是协程会做两步操作,首先将接口办法的返回类型伪装成 Call
类型,而后再将 SkipCallbackExecutor
手动增加到 annotations
中。字面意思就在后续调用 callAdapter.adapt(call)
时,跳过创立 Executor
,简略了解就是协程不须要Executor
来切换线程的。为什么这样?这一点先放这里,后续创立 Call
的时候再说。
咱们间接看协程的 7,8 局部。7 也不详细分析,简略提一下,它就是返回一个 Response<T>
的类型,这个 Retrofit
最根本的反对了。至于如何在应用协程时将 Call<T>
转化成 Response<T>
原理与 8 基本相同,只是比 8 少一步,将它的 body
转化成对应的返回类型model
。所以上面咱们间接看 8。
将 Call 转化成对应的 Model
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
private final boolean isNullable;
SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) {super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
this.isNullable = isNullable;
}
@Override protected Object adapt(Call<ResponseT> call, Object[] args) {
// 1. 获取适配的 Call
call = callAdapter.adapt(call);
//noinspection unchecked Checked by reflection inside RequestFactory.
// 2. 获取协程的 Continuation
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
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<Foo> or Call<? extends Foo>");
}
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<Object, Call<?>>() {@Override public Type responseType() {return responseType;}
@Override public Call<Object> adapt(Call<Object> call) {
// 3. 最终调用 adapt 时候返回的就是它自身的 Call,即不须要进行适配。return executor == null
? call
: new ExecutorCallbackCall<>(executor, call);
}
};
}
代码正文曾经将之前 3 的作用解释完了,咱们回到 SuspendForBody
的adpt
,再看第二步。相熟的一幕,又用到了最初的一个参数。这里的 isNullable
目前 Retrofit
的版本都是false
,可能后续会反对空类型。但当初必定是不反对的,所以间接进入KotlinExtensions.await()
suspend fun <T : Any> Call<T>.await(): T {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {cancel()
}
enqueue(object : Callback<T> {override fun onResponse(call: Call<T>, response: Response<T>) {if (response.isSuccessful) {
// 1. 拿到 body
val body = response.body()
if (body == null) {val invocation = call.request().tag(Invocation::class.java)!!
val method = invocation.method()
val e = KotlinNullPointerException("Response from" +
method.declaringClass.name +
'.' +
method.name +
"was null but response body type was declared as non-null")
// 2. body 为空,唤起协程,抛出异样
continuation.resumeWithException(e)
} else {
// 3. 唤起协程,返回 body
continuation.resume(body)
}
} else {
// 4. 唤起协程,抛出异样
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
// 5. 唤起协程,抛出异样
continuation.resumeWithException(t)
}
})
}
}
看到这段代码,可能有的读者很相熟,曾经明确了它的转化。因为在 Retrofit
之前的几个版本,如果应用协程是不反对接口办法间接返回 model
的,须要返回 Call<T>
类型的数据。所以过后有的开源我的项目就是通过这个相似的 extensions
办法来转化成对应的model
。
遗憾的是,就是应用了 Retrofit
的Version 2.6.0
之后的版本,我还是看到有的人应用这一套来本人转化,心愿看到这篇文章的读者不要再做反复的事件,将其交给 Retrofit
本身来做就能够了。
下面的 extensions
作用就一个,通过 suspendCancellableCoroutine
来创立一个协程,它是协程中几个重要的创立协程的办法之一,这里就不细说,后续开协程系列在具体阐明。
次要是 onResponse
回调,协程通过挂起来执行耗时工作,而胜利与失败会别离通过 resume()
与resumeWithExecption()
来唤起挂起的协程,让它返回之前的挂终点,进行执行。而 resumeWithExecption()
外部也是调用了 resume()
,所以协程的唤起都是通过resume()
来操作的。调用 resume()
之后,咱们能够在调用协程的中央返回申请的后果。那么一个完满的协程接口调用就是这样实现的。
嗯,完结了,整顿一下也不是很简单吧。之后应用 Retroift
写协程时将通顺多了。
明天就这样吧,协程局部就剖析到这里,Retrofit
的整个协程实现局部就剖析完结了,我将关键点都特地进行了标注与阐明,心愿对剖析 Retrofit
的协程实现有所帮忙。
最初,感激你的浏览,如果你有工夫的话,举荐带着这篇文章再去浏览一下源码,你将会有更粗浅的印象。
我的项目
android_startup: 提供一种在利用启动时可能更加简略、高效的形式来初始化组件,优化启动速度。不仅反对 Jetpack App Startup
的全副性能,还提供额定的同步与异步期待、线程管制与多过程反对等性能。
AwesomeGithub: 基于 Github
客户端,纯练习我的项目,反对组件化开发,反对账户明码与认证登陆。应用 Kotlin
语言进行开发,我的项目架构是基于 Jetpack&DataBinding
的MVVM
;我的项目中应用了 Arouter
、Retrofit
、Coroutine
、Glide
、Dagger
与Hilt
等风行开源技术。
flutter_github: 基于 Flutter
的跨平台版本 Github
客户端,与 AwesomeGithub
绝对应。
android-api-analysis: 联合具体的 Demo
来全面解析 Android
相干的知识点, 帮忙读者可能更快的把握与了解所论述的要点。
daily_algorithm: 每日一算法,由浅入深,欢送退出一起共勉。
为本人代言
微信搜寻公众号:【Android 补给站】或者扫描下方二维码进行关注