【问题标题】:Exception handling of network errors retrofit网络错误改造的异常处理
【发布时间】:2019-10-23 12:42:21
【问题描述】:

我想知道在使用协程时处理改造请求中的网络错误的最佳方法是什么。

经典的方法是在请求发出时在最高级别处理异常:

try {
    // retrofit request
} catch(e: NetworkException) {
    // show some error message
}

我发现这个解决方案是错误的,它添加了很多样板代码,相反我创建了一个返回错误响应的拦截器:

class ErrorResponse : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        return try {
            chain.proceed(request)
        } catch (e: Exception) {
            Snackbar.make(
                view,
                context.resources.getText(R.string.network_error),
                Snackbar.LENGTH_LONG
            ).show()
            Response.Builder()
                .request(request)
                .protocol(Protocol.HTTP_1_1)
                .code(599)
                .message(e.message!!)
                .body(ResponseBody.create(null, e.message!!))
                .build()
        }
    }
}

这个解决方案稍微好一点,但我认为它可以改进。

所以我的问题是:当用户没有互联网连接、没有大量样板代码(最好使用全局处理程序以防连接错误)时,处理这种情况的正确方法是什么?

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    使用 Result 包装我的回复

    sealed class Result<out T : Any> {
    data class Success<out T : Any>(val value: T) : Result<T>()
    data class Failure(val errorHolder:ErrorHolder) : Result<Nothing>()}
    

    错误持有者:

    sealed class ErrorHolder(override val message):Throwable(message){
     data class NetworkConnection(override val message: String) : ErrorHolder(message)
     data class BadRequest(override val message: String) : ErrorHolder(message)
     data class UnAuthorized(override val message: String) : ErrorHolder(message)
     data class InternalServerError(override val message: String) :ErrorHolder(message)
     data class ResourceNotFound(override val message: String) : ErrorHolder(message)
    }
    

    处理异常的扩展

    suspend fun <T, R> Call<T>.awaitResult(map: (T) -> R): Result<R> = suspendCancellableCoroutine { continuation ->
    try {
        enqueue(object : Callback<T> {
            override fun onFailure(call: Call<T>, throwable: Throwable) {
                errorHappened(throwable)
            }
    
            override fun onResponse(call: Call<T>, response: Response<T>) {
                if (response.isSuccessful) {
                    try {
                        continuation.resume(Result.Success(map(response.body()!!)))
                    } catch (throwable: Throwable) {
                        errorHappened(throwable)
                    }
                } else {
                    errorHappened(HttpException(response))
                }
            }
    
            private fun errorHappened(throwable: Throwable) {
                continuation.resume(Result.Failure(asNetworkException(throwable)))
            }
        })
    } catch (throwable: Throwable) {
        continuation.resume(Result.Failure(asNetworkException(throwable)))
    }
    
    continuation.invokeOnCancellation {
        cancel()
    }}
    

    这就是我调用 api 的方式:

    suspend fun fetchUsers(): Result<List<User>> {
        return service.getUsers().awaitResult { usersResponseDto ->
            usersResponseDto.toListOfUsers()
        }
    }
    

    更新:

    假设您有如下错误正文:

    {
      "error" : {
        "status" : 502,
        "message" : "Bad gateway."
      }
    }
    

    首先我们应该创建一个数据类来模拟响应体

    data class HttpErrorEntity(
    @SerializedName("message") val errorMessage: String,
    @SerializedName("status") val errorCode: Int
    )
    

    这是asNetworkException 的实现:

    private fun asNetworkException(ex: Throwable): ErrorHolder {
        return when (ex) {
            is IOException -> {
                ErrorHolder.NetworkConnection(
                    "No Internet Connection"
                )
            }
            is HttpException -> extractHttpExceptions(ex)
            else -> ErrorHolder.UnExpected("Something went wrong...")
        }
    }
    
    private fun extractHttpExceptions(ex: HttpException): ErrorHolder {
        val body = ex.response()?.errorBody()
        val gson = GsonBuilder().create()
        val responseBody= gson.fromJson(body.toString(), JsonObject::class.java)
        val errorEntity = gson.fromJson(responseBody, HttpErrorEntity::class.java)
        return when (errorEntity.errorCode) {
            ErrorCodes.BAD_REQUEST.code -> 
                    ErrorHolder.BadRequest(errorEntity.errorMessage)
                
            ErrorCodes.INTERNAL_SERVER.code -> 
                ErrorHolder.InternalServerError(errorEntity.errorMessage)
            
            ErrorCodes.UNAUTHORIZED.code ->
                ErrorHolder.UnAuthorized(errorEntity.errorMessage)
           
            ErrorCodes.NOT_FOUND.code ->
                ErrorHolder.ResourceNotFound(errorEntity.errorMessage)
            
            else -> 
                ErrorHolder.Unknown(errorEntity.errorMessage)
            
        }
    }
    

    【讨论】:

    • 你能添加 asNetworkException() 有趣的实现吗?请感兴趣看看你如何处理错误情况,谢谢
    • 私人乐趣 asNetworkException(throwable: Throwable): ErrorHolder { return ErrorHolder.NetworkConnection(throwable = throwable, message = throwable.message ?: "Empty error message") }
    • @MohammadSianaki 你错过了错误持有者上的 UnExpected 和 ErrorCodes 类这只是一堆静态字符串,比如 400 错误等?
    • @MohammadSianaki 在 Result.Success 上使用它时我也遇到错误:构造函数 Success ( value: T ) 中为 T 绑定的类型参数不满足:推断类型 R 是不是 Any 的子类型
    • @martinseal1987 kotlin 标准库有它自己的结果类型,请确保您导入的是自己的类型,而不是标准库中的类型。
    【解决方案2】:

    通过实施Interceptor,您的方式是正确的。但稍作改动,您就可以使用这个示例类:

    class NetworkConnectionInterceptor(val context: Context) : Interceptor {
    
        @Suppress("DEPRECATION")
        private val isConnected: Boolean
            get() {
                var result = false
                val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    cm?.run {
                        cm.getNetworkCapabilities(cm.activeNetwork)?.run {
                            result = when {
                                hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                                hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                                hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                                else -> false
                            }
                        }
                    }
                } else {
                    cm?.run {
                        cm.activeNetworkInfo?.run {
                            if (type == ConnectivityManager.TYPE_WIFI) {
                                result = true
                            } else if (type == ConnectivityManager.TYPE_MOBILE) {
                                result = true
                            }
                        }
                    }
                }
                return result
            }
    
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response {
            if (!isConnected) {
                // Throwing your custom exception
                // And handle it on onFailure
            }
    
            val builder = chain.request().newBuilder()
            return chain.proceed(builder.build())
        }
    }
    

    然后将其添加到您的OkHttpClient.Builder():

    .addInterceptor(NetworkConnectionInterceptor(context));
    

    如果失败,您可以像这样在onFailure 方法中处理它:

    override fun onFailure(call: Call<BaseModel>, t: Throwable) {
        if (t is NoConnectivityException) {
            // Handle it here :)
        }
    }
    

    【讨论】:

    • 我想避免手动检查网络连接,因为它需要单独的权限。这也不能解决您必须处理异常的问题。
    • @Daniel 如果不手动检查网络连接,您必须检查改造调用onFailure 方法以获取IOException 的异常实例。虽然这包括另一个错误,不仅仅是离线错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 1970-01-01
    • 1970-01-01
    • 2020-09-08
    • 2014-03-19
    • 2020-02-04
    • 2014-12-28
    相关资源
    最近更新 更多