Elegant use of Retrofit to navigate Android network requests in the coroutine era (3)

Foreword : Since the framework itself is constantly iterating, some of the code in the article may be updated or outdated. If you want to read the source code or see how the code is actually used in the project, you can view the compose project that the author is currently maintaining: Spacecraft: "Spacecraft - My Android Technology Practice Platform" - Please enter the develop branch to view the code (gitee.com)

The content of this article is mainly to implement the logic related to the overall situation in the network framework . If you have not read the previous article, please jump to the elegant use of Retrofit to travel Android network requests in the coroutine era (2) - Nuggets (juejin.cn) )

Network exception to natural language information

  In the previous section, we left some code not provided, here we review

/**
 * 网络请求出现异常
 */
data class Exception<T>(val exception: Throwable) : Failure<T>() {
    //分析异常类型,返回自然语言错误信息
    val exceptionMessage:String by lazy {
        //TODO 下文讲解
    }
}

  The meaning of this natural language is: the error message that the user can directly read. Let's think about it from another angle. If the program directly throws the information of the network timeout exception SocketTimeoutException to the user, such as a Toast, what will happen? It is conceivable that the user will be very confused (provided that he is not a programmer), so it is necessary to convert the crude program error stack information into human readable information.

Still talking nonsense! code above.

/**
 * 网络异常解析器
 */
interface HttpExceptionParser{
    /**
     * 将一个异常转化为自然语言报错
     */
    fun parse(throwable: Throwable?): String
    /**
     * 默认实现
     */
    companion object{
        val DEFAULT_PARSER= object : HttpExceptionParser {
            override fun parse(throwable: Throwable?): String {
                return when (throwable) {
                    is ConnectException, is SocketException, is HttpException, is UnknownHostException -> "网络连接失败"
                    is SSLHandshakeException -> "证书验证失败"
                    is JSONException, is ParseException, is JsonParseException -> "解析报文失败"
                    is SocketTimeoutException -> "连接超时"
                    is MsgException -> throwable.tip
                    else -> "未知错误"
                }
            }
        }
    }
}

  The code is very simple, that is, an interface contains a method that converts throwable to a string, and then the interface contains a default implementation (you can change it here to what you want)

  Next, we implement a Holder to hold the Parser, let the Holder implement the Parser method (agent mode), and then call the real parser to parse (you can replace the parser to implement the custom logic you want)

/**
 * 异常处理器持有者
 */
object HttpExceptionParserHolder:HttpExceptionParser {

    var parser=DEFAULT_PARSER

    override fun parse(throwable: Throwable?): String {
        return parser.parse(throwable)
    }

}

  In this way, back to the original code, we pass in the Holder to complete the logical closed loop, and users will never see strange network exceptions and errors again.

/**
 * 网络请求出现异常
 */
data class Exception<T>(val exception: Throwable) : Failure<T>() {
    val exceptionMessage: String by lazy {
        HttpExceptionParserHolder.parse(exception)
    }
}

NetworkResult interceptor

  在本网络框架的网络请求中,在服务器无异常且拿到服务器报文后,逻辑会执行到Success中去,但是有时候我们有这样的需求,报文中有个字段,例如Code,如果Code不为1,则说明业务报错,因此我们还需要进一步判断Code的值,否则会出现BUG。

  那么问题来了,我们如果实现全局逻辑而不是在具体的某个请求的Success中回调呢? 笔者参考过很多人的解决方案,大部分是对OKhttp的拦截器进行处理,即在okhttp拦截器中进行校验,但是这样有个缺陷是,你需要多执行一遍实体类型的转换(因为Retrofit层已经有一层),性能差而且存在代码维护的难度(因为存在两套实体类转换逻辑,okhttp和retrofit各一套),因此我们希望统一在Retrofit层面进行校验。

  让我们回到第一节的代码(第一节传送门:优雅使用Retrofit,在协程时代遨游安卓网络请求(一) - 掘金 (juejin.cn)

  在第一节中,笔者提供了一个将Retrofit的Response转成NetworkResult的扩展方法,我们的关键点就是这里。

fun <T> Response<T>.toNetworkResult(): NetworkResult<T> =
    //此处可以做全局转换
   try {
        if (isSuccessful) {
            toSuccessResult()
        } else {
            toServerErrorResult()
        }
    } catch (t: Throwable) {
        t.toExceptionResult()
    }

  这个方法的返回值是NetworkResult,因此可以返回Success,Failure中的任意一种,我们只需要在这里插入转换的逻辑,将部分Success的转成Failure即可(当然你可以实现任意的转换,甚至将错误转成正确都可以,非常的任性)。

  那么如何实现的,废话不多说,直接上代码!

interface GlobalNetworkResultInterceptor {

    /**
     * 拦截网络请求,
     */
    fun <T> onIntercept(networkResult: NetworkResult<T>): NetworkResult<T> {
        return networkResult
    }

    //默认实现
    object DefaultGlobalNetworkResultInterceptor : GlobalNetworkResultInterceptor

}

  和异常解析器非常类似,也是一接口带着一个默认实现,方法就是将一个NetworkResult变成另外一个NetworkResult,默认就是不转。

  为了方便读者理解,我们用玩安卓的api来辅助说明,首先玩安卓的api接口返回值全是一个模板(大部分公司都类似),用实体类表示如下:

/**
 * 带壳的相应bean
 * @param T data实体类
 * @property data T 报文中对应data的部分
 * @property errorCode Int 报文中对应errorCode的部分
 * @property errorMsg String 报文中对应errorMsg的部分
 * @constructor
 */
data class WanBeanWrapper<T>(
    val data: T,
    val errorCode: Int = -1,
    val errorMsg: String = ""
) {

    companion object {
        const val SUCCESS_CODE = 0
        const val LOGIN_ERROR_CODE = -1001
    }

    /**
     * 请求是否成功
     * @return Boolean true:成功
     */
    fun isSuccessful(): Boolean {
        return errorCode == SUCCESS_CODE
    }

    /**
     * 登陆失败
     * @return Boolean true:登陆失败
     */
    fun isLoginError(): Boolean {
        return errorCode == LOGIN_ERROR_CODE
    }

}

  For the global interceptor, it is enough to pay attention to this shell. Suppose we need to implement a logic: when the errorCode is not equal to SUCCESS_CODE, we need to convert the NetworkResult of type Success to NetworkResult of Error type.

  Let's take a look at an interceptor implemented by the author in the project:

class WanGlobalNetworkResultInterceptor @Inject constructor() : GlobalNetworkResultInterceptor {

    override fun <T> onIntercept(networkResult: NetworkResult<T>): NetworkResult<T> {
        return if (
            //只有成功才转换
            networkResult is Success &&
            //只转换这种类型的Bean
            networkResult.responseBody is WanBeanWrapper<*>
        ) {
            //类型强转
            val wanBeanWrapper = (networkResult.responseBody as WanBeanWrapper<*>)
            //如果不成功
            if (!wanBeanWrapper.isSuccessful()) {
                return MsgException(wanBeanWrapper.errorMsg).toExceptionResult()
            }
            networkResult
        } else {
            networkResult
        }
    }

}

  It can be seen that the conversion of the NetworkResult type is completed. It is a Success type at the beginning, and then by checking the errorCode in the message to find that the background has reported an error to us, so we are based on MsgException (just a simple subclass of IoException, the author Used to report some custom errors in okhttp's interception, you can use any exception you want to encapsulate ExceptionResult ) to generate a new ExceptionResult . In this way, the logic that would have been successful becomes the logic of error, and the caller does not need to manually determine whether the errorCode is normal after each successful network request!

Let's go back to the code from Chapter 1 and make a few changes.

//扩展方法新增一个参数
fun <T> Response<T>.toNetworkResult(interceptor: GlobalNetworkResultInterceptor): NetworkResult<T> =
    interceptor.onIntercept(
        try {
            if (isSuccessful) {
                toSuccessResult()
            } else {
                toServerErrorResult()
            }
        } catch (t: Throwable) {
            t.toExceptionResult()
        }
    )


//代理类
internal class ApexResponseCallDelegate<T>(
    private val proxyCall: Call<T>,
    //新增参数,传入全局拦截器
    private val interceptor: GlobalNetworkResultInterceptor
) :
    Call<NetworkResult<T>> {

    override fun enqueue(callback: Callback<NetworkResult<T>>) =
        proxyCall.enqueue(object : Callback<T> {
            override fun onResponse(call: Call<T>, response: Response<T>) {
                callback.onResponse(
                    this@ApexResponseCallDelegate,
                    Response.success(
                        //将参数传入到这里使用
                        response.toNetworkResult(interceptor)
                    )
                )
            }

            //省略部分代码

        })
        

In this way, the original code can be modified at a low cost, and the interception and modification of the global return result is completed!

Thank you for seeing this. This simple series is over. If you have more questions, you can leave a message to the author in the comment area. If you want to see the source code, you can also go to the author's open source project to find the relevant code (search for the class name, i.e. Yes, some class names may change).

Guess you like

Origin juejin.im/post/7118380454633439240