在上一篇 Retrofit 源码深入分析 —— Call 对象的诞生与请求 的文章中我们基本把 Retrofit 从如何构建一个请求到返回响应的整个过程都梳理了一遍,对 Retrofit 的基本工作原理有了一个完整的了解。按照文章的完成度来说,上一篇文章基本把 Retrofit 讲的差不多了,但笔者还是想把日常普遍使用的几种方式都梳理一遍,让两篇文章对 Retrofit 的分析更加完整。
本篇文章其实按理来说应该整合到上一篇中,但这样让本就有点长的文章变得更长,对于阅读来说可能会很累,而对于笔者来说无论是写还是校对也很累,索性单开一篇。而且也并不会妨碍彼此的连贯性。
让我们看看如何用 RxJava 的方式进行请求,还是用官方 sample 的例子
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> contributors(@Path("owner") String owner,@Path("repo") String repo);
}
GitHub github = retrofit.create(GitHub.class);
Observable<List<Contributor>> observable = github.contributors("square", "retrofit");
observable.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Contributor>>() {
@Override
public void onCompleted() {}
@Override
public void onError(Throwable throwable) { }
@Override
public void onNext(List<Contributor> contributors) {
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
});
上述是一个标准的支持 RxJava 的请求步骤,这里与默认的请求最大的区别除了请求的过程不同外,返回的类型由原来的 Call.class 类型变为了 Observable.class 类型也就是一个被观察者对象,所以很明显 RxJavaCallAdapterFactory 内部帮我门做了某种转换,至于注解的解析过程都是一样的,这里不在赘述。
让我们再次延续上篇文章的 6.1 小节部分。回到创建 CallAdapter 的地方,也就是 createCallAdapter 方法,上篇文章对这个方法已经进行了描述,所以废话不多说让我们直接进入 RxJavaCallAdapterFactory 的 get 方法
@Override public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
Class<?> rawType = getRawType(returnType);
boolean isSingle = rawType == Single.class;
boolean isCompletable = rawType == Completable.class;
if (rawType != Observable.class && !isSingle && !isCompletable) {
return null;
}
if (isCompletable) {
return new RxJavaCallAdapter(Void.class, scheduler, isAsync, false, true, false, true);
}
boolean isResult = false;
boolean isBody = false;
Type responseType;
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawObservableType = getRawType(observableType);
//Model 不可声明为 Retrofit 的Response 类型
if (rawObservableType == Response.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Response must be parameterized"
+ " as Response or Response extends Foo>" );
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
} else if (rawObservableType == Result.class) { //也不可声明为 RxJava 包下的 Result 类型
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Result must be parameterized"
+ " as Result or Result extends Foo>" );
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
isResult = true;
} else {
responseType = observableType;
isBody = true;
}
return new RxJavaCallAdapter(responseType, scheduler, isAsync, isResult, isBody, isSingle,
false);
RxJavaCallAdapterFactory 的 get 方法的逻辑还是很清晰的,首先创建 RxJavaCallAdapter 的前置条件必须为 Observable、Single 和 Completable 而如果是 Completable 只接创建一个返回类型为 Void 的 adapter, 至于 Single 和 Completable ,前者在 RxJava 中代表只能处理一次事件,即只能发射单个数据或错误事件。而 Completable 正如它的名字,它不负责发送数据,只会处理 Rxjava 的 conComplete 和 onError 事件。
RxJavaCallAdapter 创建完成后就和 Call 对象的诞生流程差不多了。所以让们直接进入其中的 adapt 方法看看 Observable 的真面目
//形参为 OkHttpCall 创建的过程请看上一篇文章
@Override public Object adapt(Call<R> call) {
OnSubscribe<Response<R>> callFunc = isAsync
? new CallEnqueueOnSubscribe<>(call)//异步,将 OkHttpCall 对象传入
: new CallExecuteOnSubscribe<>(call);//同步,将 OkHttpCall 对象传入
OnSubscribe<?> func;
if (isResult) {//声明类型不是的 RxJava 包下的 Result 类型 默认 false
func = new ResultOnSubscribe<>(callFunc);
} else if (isBody) { //默认 true
func = new BodyOnSubscribe<>(callFunc);
} else {
func = callFunc;
}
//创建一个被观察对象
Observable<?> observable = Observable.create(func);
if (scheduler != null) {
observable = observable.subscribeOn(scheduler);
}
if (isSingle) {//返回一个 Single 事件
return observable.toSingle();
}
if (isCompletable) {//返回一个 toCompletable 事件
return observable.toCompletable();
}
return observable;
}
注释已经进行了详细的解释就不多说了,由于我们使用的是异步请求,所以肯定创建的是 CallEnqueueOnSubscribe 对象,该对象实现了 OnSubscribe 接口并重写了 call 方法,代码如下
@Override public void call(Subscriber<? super Response<T>> subscriber) {
// Since Call is a one-shot type, clone it for each new subscriber.
Call<T> call = originalCall.clone();
final CallArbiter<T> arbiter = new CallArbiter<>(call, subscriber);
subscriber.add(arbiter);
subscriber.setProducer(arbiter);
call.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, Response<T> response) {
arbiter.emitResponse(response);
}
@Override public void onFailure(Call<T> call, Throwable t) {
Exceptions.throwIfFatal(t);
arbiter.emitError(t);
}
});
}
代码并不复杂,可以看到内部还是委托 OkHttpCall 的 enqueue 方法来获取请求的 response ,在经过 GsonConvertAdapter 转换后,将该 Response 发射出去。那么到这里整个 RxJava 的支持过程就清晰了。
首先当我们把接口的返回类型声明为 Observable 类型时,会通过 RxJavaCallAdapterFactory 的 get 方法来帮我们创建一个 RxJavaCallAdapter 对象,RxJavaCallAdapter 的 adapt 方法中会帮我们创建一个被观察者对象返回。而当我们通过如下代码发起一个订阅
observable.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer());
订阅完成后,CallEnqueueOnSubscribe 对象的 call 方法就会被触发,剩下的就是在 onNext 方法中接收 Resposne 了。
从整体的处理环节来看,除了 RxJava 本身的特性之外,基本的逻辑和默认的 Call 类型的处理是 差不多的。因为都是遵循的同一套接口标准。所以如果你不喜欢默认的类型或 Rxjava,完全可以按照 Retorfit 的标准建立一套自己的 callAdapter。
协程作为近几年很火的异步框架,其简便的异步操作方式可以说但凡用过的人没有不喜欢的,其热度在 Android 领域已经有超过 RxJava 的趋势。而 Retrofit 自然不会甘于人后,在 2.6.x 版本以上也对协程进行了支持。
如果你还不了解协程,建议先 Google/Baidu 查询一下相关文章了解一番,否则可能无法无法愉快的阅读。
接下来让还是让我们以官方 Sample 为例看看 Retrofit 在 Android 开发中使用协程的基本流程(下面的步骤是 Kotlin 代码)
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//Android 协程依赖库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// 包含协程的 Activity lifecycle 扩展
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
val API_URL = "https://api.github.com"
val retrofit = Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
interface Github {
@GET("/repos/{owner}/{repo}/contributors")
suspend fun contributors(
@Path("owner") owner: String,
@Path("repo") repo: String) : List<Contributor>
}
val github = retrofit.create(Github::class.java)
lifecycleScope.launch{
val contributors = github.contributors("square", "retrofit")
for ((login, contributions) in contributors) {
println("$login ($contributions)")
}
}
上述流程算是在 Android 中的常用步骤,但在实际使用中 lifecycleScope.launch 会单独封装不会像上面那样单独拿出来使用。
简单了解了上面的流程后,我们来看看 Retrofit 是如何对携程进行支持的。
要想知道这些让我们回到创建 RequestFactory 的地方(上一篇文章第五节)
RequestFactory build() {
//解析接口方法的注解,列如 @GET @POST @Headers 的值等等...
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
//省略一些判断代码...
//解析形参的注解并获取注解对应的 value
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
//省略一些判断代码...
return new RequestFactory(this);
}
这里我们重点要关注的是解析形参的部分也就是 parseParameter 方法,该方法的完整签名如下
/*
* @p 传入的循环索引
* @parameterType 参数类型 例如 String Int
* @annotations 参数注解数组 例如 @Path @Query
* @allowContinuation 是否使用了协程
*/
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
了解了上面的内容,我们现在再看那个循环解析参数注解的逻辑,如果对协程不了解的话阅读这段代码的时候你可能会很疑惑,为什么当索引 P == lastparameter 的时候就可以允许支持协程了, 而且还有一个重要的布尔字段那就是 isKotlinSuspendFunction 的改为 true 的过程,可能看了也会让你迷惑,让我们进入 parseParameter 方法看看关键代码
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
ParameterHandler<?> result = null;
//省略参数解析代码...
if (result == null) {
if (allowContinuation) { //当 allowContinuation 为 true 也就是最后一个参数的索引和注解数组长度相等
try {
//最后一个参数类型为 Continuation.class 类型 isKotlinSuspendFunction 为true
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
throw parameterError(method, p, "No Retrofit annotation found.");
}
return result;
}
可以看到表面逻辑倒也没什么难以理解的,就是判断参数是否为最后一个并判断最后一个参数是否为 Continuation.class ,奇怪了,我们并未声明 Continuation.class 类型的参数,它判断个什么?要想解答这些困惑,我们需要对 suspend 函数进行一下反编译来看看它在 java 中是个什么样子
//kotlin 代码
@GET("/repos/{owner}/{repo}/contributors")
suspend fun contributors(
@Path("owner") owner: String,
@Path("repo") repo: String) : List<Contributor>
//反编译后的 Java 代码
@GET("/repos/{owner}/{repo}/contributors")
@Nullable
Object contributors(
@Path("owner") @NotNull String var1,
@Path("repo") @NotNull String var2,
@NotNull Continuation var3);
看了上面的反编译代码是不是有点恍然大明白的感觉,原来 suspend 转换为 java 代码后返回的类型变为了最原始的 Object ,同时形参自动增加了一个 Continuation.class 类型的参数,至此也就明白了为什么要判断最后一个参数为 Continuation.class 类型才能把 isKotlinSuspendFunction 字段改为 true 的处理逻辑。
上篇文章中笔者故意将协程相关代码隐藏,主要是为了专注非协程情况下的源码分析,而本节将会重点展示协程相关代码,以梳理 Retrofit 对协程的支持过程
上一节中我们已经明白了 isKotlinSuspendFunction 字段的处理逻辑,现在让我们把视线转到 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;
//是否为 Suspend 挂起函数,也就是 Kotlin 的协程
if (isKotlinSuspendFunction) {
//获取所有参数类型
Type[] parameterTypes = method.getGenericParameterTypes();
//获取参数类型的下边界
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
//获取类型全限定类名并判断是否为 Response 类型
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true; //协程返回类型为 Response 类型标记位 true
} else {
//省略...
}
//获取要适配的 call 类型 ,其实就是创建了一个 ParameterizedType 的实例,类型为 call
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
//这里调用了一个实现了 SkipCallbackExecutor 注解的类,
//ensurePresent 方法主要作用是将原注解数组替换为 SkipCallbackExecutor 注解
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
//获取要适配的 call 类型
adapterType = method.getGenericReturnType();
}
//省略若干代码...
//获取 OkHttpClient 实例
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//返回的是 Response 类型
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//返回的是 Body
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
注释解释的很清楚了,这里我们重点关注一下 SkipCallbackExecutorImpl.ensurePresent() 方法,代码如下
static Annotation[] ensurePresent(Annotation[] annotations) {
if (Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)) {
return annotations;
}
//创建一个新的注解数组
Annotation[] newAnnotations = new Annotation[annotations.length + 1];
// 利用系统深拷贝将原数组注解类型替换为 SkipCallbackExecutor 注解
newAnnotations[0] = SkipCallbackExecutorImpl.INSTANCE;
System.arraycopy(annotations, 0, newAnnotations, 1, annotations.length);
return newAnnotations;
}
看完上面的代码你可能会觉得这个 SkipCallbackExecutor 似曾相识,还记得 DefaultCallAdapterFactory 的 get 方法吗
@Override
public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
//省略若干代码...
//这里其实主要的作用是为了判断是否使用了协程,如果实现了协程那么则不使用系统的回调线程并返回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) {
return executor == null
? call // OkHttpCall 对象
: new ExecutorCallbackCall<>(executor, call);
}
};
}
如此 SkipCallbackExecutor 注解的作用就清晰了,首先当为 Suspend 函数时,Retrofit 会将原注解进行深拷贝变为 SkipCallbackExecutor 注解类型,然后会在创建 Call 对象的时候判断,如果符合条件则不用默认的 Executor , 直接通过 OkHttpCall 进行 async / enqueue 请求。
接下来就是协程和非协程返回对象的区别,从上面的代码中我们可以很清晰的看到,对于协程返回有两种Response 类型,一种为自定义的 Model , 一种为 responseBody 类型,分别对应 SuspendForResponse 和 SuspendForBody 对象。而两者和非协程环境下的 CallAdapted 对象相同,都是 HttpServiceMethod 的子类,所以无论哪种环境,抛开其它因素,两者的调用过程都是相同的,即先调用 invoke 方法创建 OkHttpCall 对象,在调用 adapt 方法进行具体的请求,SuspendForResponse 类的 adapt 方法如下
/*
* @call 是 OkHttpCall 对象
* @args 是我们请求的参数 就是 github.contributors("square", "retrofit")
*/
@Override
protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
//获取参数列表中的最后一个 continuation 类型参数 ,详看上面 suspend 反编译成 Java 代码
Continuation<Response<ResponseT>> continuation =
(Continuation<Response<ResponseT>>) args[args.length - 1];
// 调用 Kotlin 扩展函数
try {
return KotlinExtensions.awaitResponse(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
在 Retrofit 源码中有一个 KotlinExtensions.kt 文件,是一个 Kotlin 的扩展文件,专门用来处理 suspend 函数,我们进入 awaitResponse 函数看看
suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
//调用 OkHttpCall 的 enqueue 方法获取转换后的响应(和上篇文章中的过程是一样的)
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
//唤醒挂起函数,返回 response
//表现形式看开头通过协程获取 model 的代码
continuation.resume(response)
}
override fun onFailure(call: Call<T>, t: Throwable) {
//唤醒挂起函数,抛出异常
continuation.resumeWithException(t)
}
})
}
}
注释解释的很清楚了,其中 suspendCancellableCoroutine 是创建协程的方式之一,感兴趣的可以了解下。至于 SuspendForBody 对象,它和 SuspendForResponse 的处理过程是一样的,这里就不多说了。
所以回顾整个流程对于 suspend 函数的处理步骤如下
如果有些地方还是不明白可以跟着笔者的步骤跟进源码一步步看,这样会更清晰。
终于,用了两篇文章将 Retrofit 分析完了,没有拉下任何一个常用的关键步骤,可以说是非常全面的分析了。而对于自己来说也有种难言的收获感,文章很长,代码也是反复看了很久很久,而过程中的抓耳挠腮,反复 debug, 在理解完最后一行源码的时候,顿时有一种通透的舒爽感,久久不能平静,这里也吐槽一下自己,其实关于 Retrofit 的文章在前几年就该写出来,但一直拖到了现在,再次感到自己是真的懒,以后尽量勤快点吧!