What is the Kotlin coroutine?

A coroutine is a concurrent design pattern and a Kotlin coroutine is a threading framework.

Why do WE need a Kotlin coroutine?

Provide convenient thread operation API, write logical and concise thread code.

Coroutines are Google’s recommended solution for asynchronous programming on Android. It has the following characteristics:

  • Lightweight: You can run multiple coroutines on a single thread because coroutines support suspension and do not block the thread running them. Suspension saves memory than blocking and supports multiple parallel operations.
  • Fewer memory leaks: Multiple operations are performed within a scope using structured concurrency mechanisms.
  • Built-in cancel support: Cancel operations are 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 scope that you can use to structure concurrency.

How do I use the Kotlin coroutine

Add the dependent

dependencies {
    implementation 'org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.3.9'
}
Copy the code

Usage scenarios

Start the coroutines

If we need to perform a simple background (or foreground) task, we can quickly launch a coroutine to handle the business logic via GlobalScope.launch. We can also specify the thread type via Dispatchs: Dispatchers.Default, dispatchers. IO, dispatchers. Main, dispatchers. Unconfined.

GlobalScope.launch(Dispatchers.IO) {
    delay(1000)
    Log.d(TAG, "processIO in ${Thread.currentThread().name}")}Copy the code

Switch threads

Main thread => IO thread => main thread. This scenario is most commonly used in development, such as the background to get a photo, and then the foreground display.

// start a coroutine in the main thread
GlobalScope.launch(Dispatchers.Main) {
    // Switch to the IO thread
    withContext(Dispatchers.IO) {
        delay(1000)
        Log.d(TAG, "processIO in ${Thread.currentThread().name}")}// Automatically cut back to the main thread
    Log.d(TAG, "processUI in ${Thread.currentThread().name}")}Copy the code

Running results:

The 2021-01-02 18:38:23. 812, 15506-15535 / tech. Kicky. The coroutine D/coroutine Sample: processIOinDefaultDispatcher - 2021-01-02 18:38:23 worker - 1. 813, 15506-15506 / tech. Kicky. The coroutine D/coroutine Sample: processUIin main
Copy the code

In disappear assist cheng

private fun cancelCoroutine(a) {
    val job = GlobalScope.launch(Dispatchers.IO) {
        for (i in 0.10000.) {
            delay(1)
            Log.d(TAG, "count = $i")
        }
    }
    Thread.sleep(30)
    job.cancel()
    Log.d(TAG, "Coroutine Cancel")}Copy the code

The result is as follows:

The 2021-01-02 18:53:37. 680, 23240-23279 / tech. Kicky. The coroutine D/coroutine Sample: Count = 0 2021-01-02 18:53:37. 682, 23240-23278 / tech. Kicky. The coroutine D/coroutine Sample: Count = 1 2021-01-02 18:53:37. 685, 23240-23280 / tech. Kicky. The coroutine D/coroutine Sample: Count = 2 2021-01-02 18:53:37. 687, 23240-23280 / tech. Kicky. The coroutine D/coroutine Sample: Count = 3 2021-01-02 18:53:37. 689, 23240-23280 / tech. Kicky. The coroutine D/coroutine Sample: Count = 4 18:53:37. 2021-01-02, 690, 23240-23280 / tech. Kicky. The coroutine D/coroutine Sample: Count = 5 18:53:37. 2021-01-02, 693, 23240-23280 / tech. Kicky. The coroutine D/coroutine Sample: Count = 6 2021-01-02 18:53:37. 696, 23240-23240 / tech. Kicky. The coroutine D/coroutine Sample: coroutine CancelCopy the code

LifecycleOwner works with it

The Kotlin coroutine is mainly used to process thread operations. If the coroutine is not properly processed, memory leaks may occur. For example, activities or fragments are destroyed but the coroutine in the interface is still being executed. Therefore, we must cancel the coroutine operation in the interface when we destroy it. We can call the cancel() method ourselves at interface destruction, but it’s easy to ignore without good programming habits. We recommend using the extension method provided by Google.

implementation "Androidx. Activity: activity - KTX: 1.1.0." "
implementation "Androidx. Fragments: fragments - KTX: 1.2.5." "
Copy the code
private fun lifecycleCoroutine(a) {
    // start a coroutine in the main thread
    lifecycleScope.launch {
        // Switch to the IO thread
        withContext(Dispatchers.IO) {
            delay(1000)
            Log.d(TAG, "processIO in ${Thread.currentThread().name}")}// Automatically cut back to the main thread
        Log.d(TAG, "processUI in ${Thread.currentThread().name}")}}Copy the code

Note:

LifecycleScope. Launch () defaults to start the coroutine on the main thread;

2. Coroutines in lifecycleScope will be cancelled automatically when Lifecycle is in a destroyed state.

3. LifecycleScope has several other extensions, such as launchWhenCreated, launchWhenStarted, and launchWhenResumed

ViewModel is used together

Coroutines work with LifecycleOwner to solve the problem of handling coroutines during interface lifecycle changes. But for scenarios where the screen is rotated and the interface is reconstructed, the ViewModel object lasts longer than the LifecycleOwner. Although the interface needs to be rebuilt, the coroutine does not have to be cancelled, and this needs to be considered for specific needs.

fun viewModelCoroutine(a) {
    viewModelScope.launch {
        Log.d("Coroutine Sample", Thread.currentThread().name)
    }
}
Copy the code

Note:

Viewmodelscope.launch () also starts the coroutine on the main thread by default;

2. Coroutines in viewModelScope are automatically cleared when the ViewModel is about to be onCleared.

Retrofit really smells good

To get to the point, versions after Retrofit 2.6 support using Kotlin’s coroutines. So, how to support?

  • Add the dependent
implementation "Com. Squareup. Retrofit2: retrofit: 2.9.0"
implementation "Com. Squareup. Retrofit2: converter - gson: 2.9.0"
Copy the code
  • Adding Network Rights
<uses-permission android:name="android.permission.INTERNET" />
Copy the code
  • Retrofit
object Retrofitance {
    private val client: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .build()
    }

    private val retrofitance: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com")
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build()
    }

    val wanAndroidApi: WanAndroidApi by lazy {
        retrofitance.create(WanAndroidApi::class.java)
    }
}
Copy the code
  • API
interface WanAndroidApi {

    @GET("/banner/json")
    suspend fun banners(a): WanAndroidRoot<Banner>
}
Copy the code

Focus on the suspend keyword in the API. Suspend means suspend, reminding developers that this method is a time-consuming method.

  • Executing network requests
class MainViewModel : ViewModel() {

    val banners = MutableLiveData<List<Banner>>()

    fun viewModelCoroutine(a) {
        viewModelScope.launch(Dispatchers.IO) {
            val result = Retrofitance.wanAndroidApi.banners()
            banners.postValue(result.data)}}}Copy the code
private val viewModel by viewModels<MainViewModel>()

override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)
    viewModel.banners.observe(this, {
        val content: List<String> = it.map { banner ->
            banner.title
        }
        binding.text.text = content.toTypedArray().contentToString()
    })
    viewModel.viewModelCoroutine()
}
Copy the code

Retrofit requests dependencies

For network requests with dependencies, we need to deal with them in callback before using coroutines. One layer and two layers are ok, but it is easy to be messy if there are more layers. Use coroutines, write code sequentially, concise and clear.

fun viewModelSequenceRequest(a) {
    viewModelScope.launch(Dispatchers.IO) {
        val start = System.currentTimeMillis()
        // Make a banner request first
        val result = Retrofitance.wanAndroidApi.banners()
        banners.postValue(result.data)
        // Request hotkeys again, as long as they are executed sequentially and the result of the previous request has been obtained, it can satisfy our usage scenario.
        val keys = Retrofitance.wanAndroidApi.hotKeys()
        hotKeys.postValue(keys.data)
        Log.d("Coroutine Sample", (System.currentTimeMillis() - start).toString())
    }
}
Copy the code

Retrofit concurrent results merge

For multiple concurrent executions, the results are processed uniformly, and then something else is executed. Before using coroutines, we can use RxJava’s ZIP operator. Coroutines async/await are easily competent.

fun viewModelAsync(a) {
    viewModelScope.launch(Dispatchers.IO) {
        val start = System.currentTimeMillis()
        val result = async { Retrofitance.wanAndroidApi.banners() }
        val keys = async { Retrofitance.wanAndroidApi.hotKeys() }
        Log.d(
                "Coroutine Sample",
                (result.await().data.size + keys.await().data.size).toString()
            )
        Log.d("Coroutine Sample", (System.currentTimeMillis() - start).toString())
    }
}
Copy the code

The code is the same as in the previous example, but the execution time is much shorter because it is executed concurrently rather than sequentially.

conclusion

In my opinion, the basic use of Kotlin coroutines focuses on the following three aspects:

  • thread
  • How to avoid memory leaks (in conjunction with LifecycleOwner, ViewModel, etc.)
  • Collocation to Retrofit

Of course, there are many more operators and methods in the Kotlin Coroutine library that need to be explored immediately.

The source code

Github.com/onlyloveyd/…

Kotlin Couroutine source

Pay attention to my

OpenCV or Android

Reply [computer vision] [Android] [Flutter] [OpenCV] white whoring learning materials.