Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use Kotlin Cooperative Program in JD.com 's APP Business

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

How to use Kotlin collaborative program usage in JD.com APP business practice, many novices are not very clear about this, in order to help you solve this problem, the following editor will explain for you in detail, people with this need can come to learn, I hope you can gain something.

The purpose of collaborative program definition and design: collaborative program is a concurrent design pattern, which is a set of threading framework provided by Kotlin. Developers can write the code of different threads in the same code block and execute it in the same scope through the structured concurrency mechanism, which simplifies the code executed asynchronously and makes our code look linear. Analysis of usage

This article is based on the kotlinx-coroutines-android V1.3.8 version of the protocol library.

Basic concept

There are a few concepts we need to understand before using a collaborative process:

Coprogram scope CoroutineScope: defines the scope of a new co-program, which can be created, started, and managed through its extension functions, such as canceling the co-program under this scope. Kotlin protocol provides us with a set of built-in Scope: MainScope: scope using Dispatchers.Main scheduler LifecycleScope: binding with Lifecycle lifecycle binding ViewModelScope: binding to ViewModel lifecycle binding GlobalScope: lifecycle throughout the global

Protocol builder: an extension function of CoroutineScope that is used to build a protocol, such as launch,async

Collaborative process context CoroutineContext: an implementation of a left linked list, Job and Dispatcher scheduler can all be its elements. A very good function of CoroutineContext is that it can easily obtain Job, Dispatcher scheduler, etc.

CoroutineStart startup mode: DEFAULT: immediately scheduled, can be cancelled before execution LAZY: start when needed, need to be triggered by start, join and other functions to schedule ATOMIC: schedule immediately, the cooperative program will definitely be executed, and cannot be cancelled before execution UNDISPATCHED: execute immediately in the current thread until the first suspend is encountered

Dispatchers scheduler: DEFAULT: default scheduler, suitable for CPU-intensive task schedulers, such as logical computing Main:UI thread scheduler Unconfined: there is no limit on the threads of co-program execution, and the IO:IO scheduler can be in any thread when the co-program is resumed. It is suitable for IO-intensive task schedulers such as reading and writing files, network requests, etc.

Suspending lambda: a suspensible lambda expression whose full definition is suspend CoroutineScope. ()-> Unit, which is a "CoroutineScope extension function type" modified by the suspend modifier. As an extension function, its advantage is that it can directly access properties within the CoroutineScope.

Suspension point hang starting point: generally corresponds to the location where the pending function is called

Suspended function: a function modified by suspend. A suspended function can only be called in a pending function or in a co-program.

Creation and start-up of cooperative program

The concept chapter at the beginning introduces the use of the co-program builder for the construction of the co-program, and the co-program builder is the extension function of CoroutineScope.

LaunchcoroutineScope.launch (Dispatchers.IO) {/ / example (1) / / run on IO thread} coroutineScope.launch (Dispatchers.Main) {/ / example (2) / / run on UI thread}

In the above code, demonstrated the creation of a protocol, we take the example (1) as an example, it means that through the coroutineScope scope extension function launch to create a run in the IO thread, you can see that the code is still very clear, at this time you can do some time-consuming operations in the process. In the same example (2), a co-program running on the UI thread is created.

Val job: Job = coroutineScope.launch (Dispatchers.IO, CoroutineStart.LAZY) {/ / example (1) / / run on IO} job.start ()

In the above code, we modified example (1) by adding a parameter CoroutineStart.LAZY when calling the launch function and assigning the returned Job object to the variable job.

By default, the startup mode of the collaborative program is CoroutineStart.DEFAULT, that is, it will be executed immediately after it is created. In the example, the startup mode is set to CoroutineStart.LAZY. In this case, the launch function creates the collaborative program and does not start it. In this case, the startup of the collaborative program needs to be started by functions such as Job's start.

Job is a background work or asynchronous task that has a life cycle and can be cancelled. IsActive, isCompleted, isCancelled attributes are provided in Job to judge the status of the cooperative program, and the api to start the cooperative program start (), cancel the cooperative program cancel () and so on.

Async concurrency

If there is a requirement and there are two interfaces, one for obtaining user's personal information and the other for obtaining enterprise information, the refresh of UI can only be carried out when the data of both interfaces are obtained, then async concurrency will highlight its advantages.

CoroutineScope.launch (Dispatchers.Main) {val async1 = async (Dispatchers.IO) {/ / Network request 1 "simulate user information data acquisition"} val async2 = async (Dispatchers.IO) {/ / Network request 2 "simulate enterprise information data acquisition"} handleData (async1.await (), async2.await ()) / / simulate merged data}

In the above code, two coprograms are initiated through async to get the data, and the request result is obtained through await (). Because it is initiated in parallel, the speed is also quite fast.

The cooperative program return value created through async means a Deferred,Deferred with delay, which can be understood as waiting for a result. Deferred is also a Job, which is a subclass of Job, so it has the same function as Job.

Of course, the default startup mode of async is the same as launch, which is also executed immediately by CoroutineStart.DEFAULT. When the startup mode is set to CoroutineStart.LAZY, you can start the collaboration program through await () or through the start () function of Job.

Kotlin cooperative process advantage

In this chapter, we will compare several examples to show where the advantages of Kotlin collaboration process are. at the same time, the author suggests that when reading this chapter, we should not pay too much attention to the details of the implementation, but pay attention to the different implementation styles.

/ * * get user information * / private fun getUserInfo () {/ / example (1) apiService.getUserInfo () .enqueue (object: Callback {override fun onResponse (c: Call, re: Response) {runOnUiThread {tvName.text = response.body ()? .username}} override fun onFailure (call: Call) T: Throwable) {}})} / * * get user information protocol * / private fun getUserInfoByCoroutine () {/ / example (2) coroutineScope.launch (Dispatchers.Main) {val userInfo = coroutineApiService.getUserInfo () tvName.text = userInfo.userName}

This is an example of a network request to obtain user information, which is realized by ordinary CallBack and Kotlin protocol respectively.

Example (1) is a common way to initiate a network request, call back data through CallBack, and finally switch the main thread to refresh UI.

Example (2) is the implementation of the cooperative program. Through the extension function launch of scope, a cooperative program running in the main thread is created. In the implementation of the cooperative program, the UI is refreshed after getting the data.

Now let's compare the implementation of the two ways to see what is optimized in the implementation of the collaboration program. First of all, there is no callback of CallBack in the implementation of the protocol, and secondly, when refreshing the UI, it does not switch to the operation of the main thread, and finally the amount of code is relatively concise.

In fact, fortunately, the first way is that in our development, this CallBack callback should have been applied countless times, and it will not be too difficult to write it in minutes. Indeed, the advantages of Kotlin collaboration are not so obvious.

Next, let's look at a more complex scenario. Taking the merging of user information data and enterprise information data mentioned in async as an example, let's take a look at the more detailed implementation. Here we repeat the scenario: "there are two interfaces, one is used to obtain user's personal information and the other is used to obtain enterprise information. UI refresh can only be carried out when the data of both interfaces are obtained."

Normal way / * * start getting data * / private fun start () {getUserInfo () getCompanyInfo ()} / * * get user information * / private fun getUserInfo () {apiService.getUserInfo () .enqueue (object: Callback {override fun onResponse (c: Call) R: Response) {/ / determine whether you have got the company information / / refresh UI handle.post ()} override fun onFailure (call: Call) T: Throwable) {}})} / * * get company information * / private fun getCompanyInfo () {apiService.getCompanyInfo () .enqueue (object: Callback {override fun onResponse (c: Call) R: Response) {/ / determine whether the user information has been obtained / / refresh UI handle.post ()} override fun onFailure (call: Call, t: Throwable) {}})}

In this way, we encapsulate two API for the two interface requests and initiate network requests at the same time, which is relatively inconvenient to use. The key lies in the processing of the data. After getting the data of the user information, we need to determine whether the enterprise information is also obtained. The same is true for the data of enterprise information. Now there is only the merging of two groups of data, if it involves the acquisition of more information types of data. The corresponding logical processing becomes more and more complex.

Of course, if the logic is changed to serial, it is also easy to deal with, for example, get the user information data first, and then read the enterprise information data, but this way sacrifices time, and the parallel requests can be changed into serial ones. The request time is longer.

Kotlin protocol / * * get information kotlin protocol * / private fun getKotlinInfo () {coroutineScope.launch (Dispatchers.Main) {val userInfo = async {apiService.getUserInfo ()} / / get user information val companyInfo = async {apiService.getCompanyInfo ()} / / Company information MergeEntry (userInfo.await (), companyInfo.await ())}}

This is the implementation of the Kotlin protocol, which is implemented using CoroutineScope's async builder. When more requests are needed, its logic processing is very convenient, one more request and one more async. Parallel requests save time, eliminate callbacks, and do not need to switch threads.

The use of cooperative programs

After understanding the creation, startup, and advantages of collaborative programs, there is a question: when do we use collaborative programs? When we need to process time-consuming data, we can use the co-program to switch to the child thread for execution. When we need to refresh the UI after processing the data, we can use the co-program to switch to the main thread. In fact, we can use the co-program to process when we need to specify the running thread.

CoroutineScope.launch (Dispatchers.IO) {/ / run on the IO thread handleFileData () / / simulate the time-consuming operation of reading files launch (Dispatchers.Main) {/ / data processing completed refresh UI tvName.text = ""}}

In the above code, there is a time-consuming read file operation, so here we use the co-program to switch to the IO thread through launch to handle the time-consuming operation. After the processing is completed, we cut to the Main thread to refresh the UI through the launch function. There seems to be nothing wrong with it. Let's move on to the next section of code.

CoroutineScope.launch (Dispatchers.IO) {/ / run on the IO thread handleFileData () / / Analog read file launch (Dispatchers.Main) {/ / data processing completed refresh UI launch (Dispatchers.IO) {/ / processing data launch (Dispatchers.Main) {/ / data processing finished Cheng refresh UI launch (Dispatchers.IO) {launch (Dispatchers.Main) {launch (Dispatchers.IO) {launch (Dispatchers.Main) {}} }}

The scenario demonstrated in this example is extreme, and it is rare to encounter IO and Main thread switching so frequently in development, just to expose the problem. We said earlier that the Kolin protocol eliminates callbacks, but in this example it is very callback and nested layer by layer.

Because the use of launch and async protocol builder functions alone can not well deal with such complex scenarios where threads need to be switched frequently, in order to solve the problem in the example, Kotlin protocol provides us with some other functions to use together, such as withContext suspend function.

WithContext pending function

WithContext is a suspend function provided by the Kotlin protocol, which provides the following functions:

You can switch to the specified thread to run

After the function body is executed, it automatically switches back to the original thread.

CoroutineScope.launch (Dispatchers.Main) {/ / start a coprogram in the main thread val data = withContext (Dispatchers.IO) {/ / cut to IO thread processing time-consuming operation handleFileData () / / run on IO thread} tvName.text = data / / withContext function body is finished Custom switch to main thread refresh UI} coroutineScope.launch (Dispatchers.Main) {withContext (Dispatchers.IO) {/ / * * Operation (1) * * / switch IO thread / /. Execute on IO thread} / /.. Perform the * * operation (2) * * withContext (Dispatchers.IO) {/ / switch the IO thread / / on the UI thread. Execute on IO thread} / /.. Execute withContext (Dispatchers.IO) {/ / switch IO thread / / on UI thread. Execute on IO thread} / /.. Execute / / on the UI thread. Wait.}

After the transformation using withContext, the nesting is eliminated and the code becomes clear. Therefore, in addition to launch and other extension functions, Kotlin protocol also needs withContext and other pending functions to reflect its advantages.

It is necessary to mention that when a thread is not used, two branches will appear in the code, such as the operation (1) in the above code, which cuts to the IO thread execution, which is a branch, followed by the execution operation (2), which is another branch, each of which goes its own way, "almost synchronously."

But in the cooperation program, after operation (1) uses the withContext suspend function to switch to the IO thread to perform its operation, it does not perform the operation (2), but waits for the withContext execution of the operation (1) to be completed, and when the switching thread returns to the main thread, the operation (2) will be performed, which will be explained in the following supend chapters.

Public suspend fun withContext (context: CoroutineContext, block: suspend CoroutineScope. ()-> T): t {}

In the above example, getData () is a normal function in which when the withContext function is suspended, it prompts the error message: suspend function 'withContext' should be called only from a coroutine or another supend function, which means that withContext is a function modified by suspend and should be called in the co-program or another spspend function. WithContext in the source code is modified by suspend.

Suspend

Suspend is a keyword of the Kotlin protocol. The function modified by suspend is a pending function, which can only be called in the co-program or another pending function.

From the developer's point of view, the function of the suspend keyword is a reminder, reminder of what? Remind the caller of the function that this is a pending function, and there is a time-consuming operation inside it, which needs to be called in the co-program or another pending function.

However, in terms of the compilation process, functions modified by suspend have a special interpretation, such as adding a parameter Continuation, which is why suspending functions cannot be called in ordinary functions.

Suspend function? Who's hanging?

Just now we said that the function modified by suspend is a pending function, which literally means that it is not executed or paused. Here is a question, who is suspending? Is it a thread? Function? Or Xiecheng?

In fact, the program is suspended. It can be understood that when the suspend suspend function is executed in the program, the execution of the subsequent code of the program will be suspended. Let's analyze the execution flow of the following code.

CoroutineScope.launch (Dispatchers.Main) {/ / start a coroutine in the main thread (1) val data = withContext (Dispatchers.IO) {/ / cut to IO thread processing time-consuming operation (2) handleFileData () / / run on IO thread (3)} tvName.text = data / / withContext function body is finished, custom switch to main thread refresh UI (4)}

A cooperative program running on the Main thread is started through CoroutineScope's extension function launch. When the cooperative program executes the withContext pending function, the IO thread cut by withCotext performs the time-consuming operation of its own function body, while the subsequent code of the cooperative program will be suspended. This is also the most magical part of the cooperative program.

So when will the subsequent code be executed? The co-program is suspended, and there are corresponding recovery operations. Here, the recovery of the co-program is involved. When the execution of the withContext suspension function is completed, the co-program will switch back to the original thread (if the thread before suspension is a child thread, it may be recycled because the thread is idle, and the thread cut back is not necessarily 100% of the original thread) to continue to execute the remaining code, such as refreshing UI in the example.

To sum up the concept of Kotlin protocol suspension, what is a hang? It can be understood as two operations:

The execution of the cooperation program was "suspended".

The coordination process is "resumed".

More commonly, when the Kotlin protocol executes to a suspended function, it will switch the thread to the thread specified by the suspended function, and the subsequent code will be suspended. When the execution of the suspended function is completed, it will switch the thread back to the original thread and resume the execution of the remaining code.

In addition, let's talk about the suspended non-blocking type:

Or take the above code as an example, operation (1) starts a co-program in the Main thread, and when the co-program executes to the operation (2), it cuts to the IO thread to perform the operation (3), and the operation (4) is paused and not executed, but is the Main thread blocked? No, the main thread does what it is supposed to do. this is the suspended non-blocking type, although it is suspended, but it is itself, the cooperative program, and does not block the original thread.

JD.com APP business practice business background

This paper is explained by the data processing of the core floor, which needs to assemble the bottom data and the dynamic data sent by the interface, and finally integrate into the data source needed by the business.

Gradle depends on configuring dependencies {implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'} Code implementation / * * scope of the protocol * / privateval scope = MainScope () private fun assembleDataList (response: PlatformResponse?) = scope.launch (CoroutineExceptionHandler {_ Exception-> / * * uncaught exception handling * /}) {val localStaticData: Deferred = async (start = CoroutineStart.LAZY) {getLocalStaticData ()} val dynamicData: Deferred = async (start = CoroutineStart.LAZY) {getDynamicData (response)} getAssembleDataListFunc (localStaticData.await (), dynamicData.await ())}

We create a new coprogram under the current MainScope and start it through the scope builder extension function launch. In the lambda expression of the launch function, we use the async function and declare that the start parameter is set to CoroutineStart.LAZY lazy mode to create a subroutine (but this protocol is not immediately executed), which returns a Deferred object, and Deferred is a Job extension with a return value (similar to the Futuer object in Java). The subroutine is actually executed only when we actively call the await or start function of Deferred.

Execution process

Compared with RxJava implementation, RxJava implements private void assembleDataList (PlatformResponse response) {Observable localStaticData = getLocalStaticData (); Observable assembleData = getDynamicData (response); Func2 assembleData = getAssembleDataListFunc (); Observable observable = Observable.zip (localStaticData, assembleData, assembleData); subscribe (observable, callback);}

As you can see from the implementation code, we use the zip operator to combine the sequence of events sent by the two observers, localStaticData and assembleData, to generate a new sequence of events and send them. (here we will not discuss whether the two sequences of events, localStaticData and assembleData, are executed serially or in parallel.)

Zip operator event flow diagram (picture from ReactiveX official website)

Compared with our business scenario, collaborative program and RxJava implementation can meet our needs, so what's the difference between them before? let's talk about the advantages of RxJava: it solves the problem of Java asynchronous callback nesting, which improves the readability and maintainability of the code; chained calls flatten the invocation of events in configuration phase, run phase and subscription phase; thread scheduling makes switching threads easy and elegant. Disadvantages of RxJava:

Observable firstObservable = Observable.create (new Observable.OnSubscribe () {@ Override public void call (Subscriber)

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report