Because the article involves only a lot of points, the content may be too long, according to their own ability level and familiarity with the stage jump read. If there is an incorrect place to tell you trouble private letter to the author, very grateful

In order to facilitate reading, this article is divided into multiple chapters, and the corresponding chapters are selected according to their own needs. Now it is only a general catalog in the author’s mind, and the final update shall prevail:

  • Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines
  • Kotlin coroutine theory (1)
  • Kotlin coroutine theory (2)
  • [Kotlin coroutine introduction for Android version in detail (8) -> In-depth Kotlin coroutine principle (3)]
  • [Kotlin coroutine introduction for Android (9) -> In-depth Kotlin coroutine principle (4)]

Extend the series

  • Encapsulating DataBinding saves you thousands of lines of code
  • A ViewModel wrapper for everyday use

This chapter, the preface

In the previous kotlin introduction to coroutines, we learned the basics of coroutines and how to use them. Starting with this chapter, we will break down the coroutine principle. Because of the principle involved, the author is more cautious. After all, my ability is limited, and I am afraid that if I say something wrong, I will mislead you. So please go to see it with questions. Remember to order a cup of milk tea first.

The following chapters can be a bit tricky to understand, and a bit of a brain drain.

But it’s not a problem. I’m sure you can do it. If you still don’t understand, watch it several times.

If you still don’t understand it, you have to believe that it has nothing to do with me. How could it be that I didn’t make it clear? It doesn’t exist. If there were, I wouldn’t admit it.

suspendOf huachangzi

As we have learned in the previous chapters, coroutines are mainly started by launch, async, etc., which receive three types of parameters.

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

CoroutineContext and CoroutineStart explain what we mean by CoroutineStart and what we mean by CoroutineStart in the kotlin Coroutine Key Points section. We are going to break down the block parameter.

Block is defined using both suspend and CoroutineScope.() -> Unit. We know that suspend means that this is a suspend function, and we need to look at it in two separate parts. () -> Unit is a function type, but it is preceded by a CoroutineScope. Prefix, indicating that this is a function type with receivers. It is similar to the extension functions with, apply, and let that allow access to members of the receiver object within the function body.

How do we understand this? Suppose we now define four functions. Functions A and B can each pass in a suspended function, and functions C and D are ordinary functions to distinguish them from a and B. We then call them in the function test:

class Test {
    fun test(a) {
        a {
        }
        b {
            launch {
            }
        }
        c {
        }
        d {
            launch {
            }
        }
    }

    fun a(block: suspend() - >Unit){}fun b(block: suspend CoroutineScope. () - >Unit){}fun c(block: () -> Unit){}fun d(block: CoroutineScope. () - >Unit){}}Copy the code

We found that functions A and C cannot use launch, async and other coroutine object methods directly. Functions B and D can be used directly.

Hiding behind your backContinuation

We need to take a look at the contents of these functions when compiled into kotlin bytecode, and extract the contents of a and B from the bytecode we want to see:

  // declaration: void a(kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>, ? extends java.lang.Object>)
  public final a(Lkotlin/jvm/functions/Function1;)V
  // declaration: void b(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope, ? super kotlin.coroutines.Continuation<? super kotlin.Unit>, ? extends java.lang.Object>)
  public final b(Lkotlin/jvm/functions/Function2;)V
  // declaration: void c(kotlin.jvm.functions.Function0<kotlin.Unit>)
  public final c(Lkotlin/jvm/functions/Function0;)V
  // declaration: void d(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope, kotlin.Unit>)
  public final d(Lkotlin/jvm/functions/Function1;)V
Copy the code

It doesn’t matter that we don’t know much about bytecode writing. From my years of writing bugs, I can see the outline.

Let’s decompile kotlin bytecode into Java code and see:

  • functionaTo receive aFunction1The parameters.
  • functionbTo receive aFunction2The parameters.
  • functioncTo receive aFunction0The parameters.
  • functiondTo receive aFunction1The parameters.
  • Their return values arevoid.
public final void a(@NotNull Function1 block) {
   Intrinsics.checkNotNullParameter(block, "block");
}
public final void b(@NotNull Function2 block) {
   Intrinsics.checkNotNullParameter(block, "block");
}
public final void c(@NotNull Function0 block) {
   Intrinsics.checkNotNullParameter(block, "block");
}
public final void d(@NotNull Function1 block) {
   Intrinsics.checkNotNullParameter(block, "block");
}
Copy the code

So what are Function0, Function1, and Function2? To see how they are defined in the Lkotlin/ JVM /functions/ file:

package kotlin.jvm.functions
/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(a): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2): R
}

// omit some code...

/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}
Copy the code

Function is a kotlin encapsulation of Function types. All of Kotlin’s function-type objects are compiled into the FunctionN family, where N indicates that the function takes N arguments.

For example, if a function does not take arguments, the corresponding function is of type Function0. The function that takes one argument is of type Function1. A function that takes two arguments is of type Function2, and so on. So, we can use () to call a function object when using a function type object, or we can call the invoke function.

So we know that function A takes 1 argument and function B takes 2 arguments, and we go back to the bytecode and there is a line comment on function A and function B:

  // declaration: void a(kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>, ? extends java.lang.Object>)
  public finala(Lkotlin/jvm/functions/Function1;) V// declaration: void b(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope, ? super kotlin.coroutines.Continuation<? super kotlin.Unit>, ? extends java.lang.Object>)
  public finalb(Lkotlin/jvm/functions/Function2;) V// declaration: void c(kotlin.jvm.functions.Function0<kotlin.Unit>)
  public finalc(Lkotlin/jvm/functions/Function0;) V// declaration: void d(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope, kotlin.Unit>)
  public finald(Lkotlin/jvm/functions/Function1;) VCopy the code

Comparing the definitions of Function0, Function1, and Function2, we can see that they are the same. But when we define functions A and b, we pass in a suspend function. So by default they all accept a Continuation type argument. Function B also receives a function type with a receiver, so it has a CoroutineScope parameter.

Functions C and D, on the other hand, do not take suspended function arguments, so c does not take any arguments. D receives a CoroutineScope type parameter.

Let’s continue with the bytecode of the test function (omitting some of it) :

 public final test(a)V
    / /...
    NEW com/example/myapplication/Test$test$1
    / /...
    INVOKESPECIAL com/example/myapplication/Test$test$1.<init> (Lkotlin/coroutines/Continuation;)V
    / /...
    INVOKEVIRTUAL com/example/myapplication/Test.a (Lkotlin/jvm/functions/Function1;)V
    / /...
    NEW com/example/myapplication/Test$test$2
    / /...
    INVOKESPECIAL com/example/myapplication/Test$test$2.<init> (Lkotlin/coroutines/Continuation;)V
    / /...
    INVOKEVIRTUAL com/example/myapplication/Test.b (Lkotlin/jvm/functions/Function2;)V
    / /...
    GETSTATIC com/example/myapplication/Test$test$3.INSTANCE : Lcom/example/myapplication/Test$test$3;
    / /...INVOKEVIRTUAL com/example/myapplication/Test.c (Lkotlin/jvm/functions/Function0;) V/ /...
    GETSTATIC com/example/myapplication/Test$test$4.INSTANCE : Lcom/example/myapplication/Test$test$4;
    / /...INVOKEVIRTUAL com/example/myapplication/Test.d (Lkotlin/jvm/functions/Function1;) V/ /...
Copy the code

In the test function, we create objects test$1 and test$2 for functions A and B, respectively, while functions C and D get static instances test$3 and test$4 directly.

Let’s look at how the four instance objects are defined in bytecode:

/ / method a
final class com/example/myapplication/Test$testThe $1extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function1 {

     public finalinvokeSuspend(Ljava/lang/Object;) Ljava/lang/Object;/ /...
      public finalcreate(Lkotlin/coroutines/Continuation;) Lkotlin/coroutines/Continuation;/ /...
      public finalinvoke(Ljava/lang/Object;) Ljava/lang/Object;/ /...
      <init>(Lkotlin/coroutines/Continuation;) V INVOKESPECIAL kotlin/coroutines/jvm/internal/SuspendLambda.<init> (ILkotlin/coroutines/Continuation;) V/ /...
}

B / / method
final class com/example/myapplication/Test$test$2 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2 {
    public finalinvokeSuspend(Ljava/lang/Object;) Ljava/lang/Object;/ /...
    public finalcreate(Ljava/lang/Object; Lkotlin/coroutines/Continuation;) Lkotlin/coroutines/Continuation;/ /...
    public finalinvoke(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object;/ /...
    <init>(Lkotlin/coroutines/Continuation;) V INVOKESPECIAL kotlin/coroutines/jvm/internal/SuspendLambda.<init> (ILkotlin/coroutines/Continuation;) V/ /...
}

C / / method
final class com/example/myapplication/Test$test$3 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {
     / /...
    public final invoke()V
     / /...
    public final static Lcom/example/myapplication/Test$test$3; INSTANCE
    static <clinit>()V
        NEW com/example/myapplication/Test$test$3
    / /...
}

D / / method
final class com/example/myapplication/Test$test$4 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function1 {
 / /...
 public finalinvoke(Lkotlinx/coroutines/CoroutineScope;) V/ /...
 public final static Lcom/example/myapplication/Test$test$4; INSTANCE
 static <clinit>()V
     NEW com/example/myapplication/Test$test$4
    / /...
}
Copy the code

If you look at them now, it’s clear that they both implement the FunctionN interface. But functions C and D inherit from Lambda, while functions A and B inherit from SuspendLambda. This makes it clear that the compile-time implementation of the suspend function defined as suspend is different from that of normal functions.

At this point, the c and D functions are done. It gives us a clear view of the difference between a suspended function and a normal function when executed after compilation. We will focus on the a and B implementations next.

Hope in the village –SuspendLambda

Where does this Continuation come from when we say that suspended functions accept a Continuation by default?

We can see in the bytecode that both functions A and B have an init method, which is the constructor. They both receive a Continuation type parameter. This is where you create the SuspendLambda object.

Now moving on to the source of invokeSuspend and create, we need to look at the definition of SuspendLambda:

internal abstract class SuspendLambda(
    publicoverride val arity: Int, completion: Continuation<Any? >? : ContinuationImpl(completion), FunctionBase<Any? >, SuspendFunction{
    constructor(arity: Int) : this(arity, null)
    / /...
}
Copy the code

You can see more clearly here that the SuspendLambda itself needs to receive a Continuation parameter when it is created. It inherits FunctionBase and SuspendFunction that we can just ignore, they are mainly used for markup.

  • FunctionBaseIt’s meant to indicate that this is a value of function type, for examplelambda,Anonymous functionsorFunction reference.
  • SuspendFunctionTo distinguish suspended function types from normal function types, all suspended function types must implement this interface.

SuspendLambda implements ContinuationImpl, a class whose main purpose is to add interceptors and implement thread scheduling.

internal abstract class ContinuationImpl( completion: Continuation<Any? >? .private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    constructor(completion: Continuation<Any?>?) : this(completion, completion? .context)/ /...
    @Transient
    private varintercepted: Continuation<Any? >? =null

    public fun intercepted(a): Continuation<Any? >= intercepted ? : (context[ContinuationInterceptor]? .interceptContinuation(this) ?: this)
                .also { intercepted = it }

    / /...
}
Copy the code

This class will be used when we talk about the details of coroutines interceptors and scheduling, so we’ll skip this class and continue. It inherits the BaseContinuationImpl class that we need to focus on:

internal abstract class BaseContinuationImpl(
    publicval completion: Continuation<Any? >?: Continuation<Any? >, CoroutineStackFrame, Serializable{
 
    public final override fun resumeWith(result: Result
       
        )
       ?>{
    // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
        var current = this
        var param = result
        while (true) {
            // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
            // can precisely track what part of suspended callstack was already resumed
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation without completionval outcome: Result<Any? > =try {
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return}}}}protected abstract fun invokeSuspend(result: Result
       
        )
       ?>: Any?

      / /...

    public open fun create(completion: Continuation<*>): Continuation<Unit> {
        throw UnsupportedOperationException("create(Continuation) has not been overridden")}public open fun create(value: Any? , completion: Continuation<*>): Continuation<Unit> {
        throw UnsupportedOperationException("create(Any? ; Continuation) has not been overridden")}/ /...
}
Copy the code

BaseContinuationImpl mainly implements the Continuation interface. As we mentioned in Section 2 earlier, the coroutine body is itself a Continuation, and the Continuation resumes the execution of the coroutine by calling the resumeWith function.

BaseContinuationImpl implements the coroutine execution process, as well as the coroutine suspend and resume operations. He also leaves the creation of his Continuation create and the execution of invokeSuspend to subclasses. Therefore, we can assume that SuspendLambda is the one that is ultimately executed.

Let’s decompile kotlin bytecode into Java code to see how these four functions are called in the test method:

public final void test(a) {
   this.a((Function1)(new Function1((Continuation)null) {
      int label;

      @Nullable
      public final Object invokeSuspend(@NotNull Object var1) {
         Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
         switch(this.label) {
         case 0:
            ResultKt.throwOnFailure(var1);
            return Unit.INSTANCE;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); }}@NotNull
      public final Continuation create(@NotNull Continuation completion) {
         Intrinsics.checkNotNullParameter(completion, "completion");
         Function1 var2 = new <anonymous constructor>(completion);
         return var2;
      }

      public final Object invoke(Object var1) {
         return ((<undefinedtype>)this.create((Continuation)var1)).invokeSuspend(Unit.INSTANCE); }}));this.b((Function2)(new Function2((Continuation)null) {
      // $FF: synthetic field
      private Object L$0;
      int label;

      @Nullable
      public final Object invokeSuspend(@NotNull Object var1) {
         Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
         switch(this.label) {
         case 0:
            ResultKt.throwOnFailure(var1);
            CoroutineScope $this$b = (CoroutineScope)this.L$0;
            BuildersKt.launch$default($this$b, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
               int label;

               @Nullable
               public final Object invokeSuspend(@NotNull Object var1) {
                  Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                  switch(this.label) {
                  case 0:
                     ResultKt.throwOnFailure(var1);
                     return Unit.INSTANCE;
                  default:
                     throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); }}@NotNull
               public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
                  Intrinsics.checkNotNullParameter(completion, "completion");
                  Function2 var3 = new <anonymous constructor>(completion);
                  return var3;
               }

               public final Object invoke(Object var1, Object var2) {
                  return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE); }}),3, (Object)null);
            return Unit.INSTANCE;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); }}@NotNull
      public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
         Intrinsics.checkNotNullParameter(completion, "completion");
         Function2 var3 = new <anonymous constructor>(completion);
         var3.L$0 = value;
         return var3;
      }

      public final Object invoke(Object var1, Object var2) {
         return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE); }}));this.c((Function0)null.INSTANCE);
   this.d((Function1)null.INSTANCE);
}
Copy the code

You don’t get it, do you? Then don’t look at it. I’m just Posting it to scare you. Because this chapter is already a little too much. The above paragraph is what we will cover in the next chapter.

So it’s not surprising that this chapter ends here. Is there a feeling of “You’re talking a lot, but you’re not talking at all”? Don’t be surprised, when it’s all expected, this chapter is just to prepare you for the election, and the rest of the chapter is the main point.

To summarize this chapter:

  1. Compiled differences between suspended functions and normal functions.
  2. Suspended functions all receive one by defaultContinuationType parameter.
  3. To distinguish suspended functions from normal functions, the suspended function types are implementedSuspendFunctionInterface.
  4. Although we all doContinuationBut in factSuspendLambdaIt implements the coroutine execution process, as well as the coroutine suspend and resume operations.

trailer

In the next article, we’ll cover the implementation details of coroutines execution, suspension, recovery, and state machines.

Originality is not easy. If you like this article, you can move your little hands to like collection, your encouragement will be transformed into my motivation to move forward.

  • Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines
  • Kotlin coroutine theory (1)
  • Kotlin coroutine theory (2)

Extend the series

  • Encapsulating DataBinding saves you thousands of lines of code
  • A ViewModel wrapper for everyday use