不久前,我用以下代碼完成了 配料管理 的第一個網絡請求,雖然是拾人牙慧的東西,可是也有點小興奮,若是你用過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協議幫咱們拼接請求報文、發起請求、收到服務器響應和預處理響應報文的部分;而另外一個就二次封裝以便咱們更靈活、更高效使用的部分了。前者咱們大機率/幾乎沒可能本身寫,因此咱們能作的只有選擇工做,選擇更好的官方/開源框架;然後者纔是咱們有能力/應該花心思解決優化以便業務開發上用起來更爽的部分。bash
天使 應該是首屈一指的元老級項目,目前爲止咱們在天使上維護某個接口、須要debug或者重寫業務邏輯的時候,老是習慣性的搜一下onSuccessResponse
,由於他們是咱們界面搜索接口請求成功的回調,事實上,這些能夠在activity
/fragment
上經過重寫的回調方法,他們都是經過繼承改造AsyncHttpResponseHandler
,經過幾迴轉接、中間作一下預處理/統一錯誤處理實現的。今天看來,雖然作法不太好,效果也有限制,可是那個時候咱們就已是按照這兩部分來作網絡架構的了,其中HTTP協議網絡交互用的是AcyncHttp
,使用API則是改造了它自帶的AsyncHttpResponseHandler
抽象類。服務器
要體諒舊項目的維護不易,維穩第一,因此 藍+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->MVP。async
再看回開頭的那段代碼,咱們將其省略掉的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
一些公共的錯誤處理
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)
}
}
複製代碼
這段代碼講的是,同時請求materialDetails
和orderList
,咱們想併發節約時間,而且後續業務時須要兩個接口同時成功才能夠進行下去的。
事實上Coroutine
提供了不少強大而簡潔的API,這個建議咱們團隊重點學習,仍是那句話,性價比很高。