What is a coroutine

The coroutine in Kotlin can be understood either as a lightweight thread or as a threading framework that allows developers to implement concurrent requirements in a non-concurrent coding manner. To simplify asynchronous programming, the asynchronous logic of the program can be expressed sequentially with the help of coroutines.

Coroutines and threads

  • Threads are scheduled by the system, and the overhead of thread switching or blocking is high. Coroutines depend on threads, but they can be suspended without blocking threads.
  • Thread execution and termination are scheduled by the operating system, but coroutines can manually control their execution and termination

How do I use coroutines

Add the dependent

Kotlin does not put coroutines into their standard library API, so if we want to use the coroutine functionality, we need to manually add the following dependencies. The second is required for Android development, and we will add them here as well.

implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.5.1." "
implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.5.1." "
Copy the code

Create coroutines

Coroutines can be created in the following ways, which are described below:

1. GlobalScope.launch

Each time you create a top-level coroutine, which ends when the application finishes running.

fun testGlobalScope (a) {
    GlobalScope.launch {
        println("hello in coroutines")}//Thread.sleep(1000)
}
Copy the code

The above code has no output because the print statement has no chance to run. If you sleep the current thread for 1 second, a printout will be displayed.

Take a look at the following code:

fun testGlobalScope (a) {
    GlobalScope.launch {
        println("hello in coroutines")
        The /** * delay() function is a non-blocking suspend function that suspends only the current coroutine */
        delay(1000)
        println("world in coroutines")}}Copy the code

Also, this part of the code will have no output, despite delay, because it is inside the coroutine. The actual running situation is that the coroutine never gets a chance to run.

2. runBlocking

The runBlocking function also creates a coroutine scope, but it guarantees that the current thread will block until all code and child coroutines in the coroutine scope have been executed. Note that the runBlocking function should normally only be used in a test environment, as using it in a formal environment can cause performance problems.

3. launch

Launch must run in a coroutine environment, which creates child coroutines under the scope of the current coroutine. The characteristic of subcoroutines is that if the coroutine in the outer scope terminates, all subcoroutines in that scope also terminate.

runBlocking {
    launch {
        println("hello")
        delay(1000)
        println("word")
    }
    launch {
        println("hello2")
        delay(1000)
        println("word2")}}Copy the code

4.sync

The async function must be called from the coroutine scope. It creates a new child coroutine and returns a Deferred object. If we want to get the execution result of the async function block, we simply call the await() method of the Deferred object.

Once async is called, the code in the block immediately starts executing. When an await() method is called, if the code in the block is not finished executing, the await() method blocks the current coroutine until the result of the async function can be obtained

runBlocking {
    var start = System.currentTimeMillis();
    var result1 = async {
        delay(1000)
        5 + 5
    }.await()
    var result2 = async {
        delay(1000)
        5 + 5
    }.await()
    println("result is ${result1 + result2}.")
    var end = System.currentTimeMillis();
    println("cost ${end - start} ms.")}Copy the code

5. coroutineScope

The coroutineScope function is also a suspend function, so it can be called in any other suspend function. It inherits the scope of an external coroutine and creates a child coroutine. With this feature, we can provide coroutine scope to any pending function.

suspend fun printHello2(a)= coroutineScope {
    launch {
        println("Hello")
        delay(1000)}}Copy the code

The coroutineScope function is also similar to the runBlocking function in that it guarantees that all code and child coroutines in its scope will be suspended until all code and child coroutines have been executed. But the coroutineScope function blocks only the current coroutine and does not affect other coroutines or any threads, so it does not cause any performance problems. As runBlocking suspends external threads, if you call it from the main thread, it may cause the interface to block, so it is not recommended to use it in real projects.

withContext

Threads can be cut with withContext as follows:

runBlocking {
    var result = withContext(Dispatchers.Default) {
        5 + 5
    }
    println(result)
}
Copy the code

WithContext must run in a coroutine scope or in a suspend function modified by suspend. In Android, the typical application scenario is to process time-consuming operations in child threads and then cut to the main thread to update the UI

withContext(Dispatchers.IO) { 
    // Switch the IO thread
}
/ /.. Execute in the UI thread
withContext(Dispatchers.IO) {
    // Switch the IO thread
   
}
/ /.. Execute in the UI thread
withContext(Dispatchers.IO) {
    // Switch the IO thread
}
/ /.. Execute in the UI thread
Copy the code

Hang up function

As the code logic in the coroutine scope becomes more complex, the code needs to be extracted into a separate function. A function declared by the suspend keyword is a suspend function, which indicates that the function body has coroutine scope. A method that is not decorated by suspend cannot participate in a coroutine task. A method decorated by suspend can only communicate with another method decorated by suspend in a coroutine.

  • Suspended functions can only be called inside a coroutine, not outside it
  • The essential difference between suspended functions and other functions is that suspended functions return asynchronously.

Termination of coroutines

If you want to terminate a coroutine after starting it, you can call the Cancel method.

var job = launch { for (i in 1.. 10) { println(i) delay(1000) } } job.cancel()Copy the code

When the launch function executes, it returns a Job object and can call the Cancel method if canceled. When async is used, we return a Deferred, which inherits from Job and can call Cancel directly.

var deferred = async {
    delay(1000)
    5 + 5
}
deferred.cancel()
//defered.await()
Copy the code

Calling the await method after calling the cancel method returns an error.

Coroutines and OKHttp

When we use coroutines and OKHttp for network communication, our code can also be very concise, as follows:

class WanAndroidFetcher {
    suspend fun getBanner(a): NetResult<List<Banner>> {
        val request = Request.Builder()
            .url("https://www.wanandroid.com/banner/json")
            .build()

        val response = OkHttpClient().newCall(request).await()

        // If the network request wasn't successful, throw an exception
        if(! response.isSuccessful)throw HttpException(response)

        returnwithContext(Dispatchers.IO) { response.body!! .use { body -> Gson().fromJson(body.string(),object: TypeToken<NetResult<List<Banner? >? >? >() {}.type) } } } }Copy the code

We define an okHttpExtensioins.kt file that extends the Call class by adding an await method with the following code:

suspend fun Call.await(a): Response = suspendCancellableCoroutine { continuation ->
    enqueue(
        object : Callback {
            override fun onResponse(call: Call, response: Response) {
                continuation.resume(response) {
                    // If we have a response but we're cancelled while resuming, we need to
                    // close() the unused response
                    if(response.body ! =null) {
                        response.closeQuietly()
                    }
                }
            }

            override fun onFailure(call: Call, e: IOException) {
                continuation.resumeWithException(e)
            }
        }
    )

    continuation.invokeOnCancellation {
        try {
            cancel()
        } catch (t: Throwable) {
            // Ignore cancel exception}}}Copy the code

When called:

private fun sendRequestWithOkHttp(a) {
    runBlocking {
        var data = WanAndroidFetcher().getBanner()
        println("data:  $data"); }}Copy the code

Coroutines and retrofit

The main thread cannot make network requests (coroutines are no exception)

We know that after Android 6.0, network requests are no longer allowed in the main thread. If you do, will encounter a android.os.Net workOnMainThreadException anomalies. We define the following interface:

interface NetApi {
    @GET("banner/json")
    suspend fun getBannerInfo(a):NetResult<List<Banner>>

    @GET("banner/json")
    fun getBannerNoSuspend(a): Call<NetResult<List<Banner>>>

    companion object {

        private const val BASE_URL = "https://www.wanandroid.com/"
        private var service: NetApi? = null

        fun getApi(a): NetApi {
            if (null == service) {
                val client = OkHttpClient.Builder().build()
                val retrofit = Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                service = retrofit.create(NetApi::class.java)
            }
            returnservice!! }}}Copy the code

We call it in the main thread

runBlocking { NetApi.getApi().getBannerNoSuspend().execute() }
Copy the code

Will find the program error, throws android.os.Net workOnMainThreadException, although in runBlocking created a collaborators cheng environment, but getBannerNoSuspend method of the return value is a Call object, Calling the execute method on this instance does not involve a thread switch, so it still runs on the main thread, and the result is still an error.

If we call in the main thread is

runBlocking {
    var data = NetApi.getApi().getBannerInfo()
    println("data: ---> " + data)}Copy the code

The network request was sent successfully and returned correctly because Retrofit switched threads for us at the bottom.

Can not locate call Adapter for class java.lang.Object

Coroutine + Retrofit + LiveData combination

The current Android development official advocates using MVVM architecture, here we use jetpack ViewModel to write a Demo, here we omit the Repository layer, directly in the ViewModel to initiate network requests. The code is as follows:

class ResourceViewModel: ViewModel() {
    private var bannerInfo = MutableLiveData<Result<NetResult<List<Banner>>>>()

    fun getBannerInfo( bannerType: Int) {
        viewModelScope.launch{
            var result = try {
                Result.success(NetApi.getApi().getBannerInfo())
            } catch (e: Exception) {
                e.printStackTrace()
                Result.failure(e)
            }
            bannerInfo.postValue(result)
        }
    }
}
Copy the code

To create a coroutine environment in a ViewModel, use viewModelScope.launch.

Network requests are processed using coroutine + RetroFIT

Because I want to understand the best practice of using coroutines to handle network requests, I have recently looked at many implementations of this area in open source projects. Generally speaking, they fall into the following categories:

Suspend function

Based on Retrofit 2.6

This is how we used the example above

@GET("banner/json")
suspend fun getBannerInfo(a):NetResult<List<Banner>>
Copy the code

In use, directly with the following code, very convenient

NetApi.getApi().getBannerInfo()
Copy the code

This approach requires our Retrofit version 2.6 and above

async + Deferred

Based on Retrofit version 2.4

@GET("banner/json")
fun getBanner(a): Deferred<NetResult<List<Banner>>>
Copy the code

When used (Retrofit helps us cut threads) :

var data = async {
    NetApi.getApi().getBannerAsync().await()
}
println("data: ---> " + data.await().data)
Copy the code

When creating a retrofit instance, you need to specify the CoroutineCallAdapterFactory this way, the only know, is now rarely used

val client = OkHttpClient.Builder().build()
val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .build()
service = retrofit.create(NetApi::class.java)
Copy the code

retrofit + flow

Flows in Kotlin can replace the functionality of RXJava, and flow is very simple to use. Our repository layer might be written like this (directly sending network requests).

class UserRepository(private val service: WanAndroidService) {

    suspend fun loginFlow(userName: String, password: String) = service.login(userName, password)
}
Copy the code

If we want to use the network request flow package, convenient later data processing, we can write like this

class UserRepository(private val service: WanAndroidService) {

    suspend fun loginFlow(userName: String, password: String) = flow {
        var data = service.login(userName, password)
        emit(data)}}Copy the code

Of course, if there is no complex data flow operation in the project, it is possible to use coroutine + LiveData directly to handle business requirements without introducing flow.

reference

  • Proandroiddev.com/kotlin-coro…
  • www.jianshu.com/p/76d2f47b9…
  • Github.com/guolindev/S…
  • Mp.weixin.qq.com/s?__biz=MzU…
  • Flow is officially recommended to replace LiveData, is it necessary juejin.cn/post/698626…
  • Juejin. Cn/post / 686035…