一步步封裝實現自己的網絡請求框架 3.0


在 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.leavesC: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則需要用泛型來聲明了,最終就對應移動端的一個泛型類,類似於 HttpWrapBean

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

data class HttpWrapBean<T>(val status: Int, val msg: String, val data: T)
復制代碼

然后在 interface 中聲明 Api 接口,這也是使用 Retrofit 的慣常用法。根據項目中的實際情況,開發者可能是使用 Call 或者 Observable 作為每個接口返回值的最外層的數據包裝類,然后再使用 HttpWrapBean 來作為具體數據類的包裝類

interface ApiService {

    @POST("api1")
    fun api1(): Observable<HttpWrapBean<Int>>

    @GET("api2")
    fun api2(): Call<HttpWrapBean<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<HttpWrapBean<Int>> = service.api1()
    call.subscribe(object : Consumer<HttpWrapBean<Int>> {
        override fun accept(userBean: HttpWrapBean<Int>?) {

        }

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

        }
    })
復制代碼

五、簡單入門

ReactiveHttp 在使用上會比上面給出的例子簡單很多,下面就來看下通過 ReactiveHttp 如何完成網絡請求

ReactiveHttp 需要知道網絡請求的結果,但不知道外部會使用什么字段名來標識 HttpWrapBean 中的三個值,所以需要外部實現 IHttpWrapBean 接口來進行標明。例如,你可以這樣來實現:

data class HttpWrapBean<T>(val status: Int, val msg: String, val data: T) : IHttpWrapBean<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(): HttpWrapBean<List<DistrictBean>>

}
復制代碼

ReactiveHttp 提供了 RemoteExtendDataSource 交由外部來繼承實現。RemoteExtendDataSource 包含了所有的網絡請求方法,外部僅需要根據實際情況來實現三個必要的字段和方法即可

  • releaseUrl。即應用的 BaseUrl
  • createRetrofit。用於創建 Retrofit,開發者可以在這里自定義 OkHttpClient
  • showToast。當網絡請求失敗時,通過該方法來向用戶提示失敗原因

例如,你可以像以下這樣來實現你自己項目的專屬 DataSource,當中就包含了開發者整個項目的全局網絡請求配置

class SelfRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(iActionEvent, 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()
        }

    }

    /**
     * 由子類實現此字段以便獲取 release 環境下的接口 BaseUrl
     */
    override val releaseUrl: String
        get() = "https://restapi.amap.com/v3/"

    /**
     * 允許子類自己來實現創建 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<DistrictBean>>()

    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 {

            }
        }
    }
復制代碼

六、進階使用

上述在使用 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 的話,可以自己來實現 IUIActionEventObserver 接口,就像以下這樣

@SuppressLint("Registered")
abstract class BaseActivity : AppCompatActivity(), IUIActionEventObserver {

    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(msg: String) {
        if (loadDialog == null) {
            loadDialog = ProgressDialog(lContext).apply {
                setCancelable(false)
                setCanceledOnTouchOutside(false)
            }
        }
        loadDialog?.let {
            if (!it.isShowing) {
                it.show()
            }
        }
    }

    override fun dismissLoading() {
        loadDialog?.let {
            if (it.isShowing) {
                it.dismiss()
            }
        }
    }

    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 BaseViewModel : 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 forecastsBeanLiveData = MutableLiveData<ForecastsBean>()

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

}

class WeatherActivity : BaseReactiveActivity() {

    private val weatherViewModel by getViewModel(WeatherViewModel::class.java) {
        forecastsBeanLiveData.observe(it, Observer {
            showWeather(it)
        })
    }

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

    private fun showWeather(forecastsBean: ForecastsBean) {

    }

}
復制代碼

七、其它

1、BaseRemoteDataSource

RemoteExtendDataSource 提供了許多個可以進行復寫的方法,既可用於配置 OkHttp 的各個網絡請求參數,也用於交由外部進行流程控制。例如,你可以這樣來實現自己項目的 BaseRemoteDataSource

class BaseRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(iActionEvent, 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()
        }
    }

    /**
     * 由子類實現此字段以便獲取 release 環境下的接口 BaseUrl
     */
    override val releaseUrl: String
        get() = HttpConfig.BASE_URL_MAP

    /**
     * 允許子類自己來實現創建 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
    }

    /**
     * 用於將網絡請求過程中的異常反饋給外部,以便記錄
     * @param throwable
     */
    override fun exceptionRecord(throwable: Throwable) {
        Log.e("SelfRemoteDataSource", throwable.message ?: "")
    }

    /**
     * 用於對 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 中聲明一個 BaseRemoteDataSource 變量實例,所有子 ViewModel 都全局統一使用同一份 DataSource 配置。如果有某些特定接口需要使用不同的 BaseUrl 的話,也可以再多聲明一個 BaseRemoteDataSource

open class BaseViewModel : BaseReactiveViewModel() {

    /**
     * 正常來說單個項目中應該只有一個 RemoteDataSource 實現類,即全局使用同一份配置
     * 但父類也應該允許子類使用一個獨有的 RemoteDataSource,即允許子類復寫此字段
     */
    protected open val remoteDataSource by lazy {
        BaseRemoteDataSource(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(bean: IHttpWrapBean<*>) : this(bean.httpCode, bean.httpMsg)

}

/**
 * 請求過程拋出異常
 * @param throwable
 */
class LocalBadException(throwable: Throwable) : BaseHttpException(CODE_ERROR_LOCAL_UNKNOWN, throwable.message?: "", throwable)
復制代碼

有時候開發者需要對某些異常情況進行特殊處理,此時就可以來實現自己的 BaseHttpException 子類。例如,在 token 失效時接口一般是會返回特定一個 httpCode 用於表明移動端需要去更新 token 了,此時開發者就可以實現一個 BaseHttpException 的子類 TokenInvalidException 並在 BaseRemoteDataSource 中進行返回,從而做到接口異常原因強提醒的效果,而不用去糾結 httpCode 到底是多少

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

open class BaseRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(iActionEvent, 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()
        }
    }

    /**
     * 由子類實現此字段以便獲取 release 環境下的接口 BaseUrl
     */
    override val releaseUrl: String
        get() = "https://restapi.amap.com/v3/"

    /**
     * 允許子類自己來實現創建 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 {
        if (throwable is ServerCodeBadException && throwable.errorCode == BaseHttpException.CODE_TOKEN_INVALID) {
            return TokenInvalidException()
        }
        return if (throwable is BaseHttpException) {
            throwable
        } else {
            LocalBadException(throwable)
        }
    }

    /**
     * 用於由外部中轉控制當拋出異常時是否走 onFail 回調,當返回 true 時則回調,否則不回調
     * @param httpException
     */
    override fun exceptionHandle(httpException: BaseHttpException): Boolean {
        return httpException !is TokenInvalidException
    }

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

}
復制代碼

八、結尾

以上已經闡述了 ReactiveHttp 的大部分重點內容,但由於我的文筆以及表達能力問題,有些讀者可能還是會看得雲里霧里,建議還是將代碼 clone 下來實際體驗下。ReactiveHttp 的示例代碼包含了一個完整的天氣預報功能,通過示例代碼可以幫助你更快入門 😇😇

此外再補充一點,ReactiveHttp 的 GitHub 提交歷史現在看是從 2020 年 12 月份開始的,而 1.0 版本的文章是我在 2019 年 2 月份發布的,這是因為我覺得舊代碼留有太多冗余資源,導致 clone 太慢,所以代碼被我重置了,三個版本分別對應三條分支。1.0 版本和 2.0 版本只保留了當時的最后一次提交,感興趣的讀者可以參照我以前的兩篇文章進行查看

點擊跳轉到 GitHub:ReactiveHttp

作者:葉志陳
鏈接:https://juejin.cn/post/6932650811642085389
來源:掘金


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM