「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
自Retofit 2.6.0 版本之后就默认只是 kotlin 协程了。使用起来也很方便。只有在函数前面加上suspend 就可以了。
interface ApiService {
@GET("article/list/{page}/json")
suspend fun getArticleList(@Path("page") page:Int): Result<PageEntity<Article>>
}
简单回顾一下使用
val retrofit= Retrofit.Builder()
.baseUrl("https://www.wanandroid.com")
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.build()
val apiService = retrofit.create(ApiService::class.java)
lifecycleScope.launch {
val result = apiService.getArticleList(1)
}
返回结果直接的我们想要的结果,不需要 Call
包裹了。那 Retofit 是如何做到的呢?
简单的源码分析
关于 Retofit 的源码分析网上有很多,我在这里就不详细介绍了。
方法入口 Retofit#create
查看Retofit的create方法发现有一个动态代理,当调用接口方法时就会触发InvocationHandler的invoke方法。
public <T> T create(final Class<T> service) {
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];
@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);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)//java1.8 接口可以有默认实现
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
方法最后的loadServiceMethod(method)
会调用 ServiceMethod
的 parseAnnotations()
方法。
ServiceMethod.java 源码
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// 0️⃣ 构建请求工厂
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.");
}
// 1️⃣
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract @Nullable T invoke(Object[] args);
}
在 0️⃣ 处 构建 RequestFactory
对象时,会调用RequestFactory
的 build()
方法, 在这个方法中有这么一段代码 遍历解析方法参数
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);
}
找到了 isKotlinSuspendFunction
parseParameter 方法部分代码
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
// 省略部分代码……
if (result == null) {
//0️⃣ 是否为最后一个参数
if (allowContinuation) {
try {
//1️⃣
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
throw parameterError(method, p, "No Retrofit annotation found.");
}
return result;
}
注释 0️⃣ 处allowContinuation
是的值是遍历解析调用时传入的 p == lastParameter
也就是判断是否是最后一个参数,如果是最后一个参数,那就判断这个参数的类是否是Continuation.class
(kotlin.coroutines包下的类)如果是就把 isKotlinSuspendFunction
标记为 true
,这里似乎是找到关于kotlin 协程相关的东西了,这把一个是否支持Kotlin suspend 函数 的遍历变成了true。
kotlin 的黑魔法
为什么判断最后一个参数是 Continuation.class
就把 isKotlinSuspendFunction
标记为 true
呢? 我们写方法的时候没有加上Continuation类型参数呀?
那这就说说Kotlin 的黑魔法了,kotlin 之所以能让我写很简洁的代码,是因为kotlin 编译器帮我们“写”了很多代码。
我们通过 AS 提供的 kotlin 的字节码工具反编译得到结果如下
//kotlin
@GET("article/list/{page}/json")
suspend fun getArticleList(@Path("page") page:Int): Result<PageEntity<Article>>
-------------------------------------------------------------------------
//kotlin字节码 反编译后的java代码
@GET("article/list/{page}/json")
@Nullable
Object getArticleList(@Path("page") int var1, @NotNull Continuation<Result<PageEntity<Article>>> var2);
发现如果函数使用 suspend 的话,编译之后就会在参数类表最后添加一个 Continuation 类型的参数。Continuation的泛型类型就是原函数的返回值类型,反编译后的java代码是看不到的,直接看kotlin 字节码是可以看到这个方法签名是这样的。kotlin协程的 Continuation 相当于回调,返回函数的返回值就是在回调是返回的。
isKotlinSuspendFunction 在哪里使用的
明白了 isKotlinSuspendFunction
判断的依据那么继续往下看吧。
我们再回到ServiceMethod的parseAnnotations的方法,方法的最后调用的HttpServiceMethod的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;
//0️⃣ 根据是否是kotlin suspend 来处理
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
//1️⃣ ⬇️
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {//2
// 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
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
// 省略部分代码……
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
//2️⃣ ⬇️
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
//3️⃣ ⬇️
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
在注释 0️⃣ 处 根据 isKotlinSuspendFunction
来判断是否按kotlin suspend 函数处理,上面我们知道suspend 函数的返回值类型,最终会变成最后一个参数 Continuation
的泛型类型,在注释 1️⃣ 处判断最后一个参数的泛型是不是 Response
类的,如果是就把 continuationWantsResponse
标记为true
。
在注释 2️⃣ 和注释 3️⃣ 处根据 continuationWantsResponse 创建不同的对象。看到这我们可以知道,suspend 函数应该还有一种写法,那就是返回值用Response包裹。
@GET("article/list/{page}/json")
suspend fun getArticleList1(@Path("page") page:Int): Response<Result<PageEntity<Article>>>
上面是另外一种写法,我们还是继续分析返回值不带 Response 的,那就分析 3️⃣ 处的情况吧,也就是SuspendForBody。
支持协程的奥秘
SuspendForBody
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) {
call = callAdapter.adapt(call);
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
try {
return isNullable
? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);
} catch (Exception e) {
return KotlinExtensions.yieldAndThrow(e, continuation);
}
}
}
根据返回值是否为空调用不同的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) {
val body = response.body()
if (body == null) {
//省略部分代码
continuation.resumeWithException(e)
} else {
continuation.resume(body)
}
} else {
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
它是一个 Call 的扩展函数 suspendCancellableCoroutine
函数中帮我调用了 enqueue 函数,在它的onResponse 回调中调用 continuation.resume(body)把请求的数据返回给我们。
还有个问题上面SuspendForBody
的 adapt
是怎么调用的呢?
我们从头再看看当接口方法调用是触发的那个方法吧,在InvocationHandler 的invoke的方法最后一句是
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
我们上面分析了那么多我们只是分析了loadServiceMethod(method)方法执过程,此时loadServiceMethod(method)的返回值就是 SuspendForBody 对象,SuspendForBody 对象调用 invoke方法最终就调用了adapt方法。
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
以上就是Retofit协程请求方式的大致过程。
扩展——回调变协程
上面我们看到 Retofit 是利用 kotlin 提供的suspendCancellableCoroutine
函数实现的对kotlin 协程的支持。kotlin 协程一个很好的用处就是解决回调地狱,让异步代码变成“同步代码”。对于 Retofit 支持kotlin 协程,那对于那些不支持的咋办。我们可以仿照 Retofit 的写法,让回调变协程。
比如我们让 OKHTTP 支持协程。我们可以写一个Okhttp的 Call 的扩展。
suspend fun Call.awaitResponse(): Response {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation { cancel() }
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
})
}
}
使用
lifecycleScope.launch {
val request = Request.Builder().url("https://www.wanandroid.com").get().build()
val response = okHttpClient.newCall(request).awaitResponse()
if (response.isSuccessful){
//TODO
}
}
任何回调,都可以通过这种方式变成支持协程,如果遇到回调嵌套的话,可以考虑用这个方式优化。