Kotlin detailed article notes collation update progress: Kotlin Series – High order functions and Common functions in standard Library (3) Kotlin Series – Advanced Deep Generic Covariant Inversion from Java to Kotlin(4)

preface

I’ve studied Kotlin for a whole series, but I haven’t put together a blog post on coroutines. Alas, recent state is a bit wrong > _ < | |. But come on anyway!! The last one should end with a perfect ending and a beautiful flower.

One of the things about coroutines is that just to give you an idea, coroutines are not esoteric things, they’re actually threads, they’re not a new concept that comes out of thin air. The official website may sound a bit lofty, but you can actually think of it as an API for us to use the thread pool to encapsulate some logic with the Handler to automatically switch threads

Some basic uses of coroutines

Add basic dependencies

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

GlobalScope

Official website definition: Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Another use of the global scope is operators running in Dispatchers.Unconfined, Which don’t have any job associated with them. Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged. This is generally used for top-level coroutines, application lifecycle level, and will not be cancelled prematurely. Applications should generally use an application-defined CoroutineScope. Instances of asynchrony or startup GlobalScope are discouraged (not recommended).

Let’s simulate a scenario where globalScopeLaunch is called from an ActivityA, or globalScopeLaunch is called to perform a time-consuming operation, such as an IO operation or a network request. Then destroy ActivityA and jump to ActivityB before it returns

fun globalScopeLaunch(){
        GlobalScope.launch(Dispatchers.Main) {
            delay(5000)
            Toast.makeText(this@MainActivity,"Wait five seconds for eject ~~~",Toast.LENGTH_LONG).show()
        }
    }
Copy the code

You’ll notice that it still pops up the Toast, but that’s not what we want. There are transactions that we should end with the end of the component’s life cycle. Otherwise, resources will be wasted or memory will leak. (The problem is that you can avoid this if you turn it off manually at the end of the life cycle. But this involves manually controlling it yourself.)

 private fun globalScopeLaunch1(){
        GlobalScope.launch(Dispatchers.Main) {
            launch (Dispatchers.Main){
                delay(1000)
                Toast.makeText(this@MainActivity,"Wait one second to eject.",Toast.LENGTH_LONG).show()
            }
            Toast.makeText(this@MainActivity,"Pop it right away.",Toast.LENGTH_LONG).show()
            delay(5000)
            Toast.makeText(this@MainActivity,"Wait five seconds for eject ~~~",Toast.LENGTH_LONG).show()
       }
    }
Copy the code

In GlobalScope Aunch1, eject immediately ~~~ -> wait one second to eject ->” Wait five seconds to eject ~~~. Here will pop up immediately pop up the message ~~~. Because in the coroutine, a new coroutine is opened, and the new coroutine blocks for a second and nothing happens to the outside coroutine, and the outside coroutine continues.

private fun globalScopeLaunch(){job = globalscope.launch (dispatchers.main){launch(dispatchers.main){runBlocking {// add runBlocking to the applet scope delay(1000) Toast.makeText(this@MainActivity,"Wait one second to eject.",Toast.LENGTH_LONG).show()
                }
            }
            Toast.makeText(this@MainActivity,"Pop it right away.",Toast.LENGTH_LONG).show()
            delay(5000)
            Toast.makeText(this@MainActivity,"Wait five seconds for eject ~~~",Toast.LENGTH_LONG).show()
       }
    }
Copy the code

– runBlocking causes the Toast to pop up immediately. Instead of showing it immediately, it will wait 1 second before popping up again.

The code above can be simplified to use withContext(dispatchers.main) instead of launch (dispatchers.main) in the coroutine scope.

job =  GlobalScope.launch(Dispatchers.Main) {
            withContext(Dispatchers.Main){} 
}
Copy the code

Coroutine scope

Globalscope.launch (dispatchers.main) is sent to the Main thread for delay but does not cause ANR

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
Copy the code

The third argument: block: suspend CoroutineScope.() means that CoroutineScope is CoroutineScope used and does not block. Blocking here refers to code blocking outside the coroutine scope, and can be blocked inside it. CoroutineScope is a parent of GlobalScope

The launch modes of coroutines are launch and Async

private fun globalScopeAsync(){
  GlobalScope.launch(Dispatchers.Main){
  val deferred = async(IO) {
                Thread.sleep(5000)
                "Wait five seconds for eject ~~~"
            }
    Toast.makeText(this@MainActivity,"Pop it out right now.", toast.length_long).show()// Val message = deferred.await() where sync does not block code outside the async coroutine Toast.makeText(this@MainActivity,message,Toast.LENGTH_LONG).show() } }Copy the code

Async will asynchronously run the logic of the coroutine outside the scope. We can see that the “pop up now first ~~” popup box will pop up first, and then” wait five seconds to pop up “popup box will pop up again after five seconds. In await it blocks waiting for the Deferred to return before continuing.

Coroutines distribution

Cancellation of coroutines

Get the corresponding coroutine Job object, call Cancel ()

Var job = globalscope.launch (dispatchers.main) {} job.cancel() // The object of Deferred is job var Deferred = globalscope.async {}  deferred.cancel()Copy the code

The correct posture for using coroutines on Android

MainScope

The official definition of Global above prompts us to use a custom coroutine. MainScope is a coroutine scope that Kotlin has customized for us. Code definition:

@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
Copy the code

Basic use:

Class MyAndroidActivity {private val scope = MainScope() // Val MainScope = MainScope() + CoroutineName(this.javaClass.name) private funmainScopeLaunch(){
        scope.launch {}
  }
  override fun onDestroy() {
    super.onDestroy()
    scope.cancel()
  }
}
Copy the code

You can put this logic in the Base class

/ / don't need to define coroutines name when the open class BaseCoroutineScopeActivity: AppCompatActivity() , CoroutineScope by MainScope() class MainActivity :BaseCoroutineScopeActivity(){

 private fun mainScopeLaunch(){
        launch {  }
    }
override fun onDestroyCancel () {super. OnDestroy () ()}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / define coroutines name when open class BaseCoroutineScopeActivity :AppCompatActivity() {
    val mainLaunch =  MainScope()+ CoroutineName(this.javaClass.simpleName)
}

class MainActivity : BaseCoroutineScopeActivity(){
 private fun mainScopeLaunch(){
        mainLaunch.launch {  }
    }
override fun onDestroy() {
        super.onDestroy()
        mainLaunch.cancel()
    }
}

Copy the code

ViewModelScope

Using the coroutine starts by importing packages

api 'androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.2.0 - rc02'
Copy the code

Code definition:

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if(scope ! = null) {return scope
            }
            return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) } internal class CloseableCoroutineScope(context:  CoroutineContext) : Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context override funclose() {
        coroutineContext.cancel()
    }
}
Copy the code

This code is the same as MainScope, except that the CloseableCoroutineScope package is added. Let’s go in and look at setTagIfAbsent

   <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            closeWithRuntimeException(result);
        }
        return result;
    }
Copy the code

So you can see here that when mCleared = true it will automatically close viewModelScope, which means it will help us with the lifecycle and we can just use it.

Use:

fun requestAhuInfo() {
         viewModelScope.launch {         }
    }
Copy the code

I don’t use LiveData && LifecycleScope myself.

recommend

Bing Xin says TM – How to use coroutines correctly on Android? There are some coroutines that Kotlin provides for us

Multiple tasks in coroutines

  • Launch + withContext multiple tasks
        viewModelScope.launch {
            var result1 = withContext(Dispatchers.IO) {
                Log.i(TAG, "result1-1")
                Log.i(TAG, "result1-2")
                Thread.sleep(4000)
                Log.i(TAG, "result1-3")
                "Hello"
            }
            var result2 = withContext(Dispatchers.IO) {
                Log.i(TAG, "result2-1")
                Log.i(TAG, "result2-2")
                Log.i(TAG, "result2-3")
                "world"} val result = result1 + result2 Log.i(TAG, Result)} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the 2020-03-23 19:19:40. 587 11021-11096/com.ldr.testcoroutines I/MainViewModel: The 2020-03-23 19:19:40 result1-1. 587, 11021-11096 / com. The LDR. Testcoroutines I/MainViewModel: Result1-2 2020-03-23 19:19:44. 592, 11021-11096 / com. The LDR. Testcoroutines I/MainViewModel: Result1-3 19:19:44. 2020-03-23, 602, 11021-11096 / com. LDR. Testcoroutines I/MainViewModel: The 2020-03-23 19:19:44 result2-1. 603, 11021-11096 / com. The LDR. Testcoroutines I/MainViewModel: Result2-2 2020-03-23 19:19:44. 603, 11021-11096 / com. The LDR. Testcoroutines I/MainViewModel: Result2-3 19:19:44. 2020-03-23, 603, 11021-11021 / com. LDR. Testcoroutines I/MainViewModel: the HelloworldCopy the code
  • (Launch + async) (launch+ launch)
        viewModelScope.launch {
            val deferred = async(Dispatchers.IO) {
                Log.i(TAG, "result1-1")
                Log.i(TAG, "result1-2")
                Thread.sleep(4000)
                Log.i(TAG, "result1-3")
                "Hello"
            }
            val deferred1 = async {
                Log.i(TAG, "result2-1")
                Log.i(TAG, "result2-2")
                Log.i(TAG, "result2-3")
                "world"} var str = deferred.await() + deferred1.await() Log.i(TAG, STR)} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the 2020-03-25 13:36:00. 736 6221-6253/com.ldr.testcoroutines I/MainViewModel: The 2020-03-25 13:36:00 result1-1. 736, 6221-6253 / com. The LDR. Testcoroutines I/MainViewModel: Result1-2 2020-03-25 13:36:00. 737, 6221-6221 / com. The LDR. Testcoroutines I/MainViewModel: The 2020-03-25 13:36:00 result2-1. 737, 6221-6221 / com. The LDR. Testcoroutines I/MainViewModel: Result2-2 2020-03-25 13:36:00. 737, 6221-6221 / com. The LDR. Testcoroutines I/MainViewModel: Result2-3 13:36:04. 2020-03-25, 738, 6221-6253 / com. LDR. Testcoroutines I/MainViewModel: Result1-3 13:36:04. 2020-03-25, 750, 6221-6221 / com. LDR. Testcoroutines I/MainViewModel: the HelloworldCopy the code
  viewModelScope.launch {
            launch(Dispatchers.IO) {
                Log.i(TAG, "result1-1")
                Log.i(TAG, "result1-2")
                Thread.sleep(4000)
                Log.i(TAG, "result1-3")
            }

            launch(Dispatchers.IO) {
                Log.i(TAG, "result2-1")
                Log.i(TAG, "result2-2")
                Log.i(TAG, "result2-3")}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the 2020-03-23 19:21:29. 781 11021-11128/com.ldr.testcoroutines I/MainViewModel: The 2020-03-23 19:21:29 result1-1. 781, 11021-11128 / com. The LDR. Testcoroutines I/MainViewModel: Result1-2 2020-03-23 19:21:29. 782, 11021-11096 / com. The LDR. Testcoroutines I/MainViewModel: The 2020-03-23 19:21:29 result2-1. 782, 11021-11096 / com. The LDR. Testcoroutines I/MainViewModel: Result2-2 2020-03-23 19:21:29. 782, 11021-11096 / com. The LDR. Testcoroutines I/MainViewModel: Result2-3 19:21:33. 2020-03-23, 782, 11021-11128 / com. LDR. Testcoroutines I/MainViewModel: result1-3Copy the code

Exception handling of coroutines

  • CoroutineExceptionHandler
  val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception") } fun catchFun(): Unit { viewModelScope.launch(handler) { throw IOException() } } fun catch2Fun(): Unit { viewModelScope.launch(handler) { launch(Dispatchers.IO) { withContext(Dispatchers.Main){ throw IOException() } } }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the 2020-03-23 19:57:21. 205 12038-12096/com.ldr.testcoroutines I/System.out: Caught Java. IO. IOException 19:59:23. 2020-03-23, 221, 12038-12096 / com. LDR. Testcoroutines I/System. Out: Caught java.io.IOExceptionCopy the code

Through the above test, can know CoroutineExceptionHandler under the method, multilayer can be nested exception also captured.

  • try { }catch(){}
Funcatch1fun (): Unit { try { viewModelScope.launch(Dispatchers.Main) { throw IOException() } }catch (e:Exception){ Log.i(this.javaClass.name,e.cause?.message?:"Threw an exception.")}} // The correct way to write it ,,,, I feel like I'm talking nonsense... fun catch1Fun(): Unit { viewModelScope.launch(Dispatchers.Main) { try { throw IOException() }catch (e:Exception){ Log.i(this.javaClass.name,e.cause?.message?:"Threw an exception.")}}}Copy the code

Attached source code address: github.com/lovebluedan…

conclusion

The above is a brief introduction, some basic usage of coroutine, about many of the original things, later have the opportunity to write ~~ to tell the truth, I did not use very deep, so many details have not understood well. In the past, you can write abstruse points, less nonsense, less code, articles written to refine points ~~