retrofit是现今流行的网络请求框架,现今有了kotlin协程的加持,如虎添翼,通常用法如下:
1、定义rest接口
1 | interface Api { |
2、定义请求包装类
1 | data class LoginWrapper( val username: String, val password: String? = null) |
与返回包装类
1 | data class Response<out T>( |
2、创建调用实例
1 | val retrofit: Retrofit = Retrofit.Builder() |
3、调用接口
1 | lifecycleScope.launch { |
一切都很美好,无论成功还是失败都能正常返回,但是如果请求的时候超时了;
app直接崩溃闪退了,不仅是超时,如果有断网,或者返回json格式不正确都会导致崩溃,我原来用rxjava也没这问题呀。
究其原因 我们根本没有对这些异常进行捕获,而rxjava是已经帮我们做了。那么我们怎么处理这个异常呢?
一种方法是调用的时候对异常显式的捕获,简单的说就是catch住,方法很简单,但是每次调用都catch一下,如果接口里方法很多。
这种方法就显得繁琐而不优雅。如果要对一个类的所以方法做个处理,那不就是切面编程吗!而且这是一个接口,天然就可以动态代理
我们将所有api接口方法统一catch住,如果发生异常。返回一个统一的错误实体
1 | val delegateRemoteService = Proxy.newProxyInstance( |
将调用处remoteService
改为delegateRemoteService
,试一下发现还会有问题。debug会发现这块儿catch住还在调用请求的线程,而异常发生在子线程中。所以统一trycatch这条路还是不通。
这时候,就想起来线程的切换是由kotlin协程来管理的,反编译下kotlin代码,发现其在每一个suspend修饰的接口方法里,会传递一个Continuation
对象,
1 |
|
这是一个接口,定义如下:
1 | public interface Continuation<in T> { |
我称其为恢复器
,其中的resumeWith方法就是恢复时的回调,可以在这个地方处理掉可能的异常。我们可以用kotlin里静态代理的便宜写法,替换掉默认恢复器
@Suppress("UNCHECKED_CAST")
val delegateRemoteService = Proxy.newProxyInstance(
PandoraApi::class.java.classLoader, arrayOf(PandoraApi::class.java)
) { _, method, args ->
val last = args.last()
if (last is Continuation<*>) {
args[args.lastIndex] = DelegateContinuation(last as Continuation<Any>)
}
method.invoke(remoteService, *args)
} as PandoraApi
class DelegateContinuation(
private val continuation: Continuation<Any>
) : Continuation<Any> by continuation {
companion object{
private val error by lazy { Response(-1, "网络错误", null) }
}
override fun resumeWith(result: Result<Any>) {
GlobalScope.launch(context) {
if (result.isFailure) {
continuation.resumeWith(Result.success(error))
} else {
result.getOrNull()?.also {
if (it is Response<*>) {
continuation.resumeWith(result)
}
}
}
}
}
}
这里将所有的异常都当作了网络错误,想细分的话可以调用result的exceptionOrNull()
,拿到具体的异常具体处理。这样在接口调用处拿到的就一定是一个Response对象。同时这里也是除了okhttp的interceptor之外可以对网络请求统一处理的地方,如token刷新,失效时重新请求。对失败请求弹个toast等等