An example of Android combined with kotlin using coroutine

  • 2021-12-09 10:13:11
  • OfStack

Recently, I entered the Android pit, and I am still in a crazy state of learning, so I haven't written a blog for a long time. Today, I recorded a small code snippet, a small example of using coroutine on Android.

Because I am doing a bookkeeping software to learn, I used gRPC, at first I used threads to do network requests:


thread {
 //  Network request code 

 runOnUiThread {
  //  Update UI Code of 
 }
}

Today, rewrite all this set as coroutine.

First of all, coroutine has to have a scheduler, which is called "Dispatchers" in English. There are several:

Dispatchers. Main coroutine runs on the main thread and UI thread in Android, so if coroutine executes a lot of time-consuming code, it will also get stuck with UI Dispatchers. IO is used to run big IO Dispatchers. Default is used to run high CPU consumption Dispatchers. Unconfined is not bound to any particular thread of execution

Then, in order to group multiple coroutine, just like many threads can be put in the process, another concept is developed, called scope. By default, there is a global scope, called GlobalScope, which is just like global variable 1. On Android, the life cycle of coroutine running inside is as long as app1, so it is not recommended to start coroutine here.

The recommended method is to start with one scope in each Activity, and then start with launch.

So I write the base class like this:


abstract class BaseActivity : AppCompatActivity(), CoroutineScope {
 /*
  Default coroutine scope Yes Main That is, UI Thread ( Main thread ) . If you want to do it IO For example, network requests, remember 
  Wrapped in  launch(Dispatchers.IO) {}  In, if you want to calculate a lot, wrap it in  launch(Dispatcher.Default) {}  Li 
  Or write directly  launch .  UI For operations, use the  withContext(Dispatchers.Main) {}  Cut back 
  */
 private val job = SupervisorJob()
 override val coroutineContext: CoroutineContext
  get() = Dispatchers.Main + job

 override fun onDestroy() {
  super.onDestroy()
  coroutineContext.cancelChildren()
 }

After this, you can directly launch and start coroutine:


launch {
 val req = CreateFeedbackReq.newBuilder().build()
 val respAny = callRPC {
  api.createFeedback(req)
 }
 respAny?:return@launch

 val resp = respAny as CreateFeedbackResp
 if (handleRespAction(resp.action)) {
  withContext(Dispatchers.Main) {
   showSnackBar(R.string.thank_you_for_feedback)
   delay(1000)
   finish()
  }
 }
}

As mentioned above, by default, root coroutine is the current activity, and they will be executed on Dispatchers. Main by default. If you want coroutine to be executed on other dispatcher, use withContext, and then use withContext (Dispatchers. Main) if you want to update UI.

Then why is Dispatchers. Main directly used if launch does not transmit parameters? Because CoroutineScope is actually an interface, and coroutineContext is a variable inside:


public interface CoroutineScope {
 /**
  * The context of this scope.
  * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
  * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
  *
  * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
  */
 public val coroutineContext: CoroutineContext
}

Let's look at the implementation of launch again:


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
}

@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
 val combined = coroutineContext + context
 val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
 return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
  debug + Dispatchers.Default else debug
}

As you can see, by default, the current coroutineContext comes first.

Kotlin's coroutine is very easy to use, but I still feel a little complicated, and I am still learning.

ref:

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/index.html


Related articles: