你在项目中采用retrofit搭配协程suspend时可能会遇到以下问题:
1,使用协程 suspend 时,我的方法返回值应该是 Response 还是 Body
//返回 response,对应Retrofit中的Response
@GET("/path")
suspend fun getDataWithResponse(): Response<ResponseBody<<Info>>>
//返回 body,对应下面自定义的ResponseBody
@GET("/path")
suspend fun getDataWithBody(): ReponseBody<List<Info>>
//自定义的通用服务端返回结构
class ResponseBody<T> {
var errorMsg: String? = null
var errorCode: Int? = 0
var data: T? = null
}
2,使用协程 suspend 时,方法内部抛出异常,除了 try catch ,有没有其他优雅的处理方式?
@GET("/path") //这里path设置一个错误路径
suspend fun getDataWithResponse(): Response<ResponseBody<<Info>>>
//获取数据
private fun getData() {
GlobalScope.launch {
val retrofit = Retrofit.Builder()
.baseUrl("http://www.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiProxy = retrofit.create(HotKeyApiInterface::class.java)
try {
//当请求url中的path错误时,这里会抛出HttpException异常
val response = apiProxy.getDataWithResponse()
} catch(e: Exception) {
}
}
}
下面将依次讲解:
- 协程 suspend 如何适配 Response
- 协程 suspend 如何适配 Body
- 协程 supspend 如何适配自定义 ApiResponse
协程 suspend 适配 Response
先看 Retrofit 中 suspend 结合 Response 的用法:
interface HotKeyApiInterface {
@GET("/hotey/json")
suspend fun getDataWithResponse(): Response<ResponseBody<List<Info>>>
}
private fun retrofitResponse() {
GlobalScope.launch {
val retrofit = Retrofit.Builder()
.baseUrl("http://www.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiProxy = retrofit.create(HotKeyApiInterface::class.java)
val response: Response<ResponseBody<List<Info>>> = apiProxy.getDataWithResponse()
val apiResult: ResponseBody<List<Info>>? = response?.body()
}
}
重点关注 apiProxy.getDataWithResponse() 是如何得到 response 的。
根据上一篇文章的分析,执行 apiProxy.getDataWithResponse() 时,由于 apiProxy 是动态代理类,所以会执行到 retrofit#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)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args); //关键点1
}
});
}
关键点1处:loadServiceMethod(method).invoke(args) 的执行结果就是 apiProxy.getDataWithResponse() 的结果,所以我们需要重点关注 loadServiceMethod(method).invoke(args) 的执行流程。
首先 loadServiceMethod(method) 方法返回的类型是 ServiceMethod,ServiceMethod有一个抽象实现类 HttpServiceMethod,如下:
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
}
HttpServiceMethod 有三个实现类:
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
}
static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
}
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
}
上面的 loadServiceMethod(method) 方法会根据 method,也就是我们定义的接口的方法信息,来决定返回的 ServiceMethod 是 CallAdapted、SuspendForResponse 或 SuspendForBody,具体判断方式为它会根据定义的方法的返回类型、是否带 suspend 等 来决定,如:
- 当方法返回Call,不带 suspend,则 loadServiceMethod(method) 返回 CallAdapter,如:
@GET("/hotkey/json")
fun getDataWithCall(): Call<ResponseBody<List<Info>>>
2,当方法带 suspend,返回 Response,则 loadServiceMethod(method) 返回 SuspendForResponse,如:
@GET("/hotey/json")
suspend fun getDataWithResponse(): Response<ResponseBody<List<Info>>>
- 当方法带 suspend,返回自定义数据 bean,则 loadServiceMethod(method) 返回 SuspendForBody,如:
@GET("/hotey/json")
suspend fun getDataWithResponseBody(): ResponseBody<List<Info>>
回到本节开头,我们定义的方法为:
@GET("/hotey/json")
suspend fun getDataWithResponse(): Response<ResponseBody<List<Info>>>
所以:上面的 loadServiceMethod(method) 的返回值 SuspendForResponse。
SuspendForResponse 就是解决 suspend 适配 Response 的,前面讲了 loadServiceMethod(method).invoke(args) 方法的返回值就是apiProxy.getDataWithResponse()的返回值,所以我们接着看SuspendForResponse的invoke方法:
//SuspendForResponse#invoke(实现在它的父类HttpServiceMethod)
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args); //关键点2
}
关键点2:看 SuspendForResponse#adapter:
@Override
protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<Response<ResponseT>> continuation =
(Continuation<Response<ResponseT>>) args[args.length - 1];
// See SuspendForBody for explanation about this try/catch.
try {
return KotlinExtensions.awaitResponse(call, continuation); //关键点3
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
}
关键点3:利用协程先挂起当前程序,然后在协程中调用 call#enqueue 执行异步请求,最终拿到结果后恢复执行。
//KotlinExtensions#awaitResponse方法
suspend fun <T> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
continuation.resume(response) //关键点4
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
关键点4处:将 Response 返回回去,就是我们 apiResponse.getDataWithResponse 的执行结果。
协程 suspend 适配 Body
接着看看 Retrofit 中 suspend 结合 Body 的用法:
interface HotKeyApiInterface {
@GET("/hotey/json")
suspend fun getDataWithBody(): ResponseBody<List<Info>>
}
private fun retrofitBody() {
GlobalScope.launch {
val retrofit = Retrofit.Builder()
.baseUrl("http://www.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiProxy = retrofit.create(HotKeyApiInterface::class.java)
val body: ResponseBody<List<Info>> = apiProxy.getDataWithBody()
}
}
根据前面一节的分析:apiProxy.getDataWithBody() 执行时,会执行到 InvocationHandler.invoke,然后执行到 loadServiceMethod(method).invoke(arg),而 suspend 搭配 Body 时 loadServiceMethod(method) 方法的返回类型为 SuspendForBody,所以 SuspendForBody.invoke 的执行结果就是 apiProxy.getDataWithBody() 的返回结果。
先看 SuspendForBody 的 invoke 方法:
//SuspendForBody#invoke(实现在HttpMethodService中)
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args); //关键点1
}
关键点1处,接着看 SuspendForBody 的 adapter 方法
@Override
protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
// Calls to OkHttp Call.enqueue() like those inside await and awaitNullable can sometimes
// invoke the supplied callback with an exception before the invoking stack frame can return.
// Coroutines will intercept the subsequent invocation of the Continuation and throw the
// exception synchronously. A Java Proxy cannot throw checked exceptions without them being
// declared on the interface method. To avoid the synchronous checked exception being wrapped
// in an UndeclaredThrowableException, it is intercepted and supplied to a helper which will
// force suspension to occur so that it can be instead delivered to the continuation to
// bypass this restriction.
try {
return isNullable
? KotlinExtensions.awaitNullable(call, continuation) //关键点2
: KotlinExtensions.await(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
关键点2处:先挂起当前函数,然后在协程中调用 Call#enqueue 执行异步请求,拿到返回结果后返回 reponse.body(),就是我们 apiProxy.getDataWithBody 的执行结果。
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) {
continuation.resume(response.body())
} else {
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T?>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
协程 Suspend 适配自定义 ApiResponse
我们用下面的方式进行网络请求,一切都正常:
interface HotKeyApiInterface {
@GET("/hotey/json") //关键点1
suspend fun getDataWithResponse(): Response<ResponseBody<List<Info>>>
}
private fun retrofitResponse() {
GlobalScope.launch {
val retrofit = Retrofit.Builder()
.baseUrl("http://www.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiProxy = retrofit.create(HotKeyApiInterface::class.java)
val response: Response<ResponseBody<List<Info>>>? = apiProxy.getDataWithResponse()
}
}
但是如果我们故意把url中的路径写错(关键点1处):
@GET("/hotey/jsonxxxx")
suspend fun getDataWithResponse(): Response<ResponseBody<List<Info>>>
再次进行请求时,apiProxy.getDataWithResponse 方法内部会抛出 HttpException 异常,会导致程序崩溃,这时我们需要通过 try catch 的方式将异常捕获,如下:
private fun retrofitResponse() {
GlobalScope.launch {
val retrofit = Retrofit.Builder()
.baseUrl("www.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiProxy = retrofit.create(HotKeyApiInterface::class.java)
val response:Response<ApiResult<List<Info>>>? = null
try {
response = apiProxy.getDataWithResponse()
} catch {
}
}
}
但是工程中所有这种情况都采用 try catc h的方式进行处理并不合适,因为这样处理会产生大量重复代码,这时我们就可以通过自定义适配器 CallAdapter 生成一个 自定义 ApiResponse 来解决这个问题。
//后续更新