First, let's start with definitions to make it clear. If you need a tutorial or playground for Coroutines and Coroutines Flow you can check out this tutorial/playground i created.
Scope
is object you use to launch coroutines that only contains one object which is CoroutineContext
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
}
The coroutine context is a set of rules and configurations that define
how the coroutine will be executed.
Under the hood, it’s a kind of map, with a set of possible keys and values.
Coroutine context is immutable, but you can add elements to a context using plus operator,
just like you add elements to a set, producing a new context instance
The set of elements that define the behavior of a coroutine are:
- CoroutineDispatcher — dispatches work to the appropriate thread.
- Job — controls the lifecycle of the coroutine.
- CoroutineName — name of the coroutine, useful for debugging.
- CoroutineExceptionHandler — handles uncaught exceptions
Dispatchers
Dispatchers determine which thread pool should be used. Dispatchers class is also
CoroutineContext which can be added to CoroutineContext
Dispatchers.Default: CPU-intensive work, such as sorting large lists, doing complex calculations and similar. A shared pool of threads on the JVM backs it.
Dispatchers.IO: networking or reading and writing from files.
In short – any input and output, as the name states
Dispatchers.Main: mandatory dispatcher for performing UI-related events in Android's main or UI thread.
For example, showing lists in a RecyclerView, updating Views and so on.
You can check out Android's official documents for more info on dispatchers.
Edit Even though official document states that
Dispatchers.IO - This dispatcher is optimized to perform disk or
network I/O outside of the main thread. Examples include using the
Room component, reading from or writing to files, and running any
network operations.
Answer from Marko Topolnic
IO runs the coroutine on a special, flexible thread pool. It exists
only as a workaround when you are forced to use a legacy, blocking IO
API that would block its calling thread.
might be right either.
Job A coroutine itself is represented by a Job.
A Job is a handle to a coroutine. For every coroutine that you create (by launch or async),
it returns a Job instance that uniquely identifies the coroutine and manages its lifecycle.
You can also pass a Job to a CoroutineScope to keep a handle on its lifecycle.
It is responsible for coroutine’s lifecycle, cancellation, and parent-child relations.
A current job can be retrieved from a current coroutine’s context:
A Job can go through a set of states: New, Active, Completing, Completed, Cancelling and Cancelled.
while we don’t have access to the states themselves,
we can access properties of a Job: isActive, isCancelled and isCompleted.
CoroutineScope It is defined a simple factory function that takes CoroutineContext
s as arguments to create wrapper around the combined CoroutineContext as
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())
internal class ContextScope(context: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext = context
// CoroutineScope is used intentionally for user-friendly representation
override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}
and creates a Job
element if the provide context does not have one already.
Let's look at GlobalScope source code
/**
* A global [CoroutineScope] not bound to any job.
*
* 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][CoroutineScope.async] or [launch][CoroutineScope.launch]
* on the instance of [GlobalScope] is highly discouraged.
*
* Usage of this interface may look like this:
*
* ```
* fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
* for (number in this) {
* send(Math.sqrt(number))
* }
* }
* ```
*/
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
As you can see it extends CoroutineScope
1- GlobalScope.launch(Dispatchers.IO) {}
GlobalScope is alive as long as you app is alive, if you doing some counting for instance in this scope and rotate your device it will continue the task/process.
GlobalScope.launch(Dispatchers.IO) {}
runs as long as your app is alive but in IO thread because of using Dispatchers.IO
2- GlobalScope.launch{}
It's same as the first one but by default, if you don't have any context, launch uses EmptyCoroutineContext which uses Dispatchers.Default, so only difference is thread with first one.
3- CoroutineScope(Dispatchers.IO).launch{}
This one is the same as first one with only syntax difference.
4- lifecycleScope.launch(Dispatchers.IO){}
lifecycleScope
is an extention for LifeCycleOwner
and bound to Actvity or Fragment's lifCycle where scope is canceled when that Activity or Fragment is destroyed.
/**
* [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
You can also use this as
class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main + CoroutineName(" Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->
println(" Exception $throwable in context:$coroutineContext")
}
private val dataBinding by lazy {
Activity3CoroutineLifecycleBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(dataBinding.root)
job = Job()
dataBinding. button.setOnClickListener {
// This scope lives as long as Application is alive
GlobalScope.launch {
for (i in 0..300) {
println(" Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
delay(300)
}
}
// This scope is canceled whenever this Activity's onDestroy method is called
launch {
for (i in 0..300) {
println(" Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
withContext(Dispatchers.Main) {
dataBinding.tvResult.text = " Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"
}
delay(300)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}