Android網絡架構演進

不久前,我用以下代碼完成了 配料管理 的第一個網絡請求,雖然是拾人牙慧的東西,可是也有點小興奮,若是你用過AsyckTask你就會發現,在API設計上,AsyckTask和下面這段代碼都有前、中、後三個概念,咱們經過閱讀AsyckTask的源碼發現了表面上貌似理所固然的API其實內裏大有文章。java

start { dpseList.value?.clear() }.request {
    forkApi(AhrmmService::class.java).enabledList(DevicePeripheralScaleEnabledListRequest())
}.then({
    dpseList.value?.addAll(it.data)
})
複製代碼

其實早在 藍+2.0.0 改版的時候,網絡框架就開始改變了,回想在那以前,咱們是怎麼請求一個接口並得到數據作一些業務上的處理的?api

HTTP協議網絡交互+使用API

我認爲,正常知足咱們開發需求的所謂的網絡架構,其實包含兩個部分,一個是基於HTTP協議幫咱們拼接請求報文、發起請求、收到服務器響應和預處理響應報文的部分;而另外一個就二次封裝以便咱們更靈活、更高效使用的部分了。前者咱們大機率/幾乎沒可能本身寫,因此咱們能作的只有選擇工做,選擇更好的官方/開源框架;然後者纔是咱們有能力/應該花心思解決優化以便業務開發上用起來更爽的部分。bash

AsyncHttpClient+AsyncHttpResponseHandler

天使 應該是首屈一指的元老級項目,目前爲止咱們在天使上維護某個接口、須要debug或者重寫業務邏輯的時候,老是習慣性的搜一下onSuccessResponse,由於他們是咱們界面搜索接口請求成功的回調,事實上,這些能夠在activity/fragment上經過重寫的回調方法,他們都是經過繼承改造AsyncHttpResponseHandler,經過幾迴轉接、中間作一下預處理/統一錯誤處理實現的。今天看來,雖然作法不太好,效果也有限制,可是那個時候咱們就已是按照這兩部分來作網絡架構的了,其中HTTP協議網絡交互用的是AcyncHttp,使用API則是改造了它自帶的AsyncHttpResponseHandler抽象類。服務器

OkHttp3+Retrofit2&Rxjava2

要體諒舊項目的維護不易,維穩第一,因此 藍+2.0.0 改版的時候,團隊猶如久旱逢甘霖般盡情吸取着各類新鮮技術開源框架,其中OkHttp3+Retrofit2&Rxjava2就是網絡架構上的大改動,OkHttp3負責HTTP協議交互部分,內裏嚴格根據Http協議定義了不少方便好用的API,雖然咱們不多用到,默認配置就足以知足90%使用場景,可是一個足夠強大的網絡請求框架確實能給人帶來自信,下面咱們看純粹使用Okhttp3的網絡請求:網絡

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
複製代碼

若是是作一個Demo固然是無所謂,可是在項目開發中咱們還須要一些配置,該封裝的封裝,該抽象的抽象;而配套的Retrofit則能幫助咱們實現業務上的分離,並且分離的方式很簡潔:架構

/**
 * 1.用戶登陸
 */
@POST("bm-officeAuto-admin-api/common/user/login")
fun login(@Body body: RequestLogin): Observable<ResultLogin>
複製代碼

能夠看到,短短一個抽象方法,已經包含了method、path、請求body,響應body四部分重要的信息,最重要的是,它不須要本身實現,Retrofit經過動態代理的方式幫你建立對象處理相應的邏輯,能夠說它是我見過的首屈一指漂亮的API了。併發

緊接着就又要回到一開始就說到的前、中、後三個概念,咱們用慣的理所固然的API其實內部必然隱藏着線程切換的過程,道理也很簡單,網絡請求是耗時操做,原本就不該該放到主線程,而數據與界面交互的部分卻又是必然要放在主線程的,因此完成一個接口的請求,加載數據到界面上最少要在兩個線程上切換,而怎麼使得這個切換對業務開發隱藏,使得業務開發徹底無感/感到溫馨,就是咱們設計這個API要考慮的最重要的問題,固然還有其它諸如使用靈活、配置方面的問題。app

而RxJava就可使得線程的切換,再也不一昧的嵌套,而是將其鋪平,使得整個線程切換過程變得很是符合直覺——一種先作什麼、再作什麼的流式代碼結構框架

mModel.login(rl)
        // doOnSubscribe以後再次調用才能改變被觀察者的線程
        .subscribeOn(Schedulers.io())
        //遇到錯誤時重試,第一個參數爲重試幾回,第二個參數爲重試的間隔
        .retryWhen(RetryWithDelay(3, 2))
        .doOnSubscribe { mRootView.showLoading() }
        .compose(BPUtil.commonNetAndLifeHandler<ResultLogin>(mRootView.getActivity(), mRootView))
        .observeOn(Schedulers.io())
        // 登錄成功,調用獲取信息
        .flatMap {
            ClientStateManager.loginToken = it.token
            val body = RequestBase.newInstance()
            return@flatMap mModel.getUserInfo(body)
        }
        .compose(BPUtil.commonNetAndLifeHandler<ResultGetUserInfo>(mRootView.getActivity(), mRootView))
        .doFinally {
            mRootView.hideLoading()
            mRootView.disableLogin(false)
        }
        .subscribe(object : ErrorHandleSubscriber<ResultGetUserInfo>(mErrorHandler) {
            override fun onNext(userInfo: ResultGetUserInfo) {
                ClientStateManager.userInfo = userInfo.user
                // 去主頁面
                Utils.navigation(mRootView as Context, RouterHub.APP_OAMAINACTIVITY)
                mRootView.killMyself()
            }
        })
複製代碼

至此,咱們網絡架構完成了第一個轉身,其中咱們的總體架構也從MVC->MVPasync

OkHttp3+Retrofit2&Coroutine

再看回開頭的那段代碼,咱們將其省略掉的API完整放出來:

@POST("md/device/peripheral/scale/enabledList")
    suspend fun enabledList(@Body bean: DevicePeripheralScaleEnabledListRequest): DevicePeripheralScaleEnabledListResult

start { dpseList.value?.clear() }.request {
    forkApi(AhrmmService::class.java).enabledList(DevicePeripheralScaleEnabledListRequest())
}.then(onSuccess = {
    dpseList.value?.addAll(it.data)
},onException = {
    false
},onError = {
    
},onComplete = {})
複製代碼

咱們對比上面的RxJava的實現,來看看兩者實現一樣做用的相關代碼的狀況:

線程切換

RxJava:主動調用:subscribeOn(Schedulers.io()),observeOn(Schedulers.io())

Coroutine:隱藏在內部經過關鍵字識別:suspend

省略API

一些公共的錯誤處理

RxJava:主動調用:compose

Coroutine:具名可選,省略則爲空

其它

Coroutine更加簡潔直觀,試想第一看到這兩段代碼,你更願意用哪種?

RxJava功能更增強大,通用性更強,遺憾的是咱們百分之八九十的使用場景都是最普通的前、中、後就能夠完成了。

那麼,對於一些特殊的請求咱們怎麼辦呢?

答案是特殊問題,特殊處理,設計API的時候都會面對這種權衡取捨的問題,越想兼容更多狀況,設計起來和用起來就越複雜,我認爲覆蓋大多數使用場景已經足矣,不該該過分優化。

舉個例子,兩個接口併發,同時完成才能接着日後走,這種需求就是少見中的大多數了把:

lifecycleScope.launchWhenResumed {
    try {
        coroutineScope {
            //異常雙向傳遞模式
            loading.value?.set(true)
            forkApi(DosingService::class.java).apply {
                val mds = async {
                    materialDetails(MaterialDetailsRequest(materialCode))
                }
                val dolr = async {
                    orderList(DosingOrderListRequest(materialCode, true))
                }
                mdData.value?.set(mds.await().data)
                listViewModel?.recordList?.value?.addAll(dolr.await().data)
            }
        }
    } catch (e: Exception) {
    } finally {
        loading.value?.set(false)
    }
}
複製代碼

這段代碼講的是,同時請求materialDetailsorderList,咱們想併發節約時間,而且後續業務時須要兩個接口同時成功才能夠進行下去的。

事實上Coroutine提供了不少強大而簡潔的API,這個建議咱們團隊重點學習,仍是那句話,性價比很高。

相關文章
相關標籤/搜索