1. What is a protocol
There are many definitions of a collaboration. In Kotlin, a reasonable definition of a collaboration should be a threading framework (a throw line) or a concurrent design pattern (official). It is an official set of API s designed to facilitate multi-threaded development by developers.
2.What can a project do
The main uses of collaborations are summarized as multithreaded code that can be implemented in more elegant code. This is mainly reflected in the synchronization of work that would otherwise require asynchronous callbacks. For a simple example, get user information from the server and display it as follows:
api.sendRequest(object : Callback { override fun onFail(err: Error) { } override fun onSucc(resp: String) { funOnUiThread { updateUI(resp) } } })
The above is a simpler example, which is nested twice. If a network request is dependent, it will be more complex to implement:
api.fetchUserInfo(object : Callback { override fun onFail(err: Error) { } override fun onSucc(resp: String) { api.fetchDetail(object : Callback { override fun onFail(err: Error) { } override fun onSucc(resp: String) { funOnUiThread { updateUI(resp) } } }) } })
Here's how we work together:
launch(Dispatcher.Main) { val user = withContext(Dispatchers.IO) { api.fetchUserInfo() } var detail = withContext(Dispatchers.IO) { api.fetchUserInfo(useruser) } updateUI(detail) }
You can see that the code is much simpler. The code block above can be interpreted as a protocol, which executes in the main thread and switches to the IO thread to execute when requesting user information. Because the network request is a time-consuming operation, the IO thread is blocked and waiting for the result of the network request. At this time, the protocol suspends waiting for the result of the request, but it is not blocked.Plugging the UI thread, which we can understand as detaching from the current thread executing it (UI thread), frees the current thread. When the network request gets the result, the code is reloaded to the UI thread to continue execution.
From the code above, we can see that through a collaboration we can use code that looks synchronized to do the work that actually needs to be switched between different threads.
3.Usage of protocols
1.Simply start a protocol
GlobalScope.launch(Dispatchers.Main) { Log.v("Tag", "Coroutines in Thread.currentThread().name") }
Start threads have launch and async methods, which use launch method to run the process in the main thread. The system mainly provides the following three dispatchers. Main executes the program in the main thread, usually doing UI-related work, IO runs the program on the IO thread, which is specially optimized for I/O operations on disk and network, Default is specially optimized forA lot of CPU computing.
Dispatchers.Main Dispatchers.Default Dispatchers.IO
2.Switch Threads
After starting a protocol, you can use withContext() to control the thread pool where code runs without callbacks:
GlobalScope.launch(Dispatchers.Main) { Log.v("Tag", "xxxxxx1 in " + Thread.currentThread().name) withContext(Dispatchers.IO) { Thread.sleep(1000) Log.v("Tag", "xxxxxx2 in " + Thread.currentThread().name) } Log.v("Tag", "xxxxxx3 in " + Thread.currentThread().name) }
The result of running the code above:
Tag: xxxxxx1 in main
Tag: xxxxxx2 in DefaultDispatcher-worker-1
Tag: xxxxxx3 in main
We can see that the code runs on the main thread, then cuts to the IO thread, and then cuts back to the main thread to continue. This process does not block the main thread, it just suspends the collaboration.
3.Suspend function
In the example of switching threads above, with withContext ()The function suspends the current thread, but if you want to put the work done after switching threads into a separate function, you need to declare the function suspend. The suspend function can only be called in a coprocess or another suspend function. When the suspend function is called, the coprocess suspends, that is, it detaches from the current thread, and waits until the function has been executed before cutting back to the original thread to continue.Execute.
GlobalScope.launch(Dispatchers.Main) { Log.v("Tag", "xxxxxx1 in " + Thread.currentThread().name) doIO() Log.v("Tag", "xxxxxx3 in " + Thread.currentThread().name) } suspend fun doIO() { withContext(Dispatchers.IO) { Thread.sleep(1000) Log.v("Tag", "xxxxxx2 in " + Thread.currentThread().name) } }
4. Agreement Cancellation and Timeout
If we start a protocol on one page, we need to cancel it when the page is closed, otherwise we will get an error when the protocol cuts back to the main thread to update the UI. Another scenario is that we usually need to set a timeout when making network requests. Here are some examples of cancelling and timeout of a protocol:
GlobalScope.launch(Dispatchers.Main) { var job = GlobalScope.launch(Dispatchers.Default) { repeat(10) { Log.v("Tag", "xxxxxx1 $it ") delay(500) } } delay(2000) job.cancel() //job.join() }
The join() function below is a suspend function, which means to wait for the collaboration work to complete. That is, to wait for the job collaboration work to complete and the current collaboration to continue.
GlobalScope.launch(Dispatchers.Main) { var user = doIO() Log.v("Tag", "xxxxxx result $user") } suspend fun doIO(): String? { var res = withContext(Dispatchers.IO) { withTimeoutOrNull(4000) { delay(3000) "done" } } return res }
The withTimeoutOrNull() function indicates that if the work is completed within a specified time, the following result ("done") is returned, and if it is not done, null is returned.
5.Async and await
As mentioned earlier, a collaboration is blocked (suspended) even though it does not block the thread it is running on after thread cutting or after calling the suspend function, for example, two unrelated requests need to be made within a collaboration, but they are also one that finishes execution before the other:
GlobalScope.launch(Dispatchers.Main) { Log.v("Tag", "xxxx start") var resp1 = doRequest1() var resp2 = doRequest2() Log.v("Tag", "xxxx $resp1, $resp2") } suspend fun doRequest1(): String? { delay(2000) return "resp1" } suspend fun doRequest2(): String? { delay(2000) return "resp2" }
A request takes two seconds, so two requests here take four seconds. This is obviously not reasonable. Kotlin can use async to start an asynchronous protocol and get the results back through a pending function, await(), which allows multiple protocols to work in parallel:
GlobalScope.launch(Dispatchers.Main) { Log.v("Tag", "xxxx start") var defer1 = async { doRequest1() } var defer2 = async { doRequest2() } Log.v("Tag", "xxxx ${defer1.await()}, ${defer2.await()}") } suspend fun doRequest1(): String? { delay(2000) return "resp1" } suspend fun doRequest2(): String? { delay(2000) return "resp2" }
async() above starts a protocol and starts executing code. async returns a Deferred object, which is a subclass of Job.
6.Coprocess Extension
The declaration cycle of GlobalScope depends on the process before it exits at the end. Manual cancel() is required when the GlobalScope.launch protocol cannot be completed.For ease of use, android provides two extensions, lifecycleScope and viewModelScope, to automatically bind declaration cycles. LifecycleScope and viewModelScope are recommended for development, and only the following dependencies need to be introduced before use. LifecycleScope is used in Fragment and Active, and viewModelScope is used in ViewModel.
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'