不久前,我用以下代码完成了 配料管理 的第一个网络请求,虽然是拾人牙慧的东西,可是也有点小兴奋,若是你用过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,这个建议咱们团队重点学习,仍是那句话,性价比很高。