Retrofit 低侵入性小技巧
常见开发需求
- 需求一:Kotlin 使用 Retrofit 进行协程接口调用的时候,总是要处理网络异常
- 需求二:使用 Retrofit 进行接口调用的时候,统一做错误处理
Tips
- Retrofit 版本需要在2.6.0及以上
- API 数据模型和数据接口基于 WanAndroid
需求一
- 第一种方式:在 Retrofit 内部的 CallAdapter 进行一次 Result 异常处理,代码如下
使用处
// WanAndroid 返回的基本数据结构
data class API<Data>(
val errorCode: Int = 0,
val errorMsg: String? = null,
val data: Data? = null
)
// WanAndroid 其中一个接口
interface UserRemoteDataSource{
// Tips:这里是不需要套一个 retrofit.Call<*>
@GET("/user/logout/json")
@suspend fun logout(): Result<API<String>>
}
CallAdapter 处
class APIResultCallAdapterFactory : CallAdapter.Factory() {
// 根据返回值解析内容
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
// step1: 返回类型需要是 retrofit.Call<T> 且 返回类型需要是泛型 => 不符合直接返回 null
if (getRawType(returnType) != Call::class.java || returnType !is ParameterizedType) {
return null
}
// step2: 第二层是 Result<T> 类型判断 => 不符合直接返回 null
val firstType = getParameterUpperBound(0, returnType)
if (getRawType(firstType) != Result::class.java || firstType !is ParameterizedType) {
return null
}
// step3: 符合前面所有前提,可以进行转换
val secondType = getParameterUpperBound(0, firstType)
return APIResultCallAdapter<Any>(secondType)
}
}
class APIResultCallAdapter<T>(private val responseType: Type) : CallAdapter<T, Call<Result<T?>>> {
override fun responseType(): Type = responseType
override fun adapt(call: Call<T?>): Call<Result<T?>> = APIResultCall(call)
}
class APIResultCall<T>(private val originCall: Call<T?>) : Call<Result<T?>> {
override fun execute(): Response<Result<T?>> {
return try {
val originResponse = originCall.execute()
Response.success(Result.success(originResponse.body()))
} catch (throwable: Throwable) {
Response.success(Result.failure<T>(throwable))
}
}
override fun enqueue(callback: Callback<Result<T?>>) {
originCall.enqueue(object : Callback<T?> {
override fun onResponse(call: Call<T?>, response: Response<T?>) {
callback.onResponse(this@APIResultCall, Response.success(Result.success(response.body())))
}
override fun onFailure(call: Call<T?>, t: Throwable) {
// Tips: 这里需要调用 onResponse,协程才不会抛出异常
callback.onResponse(this@APIResultCall, Response.success(Result.failure(t)))
}
})
}
// 省略若干重写代码
}
- 第二种方式:因为一般一个应用与服务器交互只有一种数据模型,我们可以不使用上述方法支持所有模型,而只针对一种模型进行更内部的异常处理
使用处
// WanAndroid 其中一个接口
interface UserRemoteDataSource{
// Tips:这里是不需要套一个 retrofit.Call<*>
@GET("/user/logout/json")
@suspend fun logout(): API<String>
}
CallAdapter 处
class APICallAdapterFactory : CallAdapter.Factory() {
// 根据返回值解析内容
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
// step1: 返回类型需要是 retrofit.Call<T> 且 返回类型需要是泛型 => 不符合直接返回 null
if (getRawType(returnType) != Call::class.java || returnType !is ParameterizedType) {
return null
}
// step2: 第二层是 API<T> 类型判断 => 不符合直接返回 null
val firstType = getParameterUpperBound(0, returnType)
if (getRawType(firstType) != API::class.java || firstType !is ParameterizedType) {
return null
}
// step3: 符合上述类型,进行转换[注意:这里类型是firstType]
return APICallAdapter(firstType)
}
}
class APICallAdapter(private val responseType: Type) : CallAdapter<API<*>, Call<API<*>>> {
// 代表Convert转化的内容
override fun responseType() = responseType
override fun adapt(call: Call<API<*>>) = APICall(call)
}
class APICall(private val originCall: Call<API<*>>) : Call<API<*>> {
override fun execute(): Response<API<*>> {
return try {
// 响应正确返回
originCall.execute().check()
} catch (e: Exception) {
// 响应未正确返回
Response.success(e.toAPI())
}
}
override fun enqueue(callback: Callback<API<*>>) {
originCall.enqueue(object : Callback<API<*>> {
override fun onResponse(call: Call<API<*>>, response: Response<API<*>>) {
try {
// 响应正确返回
callback.onResponse(this@APICall, response.check())
} catch (e: Exception) {
callback.onResponse(this@APICall, Response.success(e.toAPI()))
}
}
override fun onFailure(call: Call<API<*>>, t: Throwable) {
// 响应未正确返回
callback.onResponse(this@APICall, Response.success(t.toAPI()))
}
})
}
// 省略若干重写代码
}
异常处理部分
// 针对指定异常转换成对应的模型
fun Throwable.toAPI(): API<*> {
return when (this) {
is IOException -> {
API(-0xFF, this.message, null)
}
else -> {
API(-0xFFF, this.message, null)
}
}
}
HTTP响应处理
private fun Response<API<*>>.check(): Response<API<*>> {
val body = this.body()
if (body != null) {
// 这里可以进行全局进行异常处理
if (body.errorCode == HttpConstant.ALREADY_LOGIN_OUT) {
HttpConstant.clearTokenSync()
}
return this
}
// 这里是响应非200的时候处理
return Response.success(NullPointerException().toAPI())
}
总结
- 方式一:可以兼容所有数据模型异常捕获,更加通用
- 方式二:只兼容一种模型,但可以做更细致的异常处理;比如可以针对指定的code做全局处理、HTTP code不为2xx的时候处理;并且在使用处对原先定义是不需要修改的,只需要增加CallAdapter即可,转换成本较低
需求二
- 第一种方式:上述方法二说明到了,但只适用于一种数据模型
- 第二种方式:在每次调用的时候中间加入一层调用,比如 response.intercept() 等,同样只适用于一种数据模型,并且跟第一种方式相比,侵入性更大 [WanAndroid Kotlin Demo就是这样做的]
- 第三种方式:劫持第一次输入的 CallAdapterFactory,重新生成一次 Retrofit,此种方式可以适用于所有数据模型,代码如下
定义一个响应处理接口
interface ResponseInjectListener {
fun <T> onResponse(call: Call<T>, response: Response<T>)
}
在 Retrofit 生成的末尾增加如下 inject 方法
fun Retrofit.inject(listener: ResponseInjectListener): Retrofit {
val newCallAdapterFactories = this.callAdapterFactories().map {
it.warp(listener) // 开始劫持
}
// 把原先的retrofit的内容都重新赋值一份
return Retrofit.Builder()
.baseUrl(baseUrl())
.also { builder -> newCallAdapterFactories.forEach { builder.addCallAdapterFactory(it) } }
.also { builder -> converterFactories().forEach { builder.addConverterFactory(it) } }
.callFactory(callFactory())
.also {
val executor = callbackExecutor()
if (executor != null) it.callbackExecutor(executor)
}
.build()
}
对请求全链路部分进行包装
private fun CallAdapter.Factory.warp(listener: ResponseInjectListener): CallAdapter.Factory {
val origin = this
return object : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
// 继续进行包装
return origin.get(returnType, annotations, retrofit)?.warp(listener)
}
}
}
private fun <T, R> CallAdapter<T, R>.warp(listener: ResponseInjectListener): CallAdapter<T, R> {
val origin = this
return object : CallAdapter<T, R> {
override fun responseType(): Type = origin.responseType()
// 继续进行包装
override fun adapt(call: Call<T>): R = origin.adapt(call.warp(listener))
}
}
private fun <T> Call<T>.warp(listener: ResponseInjectListener): Call<T> {
val origin = this
return object : Call<T> {
override fun clone(): Call<T> = origin.clone()
override fun execute(): Response<T> {
val response = origin.execute()
// 防止回调异常,影响主流程
runCatching { listener.onResponse(this@warp, response) }
return response
}
override fun enqueue(callback: Callback<T>) {
return origin.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
callback.onResponse(call, response)
// 防止回调异常,影响主流程
runCatching { listener.onResponse(this@warp, response) }
}
override fun onFailure(call: Call<T>, t: Throwable) {
callback.onFailure(call, t)
}
})
}
// 省略如下方法
}
}
使用处
private fun buildRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://www.wanandroid.com")
.addCallAdapterFactory(APICallAdapterFactory())
.addConverterFactory(FastJsonConverterFactory.create())
.client(okHttpClient)
.build()
.inject(object : ResponseInjectListener {
override fun <T> onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body is API<*>) {
when (body.errorCode) {
HttpConstant.ALREADY_LOGIN_OUT -> {
HttpConstant.clearTokenSync()
}
}
}
// 根据body判断数据模型即可增加不同类型捕获
}
})
总结
劫持原先 CallAdapter 的方式,代表获取到的内容都是处理好的(即处理好的数据模型),可以直接用于判断;同时增加不同的判断分支即可增加不同数据模型捕获,对于原先代码侵入性较低;但目前缺点是,对于重复修改Retrofit参数表现不太好,但一般也不会这样干吧?
网友评论