Not long ago, I completed my first web request for ingredient management with the following code. Although it was a bit of a gimmick, it was also a bit exciting. If you’ve used AsyckTask, you’ll notice that both AsyckTask and the code below have three concepts in API design: front, middle and back. By reading AsyckTask’s source code, we discovered that there’s a lot going on inside the seemingly obvious API.
start { dpseList.value? .clear() }.request { forkApi(AhrmmService::class.java).enabledList(DevicePeripheralScaleEnabledListRequest()) }.then({ dpseList.value? .addAll(it.data) })Copy the code
In fact, as early as blue +2.0.0 revision, the network framework began to change, recall before that, how we request an interface and get data to do some business processing?
HTTP network interaction + use API
In my opinion, the so-called network architecture that normally meets our development needs actually contains two parts. One is the part that helps us splice request packets, initiate request, receive server response and preprocess response packets based on HTTP protocol. And the other part is repackaged so that we can use it more flexibly and efficiently. The former is very likely/almost impossible for us to write ourselves, so all we can do is choose to work with a better official/open source framework; The latter is the part we can/should focus on optimizing to make business development more enjoyable.
AsyncHttpClient+AsyncHttpResponseHandler
Angel is probably one of the oldest projects in the world. By now, we routinely search onSuccessResponse when we’re maintaining an interface, debugging or rewriting business logic on Angel, because it’s a callback to a successful search interface request. In fact, These callback methods can be overwritten on activities/fragments by inheriting AsyncHttpResponseHandler through several transitions, with some preprocessing/unified error handling in between. In today’s view, although the practice is not good, the effect is also limited, but at that time we have been based on the two parts of the network architecture, HTTP protocol network interaction using AcyncHttp, the use of API is to transform its own AsyncHttpResponseHandler abstract class.
OkHttp3+Retrofit2&Rxjava2
To understand the maintenance of the old project is not easy, maintenance stability first, so when blue +2.0.0 revision, the team like a rain of rain to absorb a variety of new technology open source framework, OkHttp3+Retrofit2&Rxjava2 is the network architecture of the big change, OkHttp3 is responsible for HTTP protocol interaction part, There are a number of handy apis that are defined strictly according to the Http protocol. Although we rarely use them, the default configuration is sufficient for 90% of the usage scenarios, but a strong enough network request framework does give people confidence. Let’s look at web requests that use Okhttp3 purely:
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()) {
returnresponse.body().string(); }}Copy the code
If it’s a Demo, of course, it doesn’t matter, but in the development of the project we need some configuration, encapsulation of the encapsulation, abstraction of the abstraction; The companion Retrofit allows us to separate our businesses in a very simple way:
/** * 1. User login */ @post ("bm-officeAuto-admin-api/common/user/login")
fun login(@Body body: RequestLogin): Observable<ResultLogin>
Copy the code
Retrofit is one of the most beautiful APIS I’ve ever seen, because it creates objects and handles the logic for them through dynamic proxies.
And then will return to the beginning said before, during and after the three concepts, we used to take for granted the API actually inevitable hidden inside thread switching process, it is also very simple, the network request is time-consuming operation, originally should not be on the main thread, and the part data and the interface interaction is on the main thread, so complete an interface request, Loading data into the interface requires switching between at least two threads, and how to make this switch hidden from business development and completely comfortable for business development is the most important consideration for the API, along with other issues such as flexibility and configuration.
RxJava, on the other hand, smoothes out thread switching rather than nesting it, making it very intuitive — a sort of streaming code structure to do something first and then do something later.
mModel.login(rl)
// do// retryWhen an error occurs. The first parameter is retry times and the second parameter is retry interval. RetryWhen (RetryWithDelay(3, 2)) .doOnSubscribe { mRootView.showLoading() } .compose(BPUtil.commonNetAndLifeHandler<ResultLogin>(mRootView.getActivity(), Schedulers.io()) Calls to obtain information. 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. The userInfo = the userInfo. User / / to the main page Utils. Navigation (mRootView as Context, RouterHub.APP_OAMAINACTIVITY) mRootView.killMyself() } })Copy the code
At this point, our network architecture has completed its first turnaround, in which our overall architecture has gone from MVC->MVP.
OkHttp3+Retrofit2&Coroutine
Going back to the code at the beginning, let’s put out the API in its entirety:
@POST("md/device/peripheral/scale/enabledList")
suspendfun 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 = {})
Copy the code
Let’s compare the above implementation of RxJava to see what happens to the code that does the same thing:
thread
Schedulers.io() and observeOn(schedulers.io ()).
Coroutine: Hidden internally identified by keyword: suspend
Omit the API
Some common error handling
RxJava: active call: compose
Coroutine: The Coroutine name is optional, but the Coroutine name is empty
other
Coroutine is much cleaner and more intuitive. When you first see these two pieces of code, which one do you prefer?
RxJava is much more powerful, much more versatile, and unfortunately 80 or 90 percent of our use scenarios are common before, during, and after.
So, what about special requests?
The answer is special problems, special handling, and there are trade-offs when you design an API, and the more you try to accommodate more situations, the more complex it becomes to design and use, and I think it’s enough to cover most of the usage scenarios and not to over-optimize.
For example, the need for two interfaces to be completed at the same time in order to move forward is rare:
LifecycleScope. LaunchWhenResumed {try {coroutineScope {/ / exception two-way transmission mode loading. The 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)}}Copy the code
This code tells us that both the materialDetails and orderList are requested at the same time. We want to save time by concurrency, and the subsequent business can proceed only when both interfaces succeed at the same time.
In fact, Coroutine offers a lot of powerful and concise apis, which our team should focus on learning, and again, cost effective.