This is the 16th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

preface

The basics of Kotlin have been covered in previous articles. Starting with this article, we will begin to explain Kotlin’s coroutines in detail!

No more words, just get started!

1. Basic use of Kotlin

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val nameTextView = findViewById<TextView>(R.id.nameTextView)
        nameTextView.text = "Jack"
        val submitButton = findViewById<Button>(R.id.submitButton)
// submitButton.setOnClickListener(View.OnClickListener {
// Log.d("hqk","onClick")
/ /})
        // The above one is a bit Java, you can set the click event in the following way
        submitButton.setOnClickListener{
            Log.d("hqk"."onClick")}}}Copy the code

This is a very simple MainActivity that contains the corresponding controls to get and the corresponding events to add.

At this point, readers of the first installment of this column will have the ability to transfer their Java projects to Kotlin projects.

Of course, that’s not all Kotlin has. The best thing about Kotlin is coroutines! And a tough nut to crack! But incense!

2. Preliminary study on Kotlin coroutines

What is a coroutine?

Official description: A coroutine is actually a lightweight thread that can be suspended and resumed later. Coroutines are supported by suspending functions: a call to such a function might suspend the coroutine and start a new coroutine, and we typically use anonymous suspending functions (that is, suspending lambda expressions).

Coroutines simplify asynchronous programming by putting complexity into libraries. The program logic can be expressed sequentially in coroutines, and the underlying library takes care of the asynchrony for us. The library can wrap the relevant parts of user code as callbacks, subscribing to related events, and scheduling execution on different threads (or even different machines), while keeping the code as simple as sequential execution.

The preparatory work

    implementation 'org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.4.2'
Copy the code

Need to inject the corresponding dependency in the corresponding Module!

2.1 the sample a

fun main(a){
    // A new coroutine running in the background
    GlobalScope.launch {
        delay(1000)
        println("Kotlin!")
    }
    println("Hello,")
    Block the main thread for 2 seconds to keep the JVM alive while the coroutine is waiting
    Thread.sleep(2000)}Copy the code

Running effect

Hello, // Here is a pause of one second before printing the next sentence Kotlin! Kotlin!Copy the code

Here we see a coroutine created by globalScope.launch {}, suspended by delay for 1 second, and then printed!

A coroutine is actually a lightweight thread, so it’s still a thread. How about using Java threads?

import kotlinx.coroutines.*

fun main(a){
    thread{
        Thread.sleep(1000)
        println("Kotlin!")
    }
    println("Hello,")
    Block the main thread for 2 seconds to keep the JVM alive while the coroutine is waiting
    Thread.sleep(2000)}Copy the code

Running effect

Hello, // Here is a pause of one second before printing the next sentence Kotlin! Kotlin!Copy the code

Sleep (1000) blocks for 1 second, then prints again, and finally we see the same result!

At first glance it looks the same!

What is the difference between Thread.sleep(1000) and Delay (1000)?

Notice that I used different words to explain what these two methods do:

  • delay(1000)To suspend, it does not block the thread, but suspends the coroutine and can only be used within the coroutine.
  • Thread.sleep(1000)Is blocked, which causes the thread to block!

So what’s the difference between a suspend and a block? (the point! The point! The point!)

  • Suspension: Usually active, issued by a system or program, or even in storage. (Do not release CPU, may release memory, put in external storage)
  • Blocking: It is usually passive, unable to get a resource while grabbing it, and passively hangs in memory, waiting for some resource or semaphore to wake it up. (Release CPU, not memory)

To explain in polite terms:

  • Hang up: the car unloaded the goods, but the car is still running, the car may be dragging other goods
  • Jam: the goods are not unloaded, the car stopped, waiting for the traffic light, the green light to go

However, the first example mixes non-blocking delay(…) in the same code. With blocked thread.sleep (…) . It’s easy to remember which is blocking and which is non-blocking. Then try blocking with the runBlocking coroutine builder:

2.2 example 2

import kotlinx.coroutines.*

fun main(a){
    GlobalScope.launch {
        delay(1000)
        println("Kotlin!")
    }
    println("Hello,")
    // This expression blocks the main thread and we delay it by 2 seconds to keep the JVM alive
    runBlocking {
        delay(2000L)}}Copy the code

Running effect

Hello, // Here is a pause of one second before printing the next sentence Kotlin! Kotlin!Copy the code

The result is similar, with the main thread calling runBlocking{} blocking until the coroutine inside runBlocking completes.

This example is not obvious, just for transition purposes! Let’s look at the next example:

2.3 the sample 3

The main thread that calls runBlocking{} will block until the coroutine inside runBlocking completes.

So try wrapping the corresponding coroutine inside runBlocking {}? :

fun main(a) = runBlocking<Unit> { // Start executing the main coroutine
    GlobalScope.launch {
        delay(1000L)
        println("Kotlin!")
    }
    println("Hello,")
    delay(2000L)}Copy the code

Running effect

Hello, // Here is a pause of one second before printing the next sentence Kotlin! Kotlin!Copy the code

Here runBlocking

{… } as the adapter used to start the top-level main coroutine. We explicitly specify its return type Unit because in Kotlin main must return Unit. The < Unit > can be omitted

Of course, this will cause a waste of time, why to suspend 2 seconds, can you remove delay(2000L)! Let the coroutine finish automatically

2.4 sample four

fun main(a) = runBlocking<Unit> { // Start executing the main coroutine
    val job:Job = GlobalScope.launch {
        delay(1000L)
        println("Kotlin!")
    }
    println("Hello,")
    job.join() // Wait until the subcoroutine execution is complete
    //....
}
Copy the code

Running effect

Hello, // Here is a pause of one second before printing the next sentence Kotlin! Kotlin!Copy the code

Here’s an additional variable: job:Job receives the corresponding coroutine and waits through job.join() until the subcoroutine finishes!

But there are drawbacks:

When globalScope.launch is used, a top-level coroutine is created. Although it is lightweight, it still consumes some memory resources when it runs. If you forget to keep a reference to a newly started coroutine, it will continue to run. What if the code in the coroutine hangs (mistakenly delayed too long, for example), or starts too many coroutines and runs out of memory? Manually keeping references to all started coroutines via job.join() is also error-prone.

So, moving on to the next example:

2.5 the sample 5

In the example, the runBlocking coroutine builder is used to convert the main function into a coroutine. Each coroutine builder, including runBlocking, adds an instance of CoroutineScope to the scope in which its code block resides. You can start coroutines in this scope without explicitly calling the join function, because the external coroutine (runBlocking in the example) does not end until all coroutines started in its scope have executed. Therefore, the example can be simplified to:

fun main(a) = runBlocking<Unit> {// this : CoroutineScope
    launch {
        delay(1000L)
        println("Kotlin!")
    }
    println("Hello,")}Copy the code

Running effect

Hello, // Here is a pause of one second before printing the next sentence Kotlin! Kotlin!Copy the code

In addition to having coroutine scopes provided by different builders, you can declare your own scopes using the coroutineScope builder.

  • RunBlocking and coroutineScope may look similar because they both wait for their coroutine body and all its child coroutines to end.

  • The main difference is that the runBlocking method blocks the current thread to wait, while the coroutineScope simply hangs, freeing the underlying thread for other purposes.

  • Because of this difference, runBlocking is a normal function and coroutineScope is a suspend function.

Let’s look at the next example with analysis:

2.6 the sample 6

fun main(a) = runBlocking<Unit> {
    launch {
        delay(200L)
        println("Hello Kotlin")}// Create a coroutine scope
    coroutineScope {
        launch { / / embedded launch
            delay(500L)
            println("Embedded launch")
        }
        delay(100L)
        // This line will be printed before the embedded launch
        println("Custom scope")}// This line will be output only after the embedded launch has executed
    println("runBlocking over")}Copy the code

Running effect

Custom scope Hello Kotlin with launch runBlocking over embeddedCopy the code

Notice that “Hello Kotlin” is executed and printed immediately after the “Custom scope” message while waiting for the embedded launch — even though the coroutineScope is not finished yet.

conclusion

Well, that’s almost the end of this piece. I believe you have a basic understanding of coroutines. In the next article, we’ll look at the cancel timeout composite suspend functions for coroutines.

Any wrong place and suggestion, welcome to correct.