Today we will talk about Kotlin
the coroutine Coroutine
. At the end of the article there is an easter egg for everyone.
If you have not been in contact with coroutines, I recommend you to read this entry-level article What? Do you still know Kotlin Coroutine?
If you have contacted the coroutine, I believe you have had the following questions:
- What exactly is a coroutine?
suspend
What is the role of the coroutine and how does it work?- Coroutine some of the key name (for example:
Job
,Coroutine
,Dispatcher
,CoroutineContext
andCoroutineScope
) in the end is kind of how the relationship between them? - What is the so-called non-blocking suspend and resume of the coroutine?
- What is the internal implementation principle of the coroutine?
- …
The following articles try to analyze these questions, and everyone is welcome to join in and discuss.
What is a coroutine
This question is very simple, as long as you are not in contact with the coroutine, you should be able to know. Because the definition is clearly given in the official document.
Let's take a look at the original official words (also the most emboldened paragraph in this article).
Coroutine is a concurrent design pattern, you can use it on the Android platform to simplify the asynchronous code execution.
Knock on the blackboard and draw the key points: Coroutine is a concurrent design pattern.
So it's not another manifestation of what some people say about threads. Although threads are also used internally in the coroutine. But its greater role is its design philosophy. Callback
Eliminate our traditional callback method. Move asynchronous programming closer to synchronous alignment.
After so many explanations, in the end we still point directly to see its advantages
- Lightweight: You can run multiple coroutines on a single thread, because the coroutine supports suspend and will not block the thread that is running the coroutine. Suspending saves memory compared to blocking and supports multiple parallel operations.
- Less memory leaks: Use structured concurrency mechanisms to perform multiple operations in a scope.
- Built-in cancellation support: The cancellation function will automatically propagate through the running coroutine hierarchy.
- Jetpack integration: Many Jetpack libraries include extensions that provide full coroutine support. Some libraries also provide their own coroutine scope, which you can use for structured concurrency.
suspend
suspend
It is the key word of the coroutine, each suspend
modified method must be called in another suspend
function or Coroutine
coroutine program.
The first time I saw this definition, I don’t know if you have any doubts. Anyway, I’m wondering if I have a nap. Why do the suspend
modification methods need to have this limitation? Why can't you not add it? What is its function?
Of course, if you pay attention to my previous article, you should know something about it, because I have briefly mentioned it in this article on reviewing the Retrofit source code and laughing at the coroutine implementation .
Here is a common name for a mechanism CPS(Continuation-Passing-Style)
. Each suspend
modified method or lambda
expression will add additional Continuation
type parameters to it when the code is called .
@GET("/v2/news")
suspend fun newsGet(@QueryMap params: Map<String, String>): NewsResponse
The CPS
real face of the above code after conversion is like this
@GET("/v2/news")
fun newsGet(@QueryMap params: Map<String, String>, c: Continuation<NewsResponse>): Any?
After the conversion, the original return type NewsResponse
is added to the new Continutation
parameter, and the Any?
type is returned at the same time . There may be questions here? The return type is changed, and the result is wrong?
Actually not, Any?
in Kotlin
rather special, it can represent any type.
When a suspend
function is suspended by the coroutine, it will return a special identifier COROUTINE_SUSPENDED
, which is essentially one Any
; when the coroutine is not suspended for execution, it will return the result of the execution or the exception raised. In this way, in order to support the return in both cases, a Kotlin
unique Any?
type is used.
The return value is understood, now let's talk about this Continutation
parameter.
First look at Continutation
the source code
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
context
It is the context of a coroutine. It is more often a CombinedContext
type, similar to a collection of coroutines. This will be explained in detail later.
resumeWith
It is used to wake up the suspended coroutine. As mentioned earlier, in the process of execution of the coroutine, in order to prevent blocking, the suspend feature is used. Once the logic inside the coroutine is executed, this method is used to invoke the coroutine. Let it continue execution where it was previously suspended.
So every suspend
modified function will get the upper level Continutation
and pass it to itself as a parameter. Since it is passed from the upper layer, Continutation
who created it?
In fact, it Continutation
is not difficult to guess that it was created with the coroutine when it was created.
GlobalScope.launch {
}
launch
At that time, the Continutation
object has been created and the coroutine has been started. So the parameters passed by the suspended coroutine in it are all this object.
The simple understanding is that the use of coroutines resumeWith
replaces the traditional method callback
. Every coroutine program will be created Continutation
, and the coroutine will be automatically Continutation
called back once when it is created resumeWith
, so that the coroutine can start to execute.
CoroutineContext
The context of the coroutine, which contains some user-defined data collections, which are closely related to the coroutine. It is similar to a map
collection and can be key
used to obtain different types of data. At the same time CoroutineContext
, it is very flexible. If it needs to be changed, just use the current one CoroutineContext
to create a new one CoroutineContext
.
Look at CoroutineContext
the definition
public interface CoroutineContext {
/**
* Returns the element with the given [key] from this context or `null`.
*/
public operator fun <E : Element> get(key: Key<E>): E?
/**
* Accumulates entries of this context starting with [initial] value and applying [operation]
* from left to right to current accumulator value and each element of this context.
*/
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
/**
* Returns a context containing elements from this context and elements from other [context].
* The elements from this context with the same key as in the other one are dropped.
*/
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
/**
* Returns a context containing elements from this context, but without an element with
* the specified [key].
*/
public fun minusKey(key: Key<*>): CoroutineContext
/**
* Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
*/
public interface Key<E : Element>
/**
* An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.
*/
public interface Element : CoroutineContext {..}
}
Each one CoroutineContext
has its only one Key
type is Element
that we can Key
get the corresponding specific object through the corresponding . It's a bit abstract, let's understand it directly through examples.
var context = Job() + Dispatchers.IO + CoroutineName("aa")
LogUtils.d("$context, ${context[CoroutineName]}")
context = context.minusKey(Job)
LogUtils.d("$context")
// 输出
[JobImpl{Active}@158b42c, CoroutineName(aa), LimitingDispatcher@aeb0f27[dispatcher = DefaultDispatcher]], CoroutineName(aa)
[CoroutineName(aa), LimitingDispatcher@aeb0f27[dispatcher = DefaultDispatcher]]
Job
, Dispatchers
And CoroutineName
have realized the Element
interface.
If you need to combine different ones, CoroutineContext
you can go directly through +
splicing, the essence is to use the plus
method.
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
plus
The realization logic of is to CoroutineContext
encapsulate two splicing CombinedContext
into a splicing chain, and at the same time it will be ContinuationInterceptor
added to the end of the splicing chain each time .
So CombinedContext
what is it?
internal class CombinedContext(
private val left: CoroutineContext,
private val element: Element
) : CoroutineContext, Serializable {
override fun <E : Element> get(key: Key<E>): E? {
var cur = this
while (true) {
cur.element[key]?.let { return it }
val next = cur.left
if (next is CombinedContext) {
cur = next
} else {
return next[key]
}
}
}
...
}
Pay attention to its two parameters, we directly take the above example to analyze
Job() + Dispatchers.IO
(Job, Dispatchers.IO)
Job
Corresponds to left
, Dispatchers.IO
correspondence element
. If you splice another layer CoroutineName(aa)
, it's like this
((Job, Dispatchers.IO),CoroutineName)
The function is similar to the linked list, but the difference is that you can get the whole content connected to you . It is the corresponding minusKey
method to remove the mapping from the set Key
of CoroutineContext
instances.
With this foundation, the get
method we look at it will be very clear. Take it first element
, not take it again left
.
So Key
what exactly is this? Let's take a lookCoroutineName
public data class CoroutineName(
/**
* User-defined coroutine name.
*/
val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
/**
* Key for [CoroutineName] instance in the coroutine context.
*/
public companion object Key : CoroutineContext.Key<CoroutineName>
/**
* Returns a string representation of the object.
*/
override fun toString(): String = "CoroutineName($name)"
}
It's very simple Key
is CoroutineContext.Key<CoroutineName>
, of course, this is not enough, need to continue to combine the operator get
method, so we'll look at Element
the get
methods
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
As used herein, to Kotlin
the operator
operator overloading characteristics. Then the following code is equivalent.
context.get(CoroutineName)
context[CoroutineName]
So we can directly analogous Map
to acquire the entire coroutine manner CoroutineContext
set corresponding to Key
the CoroutineContext
example.
This article mainly introduces the suspend
working principle and CoroutineContext
internal structure. I hope it will be helpful to the partners who study the coroutine, so stay tuned for the follow-up coroutine analysis.
project
android_startup : Provides a simpler and more efficient way to initialize components when the application starts, and optimize the startup speed. Not only supports Jetpack App Startup
all the functions, but also provides additional synchronization and asynchronous waiting, thread control and multi-process support.
AwesomeGithub : Github
Client- based , pure exercise project, support component development, support account password and authentication login. Use Kotlin
language development, project architecture is based on Jetpack&DataBinding
the MVVM
; the project uses Arouter
, Retrofit
, Coroutine
, Glide
, Dagger
and Hilt
other open source technologies popular.
flutter_github : Based on Flutter
the cross-platform version of the Github
client, AwesomeGithub
corresponding to it.
android-api-analysis : Combined with detailed Demo
analysis of Android
relevant knowledge points, it helps readers to grasp and understand the points expounded more quickly.
daily_algorithm : A daily algorithm, from shallow to deep, welcome to join and encourage each other.
Kotlin learns from zero to the whole system
Why learn Kotlin? The future trend? Is it really fragrant?
I personally think that many Kotlin features make the code more concise and understandable than Java without sacrificing performance or security.
Kotlin is compiled to bytecode, so its performance is as good as Java. It has the same compile-time checks as Java (and more, such as built-in nullability checks). Most importantly, Kotlin's language functions and standard library functions can achieve concise and effective code.
Concise, because this is a key factor in improving programmer productivity.
Initially it was assembled. Each line of code only provides you with a functional description of the entire program. This makes reading and writing difficult, because you have to keep so much code in your mind at once.
High-level languages allow us to add more ideas to each line of code. For example, sorting a list is trivial in most modern languages. When each line of code gains more functionality, it is easier to write larger programs.
However, we don't want to include as many ideas as possible in each line of code. If the code is too concise, it will also become difficult to understand. Regular expressions are a typical example of this problem. In short, I still fight it regularly.
In summary, uphold the love of Kotlin, and hope that more friends can get started with Kotlin better and painlessly. The compressed package in the picture of "Kotlin from Zero to Whole Study Notes" collected and compiled is a large number of actual Kotlin cases. , Share it with everyone for free. You can also find the update address of my Android high-level instructional video at station B on my homepage