The purpose of this blog:

  1. You know what a Kotlin coroutine is, why do you use a Kotlin coroutine
  2. Get a quick start on Kotlin coroutines
  3. Get to the core and avoid being misled

What is a Kotlin coroutine

Kotlin’s coroutine is simply a framework for threading, and more specifically a set of higher-level tool apis that are implemented based on threads

The term coroutine was coined as early as 1958 and used to build assembler programs, showing that coroutines are a programming idea that is not limited to a particular language. For example, the Go language also has coroutines called Goroutines

So why do we use Kotlin coroutines?

We often write code for asynchronous operations, so we have to deal with communication and switching between threads. As you might expect, Android already has some great frameworks to do this for us, such as AsyncTask. But it has some disadvantages:

  • It has to deal with a lot of callbacks, and it’s easy to get stuck in “callback hell” if there’s too much business.
  • Forcibly split the business into the front, middle update, background three functions.

Callback hell refers to multiple callbacks nested together

When writing business code, there are several interfaces that you need to use. Interface A needs the callback result of interface B as A parameter to request data. This results in a nesting of callback functions. If you have three or four layers of unnesting, you end up with something like this:

asyncFunc1(opt, (... args1) { asyncFunc2(opt, (... args2) { asyncFunc3(opt, (... args3) { asyncFunc4(opt, (... args4) { // some operation }); }); }); });Copy the code

If you don’t feel nauseous, I think you probably write code like this so often that you get used to it. There’s a famous saying: you get used to it when you spit.

Rxjava provides an Observable programming paradigm for chained calls, which eliminates callbacks.

So what does this coroutine actually do? It certainly solves the above problems, so what are its advantages over RxJava?

I think the main thing is that it can write asynchronous code in a way that looks synchronous. It’s comfortable for the people who write the code and comfortable for the people who read the code.

Quick learning

The following author uses Retrofit with coroutine to achieve a login function

First, you need to add the following dependency libraries


implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.3.0"
implementation 'com. Squareup. Retrofit2: retrofit: 2.6.2'
implementation 'com. Squareup. Retrofit2: converter - gson: server'// Add implementation support for Retrofit to Deferred'com. Jakewharton. Retrofit: retrofit2 - kotlin coroutines -- adapter: 0.9.2'
Copy the code

Using the WanAndroid login API, type out the client login interface via the Retrofit framework

Interface API link: www.wanandroid.com/blog/show/2

interface ApiService {
    companion object {
        const val BASE_URL = "https://www.wanandroid.com"
    }

    @FormUrlEncoded
    @POST("/user/login")
    fun login(@Field("username") username: String,
              @Field("password") password: String): Deferred<WanResponse<User>>
}
Copy the code

What’s Deferred? It is a subinterface of Job. What is a Job? Understandably, the entire login request is encapsulated as a Job and handed over to the coroutine scheduler. But a Job does not return a value when it is completed, so we have Deferred, which means that the result is Deferred, and it provides a return value when the Job is completed.

Write the data class of the returned value based on the JSON returned after the request

data class WanResponse<out T>(val errorCode: Int,val errorMsg: String,val data: T)
Copy the code
data class User(val collectIds: List<Int>,val email: String,
                val icon: String,val id: Int,
                val password: String, val type: Int, val username: String)
Copy the code

Then build a Retrofit instance and use it to request logins

class ApiRepository { val retrofit = Retrofit.Builder() .baseUrl(ApiService.BASE_URL) AddConverterFactory (GsonConverterFactory. The create ()) / / add support for Deffered .addCallAdapterFactory(CoroutineCallAdapterFactory.invoke()) .build() .create(ApiService::class.java) fun login(name: String,password: String): Deferred<WanResponse<User>>{return retrofit.login(name,password)
    }
}
Copy the code

And now the main character coroutine. We can launch a coroutine using the launch function

GlobalScope.launch(Dispatchers.IO) { var result: WanResponse<User>? =null result = repository.login(userName,userPassword).await() launch(Dispatchers.Main) { btnLogin.text = result.data.username } }Copy the code

This code has the Dispatchers scheduler, which can restrict coroutine execution to a particular thread, or dispatch it to a pool of threads, or let it run unrestricted. I won’t expand Dispatchers here.

There are three kinds of Dispatchers commonly used:

  • Dispatchers.Main: The Main thread in Android
  • Dispatchers.IO: Optimized for disk and network IO for IO intensive tasks such as reading and writing files, manipulating databases, and network requests
  • Dispatchers.Default: Suitable for CPU-intensive tasks such as computing

But the chestnut above is just one Internet request, and if there are multiple requests it might look like this:

Globalscope.launch (Dispachers.IO) {// IO operation launch(Dispachers.Main){// UI operation launch(Dispachers Launch (dispacher.main) {// UI operation}}}}Copy the code

This nesting?? Coroutines don’t have to write nested code, right

So there’s a useful function in the coroutine: withContext. This function switches to the specified thread and automatically cuts the thread back to continue execution after the logic in the closure has finished.

Rewrite it withContext, which is roughly the length of this subchild:

launch(Dispachers.Main) { ... withContext(Dispachers.IO) { ... }... withContext(Dispachers.IO) { ... }... }Copy the code

For example, the login chestnut above can be rewritten like this:

GlobalScope.launch(Dispatchers.Main) { var result: WanResponse<User>? =null withContext(dispatchers.io){// request to login result = repository. Login (userName,userPassword).await()} // update UI btnLogin.text = result? .data? .username }Copy the code

It does seem to be getting a lot cleaner, but it falls a little short of our goal: writing asynchronous code in a way that looks synchronous.

Since there is no need for nesting, we can take the operation of the IO thread and separate it as a function. We can write it like this:

  suspend fun login(name: String,password: String): WanResponse<User> {
       return withContext(Dispatchers.IO) {
            val repository = ApiRepository()
            repository.login(name, password).await()
        }
    }
Copy the code

Suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend This will be explained in more detail below, but I’ll skip it here.

Now that the hang function is written, the code for opening coroutines can be rewritten

   GlobalScope.launch (Dispatchers.Main){
                val result =login(userName,userPassword)
                btnLogin.text = result.data.username
    }
Copy the code

This should look the same as the synchronized code

You could be more concise, right?

We set up Retrofit’s support for Kotlin coroutines by adding a third-party dependency library. It’s not really necessary, since the Retrofit library has built-in support for Kotlin Coroutines since version 2.6.0, which makes it easy to use Retrofit and Coroutines for web requests.

The above code can be rewritten as follows:

@FormUrlEncoded
@POST("/user/login")
suspend fun login(
@Field("username") username: String,@Field("password") password: String): WanResponse<User>
Copy the code

You can see that we just return our WanResponse, we don’t return Deferred<WanResponse>

suspend fun login(name: String,password: String): WanResponse<User>{
        return retrofit.login(name,password)
    }
Copy the code

We also don’t need to call the await method ourselves when we suspend the function request, because Retrofit is already doing it for us

suspend fun login(name: String, password: String): WanResponse<User> {
       return withContext(Dispatchers.IO) {
            val repository = ApiRepository()
            repository.login(name, password)
        }
    }
Copy the code
GlobalScope.launch (Dispatchers.Main){
    val result =login(userName,userPassword)
    btnLogin.text = result.data.username
}
Copy the code

Note: For the sake of easy understanding, none of the above sample code handles exceptions, so I’ll leave this blog as it’s not the focus of this article, and more on exception handling can be written in a separate article.

The role of the supspend keyword

I mentioned suspend in the suspend function, but what does it do? Does the hang work?

If the hang is in effect, what object is it hanging on? Is it the current thread or the function?

The answer is neither, a suspend in a coroutine, essentially the suspended object is a coroutine. What is a coroutine? That’s the block of code wrapped around the launch function.

Globalscope.launch (Dispatchers.Main){// Login is asuspendFunction val result = login(userName,userPassword) btnlogin.text = result.data.username} //Next.....Copy the code
  suspend fun login(name: String,password: String): WanResponse<User> {
       return withContext(Dispatchers.IO) {
            val repository = ApiRepository()
            repository.login(name, password).await()
        }
    }
Copy the code

When the suspend function is executed, the coroutine is suspended in the current thread.

So what’s the current thread doing? It does what it’s supposed to do. As in the previous example, since the coroutine is in the main thread, the coroutine is suspended when the login request is made, and the main thread breaks away from the coroutine and continues after NEXT. When the login request is successful, the suspend function switches it back to the main thread.

Knock on the blackboard, knock on the blackboard

What a suspend does is it automatically pulls the thread back later, and that’s called a resume, and it’s a coroutine function, so inside the coroutine (which can also be a suspend function), we call our own suspend function, such as the usual withContext().

At this point you might think that the suspend keyword function is the suspend coroutine function, but that’s overestimating it. It doesn’t have that magic function.

Suspend It is essentially just a reminder, so who’s reminding whom?

It’s a reminder from the function creator to the function caller, telling the function caller I’m a suspended function, I’m a time-consuming function, please call me inside the coroutine. Ostensibly it’s a request, but actually it’s a reminder.

Supend doesn’t do the suspend function, what does suspend is the suspend function inside this function, like the built-in suspend function that we’re using here, withContext.

So it’s important to note that if we didn’t use the suspended function in the supsend function, the suspended function would be meaningless. Because once you use a suspended keyword, it means that it can only be called in a coroutine. It’s easy to understand: you don’t need to suspend, and you need to add suspend so that the caller can only be called inside the coroutine, which is like holding the manger.

To summarize: The supsend keyword exists as a reminder, to some extent it can limit the caller from doing time-consuming operations on the main thread.

If you create a suspend function but it contains no real suspend logic inside, the compiler will give you a reminder: Redundant Suspend modifier, telling you that the suspend function is redundant.

To avoid misunderstanding

1. Coroutine suspend is non-blocking while thread is blocking?

First of all, what is non-blocking?

Simply put, non-blocking means not blocking the current thread. Coroutines are indeed non-blocking in this way. If you encounter a suspended function on the main thread and get switched to another thread to do the operation, then your main thread will not get stuck.

So the question is, is it blocking to use Thread to switch threads?

This statement is only true in the case of a single thread. A single thread’s time-consuming operations are bound to stall the thread, so it is blocked. In multithreaded, multithreaded switching threads will not be stuck, so it must be non-blocking.

Is the suspension of a single coroutine blocking?

It is also non-blocking, because it can use suspend functions to cut threads.

So, coroutine suspensions are no different than thread switches; they are both non-blocking. Coroutine hangings are no more advanced than thread switching. Kotlin’s hangings are merely thread cuttings, which are essentially the same as Java’s thread cuttings, except that they are “written blocking, but not blocking”.

2. Is this non-blocking Kotlin coroutine more efficient than threading?

Kotlin coroutines are not more advanced than threads, nor are coroutines more efficient than threads.

summary

Coroutines are a set of thread encapsulation apis provided by Kotlin, but that doesn’t mean coroutines are made for threads. Coroutines were designed to solve the concurrency problem and make collaborative multitasking easier to implement. However, Kotlin coroutine can be started from thread control, which is the content introduced in this article. As for more advanced application and implementation principle, I will write a blog to share if it is necessary to learn further

If you find my writing difficult to understand, I suggest you check out the Kotlin coroutine tri-link by the throwing line master, which will surely make you feel enlightened.

Kotlin’s coroutine takes a hard glance

The content of this blog can be regarded as a study note after reading the coroutine three consecutive ~