美文网首页
Android Retrofit 低侵入性小技巧

Android Retrofit 低侵入性小技巧

作者: BlueSocks | 来源:发表于2023-10-12 17:58 被阅读0次

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参数表现不太好,但一般也不会这样干吧?

相关文章

网友评论

      本文标题:Android Retrofit 低侵入性小技巧

      本文链接:https://www.haomeiwen.com/subject/qddobdtx.html