Getting Started with Android Kotlin Coroutine
Article directory
- Preface
- 1. Coroutine Basics
-
- The first coroutine
-
- Structured Concurrency
- Suspend function suspend
- Scope builder
- Scope builder and concurrency
- job
- Lightweight
- Second, Kotlin coroutine on Android
-
- Features
- Example of Kotlin coroutine + Retrofit on Android for network request
-
- li>
-
- Some code preparation
- Kotlin is simple to use Retrofit
- Kotlin android is simple to use coroutines
- Kotlin android is simple to use Coroutine + retrofit
- Reference
Foreword
ps: The article will be very long
Coroutines are really easy to use, so let’s learn! This article will step by step by interpreting kotlin official documents and android development documentsTo know Kotlin coroutines, those who do not have a Kotlin foundation are advised to learn Kotlin first.
1. Coroutine basics
This section introduces the basic concepts of coroutines. The following are official kotlin examples, very classic.
Dependencies and Permissions
//Dependency implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") //retrofit api 'com.squareup.retrofit2:retrofit:2.6.0' api 'com.squareup.retrofit2:converter-gson:2.6.0' api 'com.squareup.retrofit2:adapter-rxjava2:2.6.0' api 'com.trello.rxlifecycle2:rxlifecycle-components:2.6.1' api 'com.facebook.stetho:stetho:1.5.0' api 'com.facebook.stetho:stetho-okhttp3:1.5.0' //permission
The first coroutine
A coroutine is an instance of suspendable computation. It’s conceptually similar to a thread in that it requires a block of code to run that does work concurrently with the rest of the code. However, coroutines are not bound to any particular thread. It can suspend execution in one thread and resume in another.
Coroutines can be thought of as lightweight threads ,But there are a number of important differences that make their real-life use very different from threads.
This code example below will be our first coroutineimport kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope // launch a new coroutine and continue start a new coroutine and connect launch { // non-blocking delay for 1 second (default time unit is ms) delay for one second delay(1000L) // print after delay print after delay println("World!") } // "Hello" in the main thread continues to execute, while "World!" is printed after a delay println("Hello") }
Unsurprisingly, we get this result:
Hello
World!Judging from this result, this is indeed very similar to a thread, isn’t it? Let’s analyze the code above
launch
is a coroutine builder. It starts a new coroutine at the same time as the rest of the code, which continues to work independently. That’s why Hello is printed first.
delay
is a special pause function. It suspends the coroutine at a specific time. Suspends a coroutine without blocking the underlying thread, allowing other coroutines to run and use the underlying thread for their code. It can be mutually verified with sleep() in the thread for memory (not to say that it can be equivalent)
runBlocking
is also a coroutine builder that connects the regular fun main()’s non-coroutine with the coroutine code inside runBlocking{…}. In the IDE, there is a hint after the curly braces at the beginning of runBlocking: CoroutineScope.Note: If you delete or forget runBlocking in this code, you will get the following error in the start call, because start is only declared in CoroutineScope:
Unresolved reference: launch
The name of runBlocking means that the thread running it (in this case the main thread) is blocked during the call, until all coroutines in runBlocking{…} are blocked to complete their code logic. You’ll often see runBlocking used like this in top-level applications, but rarely in real code because threads are expensive resources and blocking them is inefficient and often unnecessary.
Structured Concurrency
Coroutines follow the principle of structured concurrency, which means that new coroutines can only be started within a specific coroutine scope that limits the lifetime of the coroutine. The above example shows that runBlocking establishes the corresponding scope, which is why the previous example waits until World! Print after a one-second delay, then exit.
In a real application, many coroutines will be launched. Structured concurrency ensures that they are not lost, nor leaked. An outer scope cannot complete until all of its child coroutines have completed. Structured concurrency also ensures that any bugs in your code are properly reported and never lost.
Suspend function suspend
Appeal is just a basic coroutine, we can extract the code block in launch{…} into a separate function. When you perform an “extract function” refactoring on this code (that’s alt+enter), you will get a new function (suspend function) with the suspend modifier. This is your first pause function. Suspend functions can be used in coroutines like regular functions, or they can be used in turn to suspend the execution of coroutines using other suspend functions (such as defer in this example).
import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { doWorld() } println("Hello") } // this is your first suspending function // first suspend function suspend fun doWorld() { delay(1000L) println("World!") }
The same results are as follows
Hello
World!Scope builder Scope builder
In addition to the coroutine scopes provided by the different builders, it is also possible to declare your own scopes using the coroutine scope builder. It creates a coroutine scope that won’t complete until all launched children have completed.
The builders for runBlocking and coroutineScope may look similar, since they both wait for the completion of their own body and all children. The main difference is that the runBlocking method blocks the current thread to wait, while coroutineScope just suspends and releases the underlying thread for other use. Because of this difference, runBlocking is a regular function, while coroutineScope is a suspending function.
We can use coroutineScope from any suspending function. For example, the concurrent printing of Hello and World can be moved to suspend fun doWorIn the ld() function:
import kotlinx.coroutines.* fun main() = runBlocking { doWorld() } suspend fun doWorld() = coroutineScope { // this: CoroutineScope launch { delay(1000L) println("World!") } println("Hello") }
Same result:
Hello
World!Scope builder and concurrency
Scope constructors can be used in any suspend function to perform multiple concurrent operations. Let’s start two concurrent coroutines in the doWorld suspending function:
import kotlinx.coroutines.* // Sequentially executes doWorld followed by "Done" //sequential execution fun main() = runBlocking { doWorld() println("Done") } // Concurrently executes both sections // Execute both parts at the same time suspend fun doWorld() = coroutineScope { // this: CoroutineScope launch { delay(2000L) println("World 2")} launch { delay(1000L) println("World 1") } println("Hello") }
The two code blocks in launch {…} are executed at the same time, printing World 1 first after one second from the start, and printing World 2 after two seconds from the start. A coroutineScope in doWorld can’t complete until both are complete, so doWorld returns and allows to print the Done string only when completed:
Hello
World 1
World 2
Donejob
The launch coroutine constructor returns a job object, which is a handle to the launch coroutine, which can be used to explicitly wait for it to complete. For example, you could wait for the child coroutine to complete, then print the “Done” string:
import kotlinx.coroutines.* fun main() = runBlocking { val job = launch { // launch a new coroutine and keep a reference to its Job // start a new coroutine and keep a reference to its work delay(1000L) println("World!") } println("Hello") // wait until child coroutine completes //Wait for the child coroutine to complete job. join() println("Done") }
The results are as follows:
Hello
World!
DoneLightweight
We have the following sample code:
import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { repeat(100_000) { // launch a lot of coroutines launch { delay(5000L) print(".") } } } //sampleEnd
It starts 100,000 coroutines, and after 5 seconds, each coroutine prints a dot.
However, if we try it with threads (delete runBlocking, replace launch with thread, replace delay with thread.sleep). what happens? (The code will most likely generate some kind of out of memory error)2. Kotlin coroutines on Android
The Android development documentation introduces coroutines as follows:
Coroutines are a concurrency design pattern that you can use on the Android platform to simplify asynchronously executed code. Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages.On Android, coroutines help manage long-running tasks that, if not managed properly, can block the main thread and render your app unresponsive. More than 50% of professional developers who use coroutines report increased productivity using coroutines. This topic describes how to use Kotlin coroutines to solve the following problems, allowing you to write cleaner and more concise application code.
Features
Coroutines are our recommended solution for asynchronous programming on Android. Noteworthy features include:
- Lightweight: You can run multiple coroutines on a single thread, since coroutines support suspension without blocking the thread that is running the coroutine. Suspending saves memory compared to blocking and supports multiple parallel operations.
- Fewer memory leaks: Use structured concurrency to perform multiple operations in one scope.
- Built-in cancellation support: Cancellation is automatically propagated throughout the running coroutine hierarchy.
- Jetpack integration: Many Jetpack libraries include extensions that provide full coroutine support. Some libraries also provide their own coroutine scopes that you can use for structured concurrency.
Example of Kotlin coroutine + Retrofit for network request on Android
For the convenience of comparison, we first use a separate Retrofit. If you are not familiar with Retrofit, you can refer to this article of the author
For the java version, the Retrofit version that supports coroutines must be at least greater than 2.6. This article uses the 2.6 version of Retrofit
Android retrofit from ignorance to entry (the latest and most complete)Some code preparation
API
import retrofit2.Call import retrofit2.Response import retrofit2.http.GET interface ISystemRemoteService { @get:GET("/system/alive") val systemStatus: Call> }
RetrofitService
import android.util.Log import retrofit2.Retrofit import okhttp3.OkHttpClient import com.zyf.kotlindemo.HttpsUtils import kotlin.Throws import com.zyf.kotlindemo.RetrofitService import java.util.concurrent.TimeUnit class RetrofitService private constructor() { private val retrofit: Retrofit fun create(clazz: Class?): T { return retrofit. create(clazz) } companion object { private val client = OkHttpClient. Builder() .connectTimeout(30, TimeUnit. SECONDS) .readTimeout(30, TimeUnit. SECONDS) .sslSocketFactory(HttpsUtils.getSslSocketFactory(null, null, null)) .hostnameVerifier { hostname, session -> true } .writeTimeout(30, TimeUnit.SECONDS).addInterceptor { chain -> val builder = chain.request() .newBuilder() val request = builder. build() chain.proceed(request) }.build() private var i: RetrofitService? = null fun i(): RetrofitService? { if (i == null) { i = RetrofitService() } return i } fun rebuild() { i = RetrofitService() } } init { val serverUrl = "https://blog.csdn.net/shop_and_sleep" retrofit = Retrofit. Builder() .baseUrl(serverUrl) //Set timeout, intercept .client(client) .build() Log.i("ServerUrl:------", serverUrl) } }
Kotlin simply uses Retrofit
Only use Retrofit without coroutines
val api = RetrofitService.i()?.create(ISystemRemoteService::class.java) val call= api?.systemStatus //Here because I am in the main thread, so it can only be asynchronous val result = call?.enqueue(object : Callback<Response>{ override fun onResponse(call: Call<Response>, response: Response<Response>) { // successful callback } override fun onFailure(call: Call<Response>, t: Throwable) { // failure callback } })
Kotlin android simple use of coroutines
In the first coroutine, we know that each coroutine must have a scope constructor, so the first coroutine in Android can become
CoroutineScope(Dispatchers.Main).launch { // non-blocking delay for 1 second (default time unit is ms) delay for one second delay(1000L) // print after delay print after delay println("World!") } println("Hello")
Dispatchers.Main
means to start this coroutine in the main thread, Dispatchers is a scheduler. In addition to Main, we can also see others in its source code, which is explained in the following table:group function Default The default scheduler (this is the default if not written) Main The scheduler of the main thread (this is the default if not written ) Unconfined A coroutine scheduler that is not confined to any particular thread IO As the name suggests, a scheduler designed to offload blocking IO tasks to a shared thread pool) Kotlin android simply uses coroutines + retrofit
After understanding the scheduler Dispatchers, the coroutine constructor, and the return job of the coroutine constructor, a simple network request appears naturally:
CoroutineScope(Dispatchers.Main).launch { /*Because the first coroutine is still the main thread, Android stipulates that network requests must be in sub-threads, So here we get the request result through withContext, Switch to the IO thread through the scheduler Dispatcher, This operation will suspend the current coroutine, but will not block the current thread*/ val result = withContext(Dispatchers.IO) { /*This is already in the child thread, So use execute() to synchronously request withContext will return the return value of the last line of code in the closureSo the above result is the Response type */ RetrofitService.i()?.create(ISystemRemoteService::class.java)?. systemStatus?. execute()?. body() } Log.d("onCreate", "result: "+result. toString()) if (result?.isSuccessful == true){ Toast.makeText(mContext, "The request was successful", Toast.LENGTH_SHORT).show() } }
Where withContext invokes the specified suspending block with the given coroutine context, suspends until complete, and then returns, except for launch
, withContext, and async , due to space reasons More details, if you are interested, you can understand by yourself;It is worth mentioning that if the call to ***withContext*** is canceled when its scheduler starts executing code, it will discard the result of ‘withContext’ and throw a [CancellationException].
In order to solve this problem, we have to mention the built-in cancellation support of coroutines, so our complete code becomes as follows:import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.util.Log import android.view.Gravity import android.widget.Button import android.widget.Toast import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.* import retrofit2. Call import retrofit2. Callback import retrofit2.Response import kotlin.coroutines.CoroutineContext class MainActivity : AppCompatActivity() , CoroutineScope { private val mContext by lazy { this } //(The delegate is used here, which means that the code will be executed only when mContext is used) //job is used to control the coroutine, the coroutine started by launch{} later, the returned job is the job object private lateinit var job: Job // Inheriting CoroutineScope must initialize the coroutineContext variable // This is a standard way of writing, + is actually the front of the plus method, which means job, which is used to control the coroutine, followed by Dispatchers, which specifies the thread to start override val coroutineContext: CoroutineContext get() = job + Dispatchers. Main override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job()setContentView(R. layout. activity_main) CoroutineScope(Dispatchers.Main).launch { RetrofitService.i()?.create(ISystemRemoteService::class.java)?. systemStatus?. execute()?. body() } Log.d("onCreate", "result: "+result. toString()) if (result?.isSuccessful == true){ Toast.makeText(mContext, "The request was successful", Toast.LENGTH_SHORT).show() } } } override fun onDestroy() { //When the acitivity ends, we don't need to request the network anymore, end the current coroutine job. cancel() super. onDestroy() } }
This is the end of this simple example, but the actual project generally uses network requests in the viewmodel instead of in the activity, which has to mention another advantage of the coroutine Jetpack integration, if it is in the viewmodel, You can use viewModelScope to bind the life cycle of viewModel, otherwise you have to manually call the job.cancel() method as in the example
Reference
kotlin official document
Android official document
Coroutine cooperates with retrofit to call network request
Kotlin coroutine (Coroutines) and Retrofit for network requests (1)