网络错误改造的异常处理

Exception handling of network errors retrofit

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

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

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

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

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()
        }
    }
}

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

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

通过实施 Interceptor,您的方法是正确的。但是稍微改变一下,你可以得到这个样本 class:

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 :)
    }
}

使用结果来包装我的回复

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."
  }
}

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

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)
        
    }
}