美文网首页
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