一步步封装实现自己的网络请求框架 3.0

8,804 阅读16分钟

公众号:字节数组

希望对你有所帮助 🤣🤣

在 2019 年的时候,我先后写过两篇文章来介绍我是如何一步步封装实现一个网络请求框架的,可以分别看做是 1.0 和 2.0 版本 😇😇

1.0 版本采用的技术栈是 Java + Jetpack + RxJava + Retrofit,2.0 版本采用的技术栈是 Kotlin + Jetpack + RxJava + Retrofit。2.0 版本主要的变化点之一就在于替换了实现语言,依赖于 Kotlin 语言的简洁性和强大的语意表达能力,使得整个网络请求框架更加的短小精悍。发展到现在,RxJava 对于 Android 开发者来说其存在感和存在意义已经在逐步减弱中了,因为此时 Kotlin 协程出现了:kotlinx.coroutines

在技术领域,新的技术总是不断出现,当时的文章所介绍的内容在现在看来也是有点跟不上时代了,这也促使我来写此篇文章,即 3.0 版本

一、ReactiveHttp

协程这个概念已经出现很多年了,但 Kotlin 协程是在 2018 年才发布了 1.0 版本,被 Android 开发者所熟知还要再往后一段时间,协程的意义不是本篇文章所应该探讨的,但如果你去了解下协程能给我们带来的开发效益,我相信你是会喜欢上它的

闲话说完,这里先贴上 3.0 版本的 GitHub 链接:ReactiveHttp

3.0 版本的技术栈已更新为了 Kotlin + Jetpack + Coroutines + Retrofit,已托管到 jitpack.io,感兴趣的读者可以直接远程导入依赖

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

dependencies {
    implementation 'com.github.leavesCZY:ReactiveHttp:latest_version'
}

二、能给你带来什么

ReactiveHttp 能够给你带来什么?

  • 更现代化的技术栈。Kotlin + Jetpack + Retrofit 现在应该是大多数 Android 开发者选用的最基础组件了,Kotlin 协程会相对比较少人接触,但我觉得协程也是未来的主流方向之一,毕竟连 Retrofit 也原生支持 Kotlin 协程了,本库会更加符合大多数开发者的现实需求
  • 极简的设计理念。ReactiveHttp 目前仅包含十二个 Kotlin 文件,设计上遵循约定大于配置的理念,大多数配置项都可以通过方法复写的形式来实现自定义
  • 极简的使用方式。只需要持有一个 RemoteDataSource 对象,开发者就可以在任意地方发起异步请求和同步请求了。此外,进行网络请求 Callback 自然是必不可少的,ReactiveHttp 提供了多个事件回调:onStart、onSuccess、onSuccessIO、onFailed、onFailToast、onCancelled、onFinally 等,按需声明即可,甚至可以完全不用实现
  • 支持通用性的自动化行为。对于网络请求来说,像 showLoading、dismissLoading、showToast 等行为是具有通用性的,我们肯定不希望每个网络请求都要来手动调用方法触发以上操作,而是希望能够在发起网络请求的过程中自动完成。ReactiveHttp 就提供了自动完成以上通用性 UI 行为的功能 ,且每个操作均和生命周期相绑定,避免了常见的内存泄漏和 NPE 问题,并提供了交由外部使用者来自定义各种其它行为的入口
  • 极低的接入成本。ReactiveHttp 并不强制要求外部必须继承于任何 BaseViewModel 或者是 BaseActivity/BaseFragment,外部只要通过实现 IUIActionEventObserverIViewModelActionEvent 两个接口即可享受 ReactiveHttp 带来的各个益处。当然,如果你不需要 ReactiveHttp 的自动化行为的话,也可以不实现任何接口
  • 支持多个(两个/三个)接口同时并发请求,在网络请求成功时同步回调,所以理论上多个接口的总耗时就取决于耗时最长的那个接口,从而缩短用户的等待时间,提升用户体验

ReactiveHttp 不能带给你什么?

  • ReactiveHttp 本身的应用领域是专注于接口请求的,所以对于接口的返回值形式带有强制约束,且没有封装文件下载、文件上传等功能
  • 肯定有,但还没想到

ReactiveHttp 目前已经在我司项目上稳定运行一年多了,在这过程中我也是在逐步优化,使其能够更加适用于外部不同环境的需求,到目前为止我觉得也是已经足够稳定了,希望对你有所帮助 🤣🤣

三、架构说明

现在应该有百分之九十以上的 Android 客户端的网络请求是直接或间接依靠 OkHttp 来完成的吧?本文所说的网络请求框架就是指 在 OkHttp 之上所做的一层封装。原生的 OkHttp 在使用上并不方便,甚至可以说是有点繁琐。Retrofit 在易用性上有所提升,但是如果直接使用的话也并不算多简洁。所以我们往往都是会根据项目架构自己来对 OkHttp 或者 Retrofit 进行多一层封装,ReactiveHttp 的实现出发点即是如此

此外,现在大多数项目都引用到了 Jetpack 这一套组件来实现 MVVM 架构的吧?ReactiveHttp 将 Jetpack 和 Retrofit 关联了起来,使得网络请求过程更加符合“响应式”概念,并提供了更加可靠的生命周期安全性和自动化行为

一般的网络请求框架是只专注于完成网络请求并透传出结果,ReactiveHttp 不太一样。ReactiveHttp 在这个基础上还实现了将网络请求和 ViewModel 以及 Activity/Fragment 相绑定的功能,ReactiveHttp 希望做到的是能够尽量完成大多数的通用行为,且每个层次均不强依赖于特定父类

Google 官方曾推出过一份最佳应用架构指南。当中,每个组件仅依赖于其下一级的组件。ViewModel 是关注点分离原则的一个具体实现,是作为用户数据的承载体处理者而存在的,Activity/Fragment 仅依赖于 ViewModel,ViewModel 就用于响应界面层的输入和驱动界面层变化,Repository 用于为 ViewModel 提供一个单一的数据来源及数据存储域,Repository 可以同时依赖于持久性数据模型和远程服务器数据源

ReactiveHttp 的设计思想类似于 Google 推荐的最佳应用架构指南

  • BaseRemoteDataSource 作为数据提供者处于最下层,只用于向上层提供数据,提供了多个同步请求和异步请求方法,和 BaseReactiveViewModel 之间依靠 IUIActionEvent 接口来联系
  • BaseReactiveViewModel 作为用户数据的承载体和处理者,包含了多个和网络请求事件相关的 LiveData 用于驱动界面层的 UI 变化,和 BaseReactiveActivity 之间依靠 IViewModelActionEvent 接口来联系
  • BaseReactiveActivity 包含与系统和用户交互的逻辑,其负责响应 BaseReactiveViewModel 中的数据变化,提供了和 BaseReactiveViewModel 进行绑定的方法

上文有说到,ReactiveHttp 提供了在网络请求过程中自动完成 showLoading、dismissLoading、showToast 等行为的能力。首先,BaseRemoteDataSource 在网络请求过程中会通过 IUIActionEvent 接口来通知 BaseReactiveViewModel 需要触发的行为,从而连锁触发 ShowLoadingLiveData、DismissLoadingLiveData、ShowToastLiveData 值的变化,BaseReactiveActivity 就通过监听 LiveData 值的变化来完成 UI 层操作

四、以前的做法

以下步骤应该是大部分应用目前进行网络请求时的惯常做法了

服务端返回给移动端的数据使用具有特定格式的 Json 来进行通信,用整数 status 来标明本次请求是否成功,在失败时则直接 showToast(msg)data则需要用泛型来声明了,最终就对应移动端的一个泛型类,类似于 HttpWrapMode

{
    "status":200,
    "msg":"success",
    "data":""
}

data class HttpWrapMode<T>(val status: Int, val msg: String, val data: T)

然后在 interface 中声明 Api 接口,这也是使用 Retrofit 的惯常用法。根据项目中的实际情况,开发者可能是使用 Call 或者 Observable 作为每个接口返回值的最外层的数据包装类,然后再使用 HttpWrapMode 来作为具体数据类的包装类

interface ApiService {

    @POST("api1")
    fun api1(): Observable<HttpWrapMode<Int>>
    
    @GET("api2")
    fun api2(): Call<HttpWrapMode<String>>

}

然后项目中使用的是 RxJava,那么就需要像以下这样来完成网络请求

    val retrofit = Retrofit.Builder()
        .baseUrl("https://xxx.com")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()
    val service = retrofit.create(ApiService::class.java)
    val call: Observable<HttpWrapMode<Int>> = service.api1()
    call.subscribe(object : Consumer<HttpWrapMode<Int>> {
        override fun accept(mode: HttpWrapMode<Int>?) {
           
        }

    }, object : Consumer<Throwable> {
        override fun accept(t: Throwable?) {
            
        }
    })

五、ReactiveHttp 简单入门

ReactiveHttp 在使用上会比上面给出的例子简单很多,下面就来看下通过 ReactiveHttp 如何完成网络请求

ReactiveHttp 需要知道网络请求的结果,但不知道外部会使用什么字段名来标识 HttpWrapMode 中的三个值,所以需要外部实现 IHttpWrapMode 接口来进行标明。例如,你可以这样来实现:

data class HttpWrapMode<T>(val status: Int, val msg: String, val data: T) : IHttpWrapMode<T> {

    override val httpCode: Int
        get() = status

    override val httpMsg: String
        get() = msg

    override val httpData: T
        get() = data

    //网络请求是否成功
    override val httpIsSuccess: Boolean
        get() = status == 200

}

suspend来修饰接口方法,且不需要其它的外层包装类。suspend是 kotlin 协程引入的,当用该关键字修饰接口方法时,Retrofit 内部就会使用协程的方式来完成该网络请求

interface ApiService {

    @GET("config/district")
    suspend fun getProvince(): HttpWrapMode<List<DistrictMode>>

}

ReactiveHttp 提供了 RemoteExtendDataSource 交由外部来继承实现。RemoteExtendDataSource 包含了所有的网络请求方法,外部仅需要根据实际情况来传入两个必要的字段,并实现一个必要的方法即可

  • httpBaseUrl。即网络请求时的 BaseUrl
  • apiServiceClass。即 ApiService 的 Class 对象
  • showToast。当网络请求失败时,通过该方法来向用户提示失败原因

例如,你可以像以下这样来实现你自己项目专属的 DataSource,当中就包含了开发者整个项目的全局网络请求配置

class SelfRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(
    iActionEvent = iActionEvent,
    httpBaseUrl = HttpConfig.BASE_URL_MAP,
    apiServiceClass = ApiService::class.java
) {

    companion object {

        private val httpClient: OkHttpClient by lazy {
            createHttpClient()
        }

        private fun createHttpClient(): OkHttpClient {
            val builder = OkHttpClient.Builder()
                .readTimeout(1000L, TimeUnit.MILLISECONDS)
                .writeTimeout(1000L, TimeUnit.MILLISECONDS)
                .connectTimeout(1000L, TimeUnit.MILLISECONDS)
                .retryOnConnectionFailure(true)
                .addInterceptor(FilterInterceptor())
                .addInterceptor(MonitorInterceptor(MainApplication.context))
            return builder.build()
        }

    }

    /**
     * 自己来实现创建 Retrofit 的逻辑
     * 此处无需缓存 Retrofit 实例,ReactiveHttp 内部已做好缓存处理
     * 但外部需要自己判断是否需要对 OKHttpClient 进行缓存
     * @param baseUrl
     */
    override fun createRetrofit(baseUrl: String): Retrofit {
        return Retrofit.Builder()
            .client(httpClient)
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    override fun showToast(msg: String) {
        Toast.makeText(MainApplication.context, msg, Toast.LENGTH_SHORT).show()
    }

}

之后,我们就可以依靠 SelfRemoteDataSource 在任意地方发起网络请求了,按需声明 Callback。此外,由于使用到了扩展函数,所以 SelfRemoteDataSource 中可以直接调用 ApiService 中的接口方法,无需特意引用和导包

	private val remoteDataSource = SelfRemoteDataSource(null)

    val provinceLiveData = MutableLiveData<List<DistrictMode>>()

    fun reqProvince() {
        //enqueueLoading 方法会在发起网络请求的时候同时弹出 loading 框
        //enqueue 方法则不会弹出 loading 框
        remoteDataSource.enqueueLoading({
            getProvince()
        }) {
            /**
             * 在显示 Loading 之后且开始网络请求之前执行
             */
            onStart {

            }
            /**
             * 当网络请求成功时回调
             */
            onSuccess {
                provinceLiveData.value = it
            }
            /**
             * 当取消网络请求时就会回调此方法
             */
            onCancelled {

            }
            /**
             * 当网络请求失败时会调用此方法,在 onFinally 被调用之前执行
             */
            onFailed {
                val httpException = it
                val realException = httpException.realException
                val errorCode = httpException.errorCode
            }
            /**
             * 用于控制是否当网络请求失败时 Toast 失败原因
             * 默认为 true,即会 Toast 提示
             */
            onFailToast {
                true
            }
            /**
             * 在网络请求结束之后(不管请求成功与否)且隐藏 Loading 之前执行
             */
            onFinally {

            }
        }
    }

六、ReactiveHttp 进阶使用

上述在使用 SelfRemoteDataSource 发起网络请求时虽然调用的是 enqueueLoading 方法,但实际上并不会弹出 loading 框,因为完成 ShowLoading、DismissLoading、ShowToast 等 UI 行为是需要 RemoteDataSource、ViewModel 和 Activity 这三者一起进行配合的,即 SelfRemoteDataSource 需要和其它两者关联上,将需要触发的 UI 行为反馈给 Activity

这可以通过直接继承于 BaseReactiveActivity 和 BaseReactiveViewModel 来实现,也可以通过实现相应接口来完成关联。当然,如果你不需要 ReactiveHttp 的各个自动化行为的话,也可以不做以下任何改动

总的来说,ReactiveHttp 具有极低的接入成本

1、BaseReactiveActivity

BaseReactiveActivity 是 ReactiveHttp 提供的一个默认 BaseActivity,其实现了 IUIActionEventObserver 接口,用于提供一些默认参数和默认行为,例如 CoroutineScope 和 showLoading

但在大多数情况下,我们自己的项目是不会去继承外部 Activity 的,而是会有一个自己实现的全局统一的 BaseActivity,所以如果你不想继承 BaseReactiveActivity 的话,可以仿照 BaseReactiveActivity 自己来实现 IUIActionEventObserver 接口,就像以下这样

class BaseReactiveActivity : AppCompatActivity(), IUIActionEventObserver {

    protected inline fun <reified VM> getViewModel(
        factory: ViewModelProvider.Factory? = null,
        noinline initializer: (VM.(lifecycleOwner: LifecycleOwner) -> Unit)? = null
    ): Lazy<VM> where VM : ViewModel, VM : IViewModelActionEvent {
        return getViewModel(VM::class.java, factory, initializer)
    }

    override val lifecycleSupportedScope: CoroutineScope
        get() = lifecycleScope

    override val lContext: Context?
        get() = this

    override val lLifecycleOwner: LifecycleOwner
        get() = this

    private var loadDialog: ProgressDialog? = null

    override fun showLoading(job: Job?) {
        dismissLoading()
        loadDialog = ProgressDialog(lContext).apply {
            setCancelable(true)
            setCanceledOnTouchOutside(false)
            //用于实现当弹窗销毁的时候同时取消网络请求
//            setOnDismissListener {
//                job?.cancel()
//            }
            show()
        }
    }

    override fun dismissLoading() {
        loadDialog?.takeIf { it.isShowing }?.dismiss()
        loadDialog = null
    }

    override fun showToast(msg: String) {
        if (msg.isNotBlank()) {
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
        }
    }

    override fun finishView() {
        finish()
    }

    override fun onDestroy() {
        super.onDestroy()
        dismissLoading()
    }

}

2、BaseReactiveViewModel

类似地,BaseReactiveViewModel 是 ReactiveHttp 提供的一个默认的 BaseViewModel,其实现了 IViewModelActionEvent 接口,用于接收 RemoteDataSource 发起的 UI 层行为。如果你不希望继承于 BaseReactiveViewModel 的话,可以自己来实现 IViewModelActionEvent 接口,就像以下这样

open class BaseReactiveViewModel : ViewModel(), IViewModelActionEvent {

    override val lifecycleSupportedScope: CoroutineScope
        get() = viewModelScope

    override val showLoadingEventLD = MutableLiveData<ShowLoadingEvent>()

    override val dismissLoadingEventLD = MutableLiveData<DismissLoadingEvent>()

    override val showToastEventLD = MutableLiveData<ShowToastEvent>()

    override val finishViewEventLD = MutableLiveData<FinishViewEvent>()

}

3、关联上

完成以上两步后,开发者就可以像如下所示这样将 RemoteDataSource、ViewModel 和 Activity 这三者给关联起来。WeatherActivity 通过 getViewModel 方法来完成 WeatherViewModel 的初始化和内部多个 UILiveData 的绑定,并在 lambda 表达式中完成对 WeatherViewModel 内部和具体业务相关的 DataLiveData 的数据监听,至此所有自动化行为就都已经绑定上了

class WeatherViewModel : BaseReactiveViewModel() {

    private val remoteDataSource by lazy {
        SelfRemoteDataSource(this)
    }

    val forecastsModeLiveData = MutableLiveData<ForecastsMode>()

    fun getWeather(city: String) {
        remoteDataSource.enqueue({
            getWeather(city)
        }) {
            onSuccess {
                if (it.isNotEmpty()) {
                    forecastsModeLiveData.value = it[0]
                }
            }
        }
    }

}

class WeatherActivity : BaseReactiveActivity() {

    private val weatherViewModel by getViewModel<WeatherViewModel> {
        forecastsModeLiveData.observe(this@WeatherActivity, {
            showWeather(it)
        })
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_weather)
        weatherViewModel.getWeather("adCode")
    }

    private fun showWeather(forecastsMode: ForecastsMode) {

    }

}

七、其它

1、RemoteDataSource

RemoteExtendDataSource 提供了许多个可以进行复写的方法,既可用于配置 OkHttp 的各个网络请求参数,也用于交由外部进行流程控制。例如,你可以这样来实现自己项目的 RemoteDataSource

class SelfRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(
    iActionEvent = iActionEvent,
    httpBaseUrl = HttpConfig.BASE_URL_MAP,
    apiServiceClass = ApiService::class.java
) {

    companion object {

        private val httpClient: OkHttpClient by lazy {
            createHttpClient()
        }

        private fun createHttpClient(): OkHttpClient {
            val builder = OkHttpClient.Builder()
                .readTimeout(1000L, TimeUnit.MILLISECONDS)
                .writeTimeout(1000L, TimeUnit.MILLISECONDS)
                .connectTimeout(1000L, TimeUnit.MILLISECONDS)
                .retryOnConnectionFailure(true)
                .addInterceptor(FilterInterceptor())
                .addInterceptor(MonitorInterceptor(MainApplication.context))
            return builder.build()
        }

    }

    /**
     * 允许子类自己来实现创建 Retrofit 的逻辑
     * 外部无需缓存 Retrofit 实例,ReactiveHttp 内部已做好缓存处理
     * 但外部需要自己判断是否需要对 OKHttpClient 进行缓存
     * @param baseUrl
     */
    override fun createRetrofit(baseUrl: String): Retrofit {
        return Retrofit.Builder()
            .client(httpClient)
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    /**
     * 如果外部想要对 Throwable 进行特殊处理,则可以重写此方法,用于改变 Exception 类型
     * 例如,在 token 失效时接口一般是会返回特定一个 httpCode 用于表明移动端需要去更新 token 了
     * 此时外部就可以实现一个 BaseException 的子类 TokenInvalidException 并在此处返回
     * 从而做到接口异常原因强提醒的效果,而不用去纠结 httpCode 到底是多少
     */
    override fun generateBaseException(throwable: Throwable): BaseHttpException {
        return if (throwable is BaseHttpException) {
            throwable
        } else {
            LocalBadException(throwable)
        }
    }

    /**
     * 用于由外部中转控制当抛出异常时是否走 onFail 回调,当返回 true 时则回调,否则不回调
     * 同时在这里将网络请求过程中发生的异常反馈给外部,以便外部上报异常
     * @param httpException
     */
    override fun exceptionHandle(httpException: BaseHttpException): Boolean {
        return true
    }

    /**
     * 用于对 BaseException 进行格式化,以便在请求失败时 Toast 提示错误信息
     * @param httpException
     */
    override fun exceptionFormat(httpException: BaseHttpException): String {
        return when (httpException.realException) {
            null -> {
                httpException.errorMessage
            }
            is ConnectException, is SocketTimeoutException, is UnknownHostException -> {
                "连接超时,请检查您的网络设置"
            }
            else -> {
                "请求过程抛出异常:" + httpException.errorMessage
            }
        }
    }

    override fun showToast(msg: String) {
        Toast.makeText(MainApplication.context, msg, Toast.LENGTH_SHORT).show()
    }

}

此外,开发者可以直接在自己的 BaseViewModel 中声明一个 DataSource 变量实例,所有子 ViewModel 都全局统一使用同一份 DataSource 配置。如果有某些特定接口需要使用不同的 BaseUrl 的话,也可以再多声明一个 DataSource 对象

open class BaseViewModel : BaseReactiveViewModel() {

    /**
     * 正常来说单个项目中应该只有一个 RemoteDataSource 实现类,即全局使用同一份配置
     * 但父类也应该允许子类使用一个单独的 RemoteDataSource
     */
    protected open val remoteDataSource by lazy {
        SelfRemoteDataSource(this)
    }

}

2、BaseHttpException

BaseHttpException 是 ReactiveHttp 对网络请求过程中发生的各类异常情况的包装类,任何透传到外部的异常信息均会被封装为 BaseHttpException 类型。BaseHttpException 有两个默认子类,分别用于表示服务器异常和本地异常

/**
 * @param errorCode        服务器返回的错误码 或者是 HttpConfig 中定义的本地错误码
 * @param errorMessage     服务器返回的异常信息 或者是 请求过程中抛出的信息,是最原始的异常信息
 * @param realException    用于当 code 是本地错误码时,存储真实的运行时异常
 */
open class BaseHttpException(
    val errorCode: Int,
    val errorMessage: String,
    val realException: Throwable?
) : Exception(errorMessage) {

    companion object {

        /**
         * 此变量用于表示在网络请求过程过程中抛出了异常
         */
        const val CODE_ERROR_LOCAL_UNKNOWN = -1024520

    }

    /**
     * 是否是由于服务器返回的 code != successCode 导致的异常
     */
    val isServerCodeBadException: Boolean
        get() = this is ServerCodeBadException

    /**
     * 是否是由于网络请求过程中抛出的异常(例如:服务器返回的 Json 解析失败)
     */
    val isLocalBadException: Boolean
        get() = this is LocalBadException

}

/**
 * API 请求成功了,但 code != successCode
 * @param errorCode
 * @param errorMessage
 */
class ServerCodeBadException(errorCode: Int, errorMessage: String) :
    BaseHttpException(errorCode, errorMessage, null) {

    constructor(mode: IHttpWrapMode<*>) : this(mode.httpCode, mode.httpMsg)

}

/**
 * 请求过程抛出异常
 * @param throwable
 */
class LocalBadException(throwable: Throwable) : BaseHttpException(
    CODE_ERROR_LOCAL_UNKNOWN, throwable.message
        ?: "", throwable
)

有时候开发者需要对某些异常情况进行特殊处理,此时就可以来实现自己的 BaseHttpException 子类。例如,在 token 失效时接口一般是会返回特定一个 httpCode 用于表明移动端需要去更新 token 了,此时开发者就可以实现一个 BaseHttpException 的子类 TokenInvalidException 并在 generateBaseException 方法中返回,从而做到接口异常原因强提醒的效果,而不用去纠结 httpCode 到底是多少

class TokenInvalidException : BaseHttpException(CODE_TOKEN_INVALID, "token 已失效", null)

class SelfRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(
    iActionEvent = iActionEvent,
    httpBaseUrl = HttpConfig.BASE_URL_MAP,
    apiServiceClass = ApiService::class.java
) {

    /**
     * 如果外部想要对 Throwable 进行特殊处理,则可以重写此方法,用于改变 Exception 类型
     * 例如,在 token 失效时接口一般是会返回特定一个 httpCode 用于表明移动端需要去更新 token 了
     * 此时外部就可以实现一个 BaseException 的子类 TokenInvalidException 并在此处返回
     * 从而做到接口异常原因强提醒的效果,而不用去纠结 httpCode 到底是多少
     */
    override fun generateBaseException(throwable: Throwable): BaseHttpException {
        if (throwable is ServerCodeBadException && throwable.errorCode == BaseHttpException.CODE_TOKEN_INVALID) {
            return TokenInvalidException()
        }
        return if (throwable is BaseHttpException) {
            throwable
        } else {
            LocalBadException(throwable)
        }
    }

}

八、结尾

以上已经阐述了 ReactiveHttp 的大部分重点内容,但由于我的文笔以及表达能力问题,有些读者可能还是会看得云里雾里,建议还是将代码 clone 下来实际体验下。ReactiveHttp 的示例代码包含了一个完整的天气预报功能,通过示例代码可以帮助你更快入门 😇😇

此外再补充一点,ReactiveHttp 的 GitHub 提交历史现在看是从 2020 年 12 月份开始的,而 1.0 版本的文章是我在 2019 年 2 月份发布的,这是因为我觉得旧代码留有太多冗余资源,导致 clone 太慢,所以代码被我重置了,三个版本分别对应三条分支。1.0 版本和 2.0 版本只保留了当时的最后一次提交,感兴趣的读者可以参照我以前的两篇文章

点击跳转到 GitHub:ReactiveHttp