利用 Kotlin 扩展函数优雅处理网络异常详解
一、前言
在 Android 开发中,网络请求是核心业务场景之一。使用 RxJava 配合 Retrofit 进行异步网络调用时,异常处理往往分散在各个订阅(Observer)或观察者(Subscriber)的 onError 回调中。这种处理方式容易导致代码重复,且难以统一处理不同层级的错误逻辑。
传统的 RxJava 提供了如 onErrorReturn 和 onErrorResumeNext 等操作符来处理异常:
- onErrorReturn:当发生错误时,发射一个默认值然后结束数据流。订阅者看不到异常信息,只看到正常的数据流结束状态。
- onErrorResumeNext:当错误发生时,使用另外一个数据流继续发射数据。返回的被观察者中看不到错误信息。
虽然这些操作符功能强大,但直接在每个 Observable 链式调用中编写显得冗长。结合 Kotlin 的特性,我们可以定义扩展函数来封装这些逻辑,使代码更加简洁优雅。
二、网络响应模型设计
网络请求返回的 Response 通常采用如下 JSON 格式:
{
"code": 0,
"message": "success",
"data": {
...
}
}
对于客户端开发而言,我们会封装一个基类的 HttpResponse 来统一处理业务状态码。
data class HttpResponse<T>(
var code: Int = -1,
var message: String? = null,
var data: T? = null
) : Serializable
interface UnProguard : Serializable
其中,isOkStatus 属性用于快速判断请求是否成功:
val isOkStatus: Boolean
get() = code == 0
三、传统异常处理的痛点
通常情况下,我们会在 Observer 的 onError 中按照如下的方式处理异常:
viewModel.getHelps(this)
.subscribe(
{ response ->
if (response.isOkStatus) {
multi_status_view.showContent()
adapter.addData(response.data?.list)
} else {
multi_status_view.showError()
}
},
{ throwable ->
multi_status_view.showError()
}
)
这种方式存在以下问题:
- 逻辑耦合:UI 更新逻辑与异常捕获逻辑混在一起。
- 重复代码:每次请求都需要编写类似的
if-else 判断。
- 维护困难:如果错误处理策略变更,需要修改所有订阅点。
四、Kotlin 扩展函数的解决方案
利用 Kotlin 扩展函数的特性,我们可以针对 RxJava 的 Maybe 类型编写通用的错误处理扩展函数。这样无须在 onError 中处理异常,而且高阶函数的 action 参数可以传递不同的异常处理逻辑。
4.1 重试机制扩展
import com.safframework.utils.RetryWithDelay
import io.reactivex.Maybe
fun <T> Maybe<T>.retryWithDelayMillis(maxRetries: Int = 3, retryDelayMillis: Int = 1000): Maybe<T> =
this.retryWhen(RetryWithDelay(maxRetries, retryDelayMillis))
4.2 错误返回值扩展
fun <T> Maybe<T>.errorReturn(defValue: T): Maybe<T> = this.onErrorReturn {
it.printStackTrace()
defValue
}
fun <T> Maybe<T>.errorReturn(defValue: T, action: (Throwable) -> Unit): Maybe<T> = this.onErrorReturn {
action.invoke(it)
defValue
}
4.3 错误恢复流扩展
fun <T> Maybe<T>.errorResumeNext(defValue: T): Maybe<T> = this.onErrorResumeNext(Maybe.just(defValue))
fun <T> Maybe<T>.errorResumeNext(): Maybe<T> = this.onErrorResumeNext(Maybe.empty())
五、实际应用场景
使用上述扩展函数后,代码结构将变得非常清晰。我们可以在数据流的上游就处理好异常,下游只需关注业务逻辑。
viewModel.getHelps(this)
.retryWithDelayMillis()
.errorReturn(HttpResponse()) { throwable ->
Log.e("NetworkError", throwable.message)
}
.subscribe {
if (it.isOkStatus) {
multi_status_view.showContent()
adapter.addData(it.data?.list)
} else {
multi_status_view.showError()
}
}
在这个例子中,errorReturn 是一个高阶函数。它的 action 参数传递的是一个函数,专门用于处理异常。每一个网络请求的异常处理并不会都一样,可以用该函数来传递不同的异常处理策略,实现了高度的灵活性。
六、最佳实践建议
- 统一错误码映射:建议在
HttpResponse 中增加一个方法,将业务错误码转换为友好的用户提示文本。
- 线程调度:确保网络请求在主线程处理结果,异常处理逻辑也应在主线程或合适的调度器上执行,避免 UI 更新崩溃。
- 日志记录:在生产环境中,建议将
throwable 上报到监控服务,以便追踪线上问题。
- 类型安全:尽量使用泛型约束,确保
data 类型的正确性,避免运行时类型转换错误。
七、总结
合理利用 Kotlin 的扩展函数,可以编写优雅的代码。而使用高阶函数,则可以达到的进一步的抽象。通过将 RxJava 的错误处理逻辑封装为通用扩展,开发者可以将注意力更多地集中在业务逻辑的实现上,而不是分散在繁琐的异常捕获代码中。这种方式不仅简化了 Observer 代码,还提高了代码的可读性和可维护性,是现代 Android 架构设计中值得推荐的做法。