(众所周知)Retrofit从2.6.0开始支持协程。
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
使用
1.定义retrofit。
val retrofit: Retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.Builder().build())
.baseUrl("")
.build()
2.定义请求接口方法。
//示例接口
@GET("api/test")
suspend fun testApi(): MyResponse
3.然后在协程作用域中调用testApi()方法,可以成功获取结果。但是,如果testApi()方法没有使用suspend关键字修饰,则会报错:
Unable to create call adapter for com.package.name.net.MyResponse
for method ServiceApi.testApi
4.从错误信息可以知道,请求是成功发起并返回了的,只是在转换结果对象的时候报错了。由此可以提出问题。
一 . 提出问题
为什么suspend关键字会影响返回结果bean的转换?既是:Retrofit是如何支持Kotlin协程的?
二 . 源码追溯
1.全局搜索“Unable to create call adapter for”,找到:
private static CallAdapter createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
try {
//noinspection unchecked
return (CallAdapter) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create call adapter for %s", returnType);
}
}
再追溯“createCallAdapter”找到:
static HttpServiceMethod parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
......
if (isKotlinSuspendFunction) {
......
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
//从这里报的错误
CallAdapter callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
......
if (!isKotlinSuspendFunction) {
//如果不是suspend函数,则返回类型需要是retrofit2.Call。
//代码走到这里就结束了。retrofit2并没有为我们发起网络请求。
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}else if (continuationWantsResponse) {
//是suspend函数的处理,retrofit2执行了网络请求并返回了结果
//返回完整的response
return (HttpServiceMethod)
new SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter>) callAdapter);
} else {
//是suspend函数的处理,retrofit2执行了网络请求并返回了结果
//返回response里面的body部分
......
}
}
1.判断是否为suspend函数;
2.检查不同情况下,参数类型和返回类型是否正确;
3.retrun 结果;
判断是否为挂起函数:
private @Nullable ParameterHandler> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
......
//获取接口方法参数最顶层的type类型。
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
......
}
这里的判断依据,跟suspend关键字有关。
使用studio自带的kotlin工具反编译查看java代码可以发现:
@GET("api/test")
@Nullable
Object testApi(@Query("test") @test Integer var1, @NotNull Continuation var2);
用suspend关键字修饰的方法,在参数里面自动添加了Continuation参数。
所以判断方法是否有一个Continuation参数就可以判断是否用suspend修饰了。
Continuation参数可以理解为一个回调。
retrofit2帮我们发起网络请求:
static final class SuspendForResponse extends HttpServiceMethod {
private final CallAdapter> callAdapter;
SuspendForResponse(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter responseConverter,
CallAdapter> callAdapter) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
@Override
protected Object adapt(Call call, Object[] args) {
call = callAdapter.adapt(call);
//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation> continuation =
(Continuation>) args[args.length - 1];
// See SuspendForBody for explanation about this try/catch.
try {
return KotlinExtensions.awaitResponse(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
}
//前面已经return了SuspendForResponse的构造方法。
//到这里,源码追溯到头了。
//再看adapt方法,必然会被调用,追溯adapt方法,找到一个唯一调用。
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
//追溯invoke方法,找到一个唯一调用。
public T create(final Class 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)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
再追溯create方法,就到我们自己定义的代码了。追溯“KotlinExtensions.awaitResponse”,再看refrofit2是怎么帮我们发起网络请求的:
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, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
1.首先这是一个retrofit2.Call,返回retrofit2.Response的suspend扩展方法。
该方法开启了一个可取消的协程挂起函数 suspendCancellableCoroutine。
该函数可以把异步回调的写法,封装转换为协程同步的写法。
这个挂起函数是使用Java的方式配合try catch调用的,既:
// See SuspendForBody for explanation about this try/catch.
try {
return KotlinExtensions.awaitResponse(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
这样就没有报:Suspend function 'xxx' should be called only from a coroutine or another suspend function.的错误。
2.调用了enqueue方法,发起了一个异步请求。这里的enqueue方法,就是对Okhttp的enqueue的封装。
实际调用到的是:retrofit2.OkHttpCall.enqueue(callBack)。
3.在得到结果后,通过“continuation.resume(response)”或者“continuation.resumeWithException(t)”恢复程序继续从这里执行,并返回结果。
4.无论接口方法是否由suspend关键字修饰,返回的结果,都经过了转换。
用到了定义retrofit时传入的GsonConverterFactory里面的GsonResponseBodyConverter。
try {
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {......}
总结
1.retrofit2封装了Okhttp,远程依赖只依赖retrofit就可以了。
2.使用suspend关键字时,retrofit2框架内部会帮我们判断接口方法的合法合规性,然后在内部开启一个可取消的协程 suspendCancellableCoroutine{} 去执行异步请求。
实际上还是调用的Okhttp Request Call enqueue 那一套。
3.所以,api接口方法如果不用suspend关键字修饰,则应该写成:
@GET("api/test")
fun testApi(@Query("test") test: Int?): retrofit2.Call>
//调用:
testService.testApi(0).enqueue(object :retrofit2.Callback>{
override fun onResponse(
call: Call>,
response: Response>,
) { }
override fun onFailure(call: Call>, t: Throwable) { }
})
使用suspend关键字修饰,则可以直接像同步方法一样调用:
suspend fun xxx() = testService.testApi(0)
4.retrofit2是怎么实现切换线程的呢?下一篇继续。