欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Kotlin掛起函數(shù)原理示例剖析

 更新時(shí)間:2022年08月04日 11:44:40   作者:瀟風(fēng)寒月  
這篇文章主要為大家介紹了Kotlin掛起函數(shù)的原理示例剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、序言

Kotlin掛起函數(shù)平時(shí)在學(xué)習(xí)和工作中用的比較多,掌握其原理還是很有必要的。本文將一步一步帶著大家分析其原理實(shí)現(xiàn)。

ps: 文中所用的Kotlin版本是1.7.0。

二、CPS原理

在某個(gè)Kotlin函數(shù)的前面加個(gè)suspend函數(shù),它就成了掛起函數(shù)(雖然內(nèi)部不一定會(huì)掛起,內(nèi)部不掛起的稱為偽掛起函數(shù))。

先隨便寫(xiě)個(gè)掛起函數(shù)

suspend fun getUserName(): String {
    delay(1000L)
    return "云天明"
}

然后通過(guò)Android Studio的Tools->Kotlin->Show Kotlin Bytecode->Decompile,現(xiàn)在我們拿到了Kotlin字節(jié)碼反編譯之后的Java代碼:

public static final Object getUserName(@NotNull Continuation var0) {
    ...
}

可以看到該函數(shù)被編譯之后,多了一個(gè)Continuation參數(shù),其次,返回值變成了Object。下面,我們?cè)敿?xì)來(lái)討論一下這2種變化:函數(shù)參數(shù)和函數(shù)返回值。

CPS參數(shù)變化

上面的suspend fun getUserName(): String函數(shù),如果我在Java中調(diào)用的話,會(huì)看到Android Studio提示我們

從圖中可以看到,新增了一個(gè)參數(shù),也就是Continuation,它其實(shí)是一個(gè)Callback,只是換了個(gè)名字而已。

來(lái)看下它的定義:

/**
 * Interface representing a continuation after a suspension point that returns a value of type `T`.
 */
@SinceKotlin("1.3")
public interface Continuation<in T> {
    /**
     * 當(dāng)前continuation所在協(xié)程的上下文
     */
    public val context: CoroutineContext
    /**
     * 繼續(xù)執(zhí)行后面的協(xié)程代碼,同時(shí)把結(jié)果回調(diào)出去,結(jié)果可能是成功或失敗
     */
    public fun resumeWith(result: Result<T>)
}

這個(gè)Callback接口會(huì)在resumeWith回調(diào)結(jié)果給外部。

CPS返回值變化

在上面的Continuation接口的定義中,其實(shí)還有個(gè)小細(xì)節(jié),它帶了個(gè)泛型T。這個(gè)泛型T就是我們suspend函數(shù)返回值的類(lèi)型,上面的getUserName返回值是String,編譯之后,這個(gè)String就來(lái)到了Continuation的泛型中。

而getUserName編譯之后的返回值變成了Object。為啥是Object?它有什么用?這個(gè)返回值其實(shí)是用來(lái)標(biāo)識(shí)該函數(shù)是否掛起的標(biāo)志,如果返回值是Intrinsics.COROUTINE_SUSPENDED,那么說(shuō)明該函數(shù)被掛起了(掛起函數(shù)的結(jié)果不是通過(guò)函數(shù)返回值來(lái)獲取的,而是通過(guò)Continuation,也就是Callback回調(diào)得到的結(jié)果)。

如果該函數(shù)是偽掛起函數(shù)(里面沒(méi)有其他掛起函數(shù),但還是會(huì)進(jìn)行CPS轉(zhuǎn)換),則是直接返回結(jié)果。

舉個(gè)例子,下面這個(gè)就是真正的掛起函數(shù):

suspend fun getUserName(): String {
    delay(1000L)
    return "云天明"
}

當(dāng)執(zhí)行到delay的時(shí)候,就會(huì)返回Intrinsics.COROUTINE_SUSPENDED表示該函數(shù)被掛起了。

下面這個(gè)則是偽掛起函數(shù):

suspend fun getName():String {
    return "程心"
}

這種偽掛起函數(shù)不會(huì)返回Intrinsics.COROUTINE_SUSPENDED,而是直接返回結(jié)果,它不會(huì)被掛起。它看起來(lái)就僅僅是一個(gè)普通函數(shù),但還是會(huì)進(jìn)行CPS轉(zhuǎn)換,CPS轉(zhuǎn)換只認(rèn)suspend關(guān)鍵字。你如果像上面這樣寫(xiě),其實(shí)Android Studio也會(huì)提示你,說(shuō)這個(gè)suspend關(guān)鍵字沒(méi)用,叫你把它移除掉。

所以,suspend函數(shù)編譯之后的返回值變成了Object,因?yàn)橐嫒輦螔炱鸷瘮?shù)的返回值,而偽掛起函數(shù)可能返回任何值,而且還可能為空。

下面我們就來(lái)詳細(xì)的探索一下掛起函數(shù)的底層原理,看看掛起函數(shù)反編譯之后是什么樣子。

三、掛起函數(shù)的反編譯

我們先寫(xiě)個(gè)很簡(jiǎn)單的suspend函數(shù),然后將其反編譯,然后分析一下。具體的流程是我們用Android Studio寫(xiě)個(gè)掛起函數(shù)的demo,然后編譯成apk,然后將apk用jadx反編譯一下,拿到對(duì)應(yīng)class的反編譯Java源碼,這樣弄出來(lái)的源碼我感覺(jué)比直接通過(guò)Android Studio的Tools->Kotlin->Show Kotlin拿到的源碼稍微好看懂一些。

首先,我創(chuàng)建了一個(gè)CpsTest.kt文件,然后在里面寫(xiě)了一個(gè)函數(shù):

package com.xfhy.coroutine
import kotlinx.coroutines.delay
suspend fun getUserName(): String {
    delay(1000L)
    return "云天明"
}

就這樣,一個(gè)很普通的掛起函數(shù),在內(nèi)部只是簡(jiǎn)單調(diào)用了下delay,延遲1000L,再返回結(jié)果“云天明”。雖然這個(gè)函數(shù)很簡(jiǎn)單,但反編譯出來(lái)的代碼卻有點(diǎn)多,而且不好看懂,我先把原代碼貼出來(lái),待會(huì)兒再放我重新組織過(guò)的代碼,作為對(duì)比:

public final class CpsTestKt {
   @Nullable
   public static final Object getUserName(@NotNull Continuation var0) {
      Object $continuation;
      label20: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return CpsTestKt.getUserName(this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         ((<undefinedtype>)$continuation).label = 1;
         if (DelayKt.delay(1000L, (Continuation)$continuation) == var3) {
            return var3;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "云天明";
   }
}

這反編譯之后的東西不太好看懂,我重新組織了一下:

public final class CpsTestKt {
    public static final Object getUserName(Continuation<? super java.lang.String> continuation) {
        //這個(gè)TestContinuation實(shí)質(zhì)上是一個(gè)匿名內(nèi)部類(lèi),這里給它取個(gè)名字而已
        final class TestContinuation extends ContinuationImpl {
            //協(xié)程狀態(tài)機(jī)當(dāng)前的狀態(tài)
            int label;
            //保存invokeSuspend回調(diào)時(shí)吐出來(lái)的返回結(jié)果
            Object result;
            TestContinuation(Continuation continuation) {
                super(continuation);
            }
            //invokeSuspend比較重要,它是狀態(tài)機(jī)的入口,會(huì)將執(zhí)行流程交給getUserName再次調(diào)用
            //協(xié)程的本質(zhì),就是CPS+狀態(tài)機(jī)
            public final Object invokeSuspend(Object obj) {
                //callback回調(diào)時(shí)會(huì)把結(jié)果帶出來(lái)
                this.result = obj;
                this.label |= Integer.MIN_VALUE;
                //開(kāi)啟協(xié)程狀態(tài)機(jī)
                return CpsTestKt.getUserName(this);
            }
        }
        TestContinuation testContinuation;
        label20:
        {
            //不是第一次進(jìn)入,則走這里,把continuation轉(zhuǎn)成TestContinuation,TestContinuation只會(huì)生成一個(gè)實(shí)例,不會(huì)每次都生成。
            if (continuation instanceof TestContinuation) {
                testContinuation = (TestContinuation) continuation;
                if ((testContinuation.label & Integer.MIN_VALUE) != 0) {
                    testContinuation.label -= Integer.MIN_VALUE;
                    break label20;
                }
            }
            //如果是第一次進(jìn)入getUserName,則TestContinuation還沒(méi)被創(chuàng)建,會(huì)走到這里,此時(shí)先去創(chuàng)建一個(gè)TestContinuation
            testContinuation = new TestContinuation(continuation);
        }
        //將之前執(zhí)行的結(jié)果取出來(lái)
        Object $result = testContinuation.result;
        //掛起的標(biāo)志,如果需要掛起的話,就返回這個(gè)flag
        Object flag = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        //狀態(tài)機(jī)
        switch (testContinuation.label) {
            case 0:
                // 檢測(cè)異常
                ResultKt.throwOnFailure($result);
                //將label的狀態(tài)改成1,方便待會(huì)兒執(zhí)行delay后面的代碼
                testContinuation.label = 1;
                //0. 調(diào)用DelayKt.delay函數(shù)
                //1. 將testContinuation傳了進(jìn)去
                //2. DelayKt.delay是一個(gè)掛起函數(shù),正常情況下,它會(huì)立馬返回一個(gè)值:IntrinsicsKt.COROUTINE_SUSPENDED(也就是這里的flag),表示該函數(shù)已被掛起,這里就直接return了,該函數(shù)被掛起
                //3. 恢復(fù)執(zhí)行:在DelayKt.delay內(nèi)部,到了指定的時(shí)間后就會(huì)調(diào)用testContinuation這個(gè)Callback的invokeSuspend
                //4. invokeSuspend中又將執(zhí)行g(shù)etUserName函數(shù),同時(shí)將之前創(chuàng)建好的testContinuation傳入其中,開(kāi)始執(zhí)行后面的邏輯(label為1的邏輯),該函數(shù)繼續(xù)往后面執(zhí)行(也就是恢復(fù)執(zhí)行)
                if (DelayKt.delay(1000L, testContinuation) == flag) {
                    return flag;
                }
                break;
            case 1:
                // 檢測(cè)異常
                ResultKt.throwOnFailure($result);
                //label 1這里沒(méi)有return,而是會(huì)走到下面的return "云天明"語(yǔ)句
                break;
            default:
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        }
        return "云天明";
    }
}

在getUserName函數(shù)中,會(huì)多出一個(gè)ContinuationImpl的子類(lèi),它是一個(gè)匿名內(nèi)部類(lèi)(為了方便,給它取了個(gè)名字TestContinuation),也是整個(gè)協(xié)程掛起函數(shù)的核心。在這個(gè)TestContinuation中有2個(gè)變量

  • label: 協(xié)程狀態(tài)機(jī)當(dāng)前的狀態(tài)
  • result: 保存invokeSuspend回調(diào)時(shí)吐出來(lái)的返回結(jié)果

invokeSuspend是一個(gè)抽象方法,當(dāng)協(xié)程從掛起狀態(tài)想要恢復(fù)時(shí),就得調(diào)用這個(gè)invokeSuspend,然后繼續(xù)走狀態(tài)機(jī)邏輯,繼續(xù)執(zhí)行后面的代碼。具體是怎么調(diào)用這個(gè)invokeSuspend的,后面有機(jī)會(huì)再細(xì)說(shuō)。暫時(shí)我們只要知道,這里是恢復(fù)的入口就行。invokeSuspend內(nèi)部會(huì)把結(jié)果(這個(gè)結(jié)果可能是正常的結(jié)果,也可能是Exception)取出來(lái),開(kāi)啟協(xié)程狀態(tài)機(jī)。

分析完TestContinuation,再來(lái)看一下第一次進(jìn)入getUserName是怎么走的。

  • 首先,第一次進(jìn)入時(shí),continuation肯定不是TestContinuation,因?yàn)榇藭r(shí)還沒(méi)有new過(guò)TestContinuation實(shí)例,所以會(huì)走到創(chuàng)建TestContinuation的邏輯,并且會(huì)把continuation包進(jìn)去。
  • 然后剛創(chuàng)建完的testContinuation的label未賦其他值,那就是初始值0了。那么switch狀態(tài)機(jī)那里,就走case 0,先把label改成1,因?yàn)轳R上就要掛起了,待會(huì)兒恢復(fù)時(shí)需要執(zhí)行下一個(gè)狀態(tài)的代碼。
  • 調(diào)用Kotlin的庫(kù)函數(shù)delay,它是一個(gè)掛起函數(shù),將testContinuation傳入其中,方便它進(jìn)行invokeSuspend回調(diào)。調(diào)用掛起函數(shù),那么它可能會(huì)返回COROUTINE_SUSPENDED,表示它已經(jīng)被掛起了,如果是掛起了那么getUserName就走完了,到時(shí)會(huì)從invokeSuspend恢復(fù)。在還沒(méi)有恢復(fù)的時(shí)候,這個(gè)協(xié)程所在的線程可以去做其他事情。

恢復(fù)的時(shí)候,又開(kāi)始從頭走getUserName,此時(shí)的continuation已經(jīng)是TestContinuation,不會(huì)重新創(chuàng)建。它的label之前已經(jīng)被改成1了的,所以switch狀態(tài)機(jī)那里,會(huì)走到case 1,先檢測(cè)一下有沒(méi)有異常,沒(méi)有異常就返回真正的返回值了“云天明”。

分析到這里也就完了,上面就是一個(gè)非常簡(jiǎn)單的掛起函數(shù)的反編譯分析的整個(gè)過(guò)程。下面我們簡(jiǎn)單分析一下偽掛起函數(shù)會(huì)帶來(lái)什么效果。

四、偽掛起函數(shù)

在之前的CpsTest.kt里面簡(jiǎn)單改一下

suspend fun fakeSuspendFun() = "維德"
suspend fun getUserName(): String {
    println(fakeSuspendFun())
    return "云天明"
}

像fakeSuspendFun這種就是偽掛起函數(shù),平時(shí)不建議像fakeSuspendFun這么寫(xiě),即使寫(xiě)了,Android Studio也會(huì)提示你,這suspend關(guān)鍵字沒(méi)用,內(nèi)部沒(méi)有掛起。它內(nèi)部沒(méi)有掛起的邏輯,但是它有suspend關(guān)鍵字,那么Kotlin編譯器依然會(huì)給它做CPS轉(zhuǎn)換。

public final class CpsTestKt {
   @Nullable
   public static final Object fakeSuspendFun(@NotNull Continuation<? super java.lang.String> $completion) {
      return "維德";
   }
   @Nullable
   public static final Object getUserName(@NotNull Continuation<? super java.lang.String> continuation) {
    final class TestContinuation extends ContinuationImpl {
        int label;
        Object result;
        TestContinuation(Continuation continuation) {
            super(continuation);
        }
        public final Object invokeSuspend(Object obj) {
            this.result = obj;
            this.label |= Integer.MIN_VALUE;
            return CpsTestKt.getUserName(this);
        }
    }
    TestContinuation testContinuation;
    label20:
    {
        if (continuation instanceof TestContinuation) {
            testContinuation = (TestContinuation) continuation;
            if ((testContinuation.label & Integer.MIN_VALUE) != 0) {
                testContinuation.label -= Integer.MIN_VALUE;
                break label20;
            }
        }
        testContinuation = new TestContinuation(continuation);
    }
      Object $result = testContinuation.result;
      Object flag = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      //變化在這里,這個(gè)變量用來(lái)存儲(chǔ)fakeSuspendFun的返回值
      Object var10000;
      switch(testContinuation.label) {
      case 0:
         ResultKt.throwOnFailure($result);
         testContinuation.label = 1;
         var10000 = fakeSuspendFun((Continuation)$continuation);
         if (var10000 == flag) {
            //如果是掛起,那么直接返回COROUTINE_SUSPENDED
            return flag;
         }
         //顯然,這里是不會(huì)掛起的,會(huì)走這里的break
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         var10000 = $result;
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      //走這里
      Object var1 = var10000;
      System.out.println(var1);
      return "云天明";
   }
}

在調(diào)用偽掛起函數(shù)時(shí),不會(huì)掛起,它不會(huì)返回COROUTINE_SUSPENDED,而是繼續(xù)往下走。

五、多個(gè)掛起函數(shù)前后關(guān)聯(lián)

平時(shí)在工作中,可能經(jīng)常會(huì)有多個(gè)掛起函數(shù)前后是關(guān)聯(lián)的,后面一個(gè)掛起函數(shù)需要前面一個(gè)掛起函數(shù)的結(jié)果來(lái)干點(diǎn)事情,比上面只有一個(gè)getUserName掛起函數(shù)稍微復(fù)雜些,我們來(lái)分析一下。

比如我們拿到一個(gè)需求,展示我的朋友圈,假設(shè)獲取流程如下:獲取用戶id->根據(jù)用戶id獲取該用戶的好友列表->獲取好友列表每個(gè)人的朋友圈。下面是非常簡(jiǎn)單的實(shí)現(xiàn):

//需求: 獲取用戶id->根據(jù)用戶id獲取該用戶的好友列表->獲取好友列表每個(gè)人的朋友圈
suspend fun showMoments() {
    println("start")
    val userId = getUserId()
    println(userId)
    val friendList = getFriendList(userId)
    println(friendList)
    val feedList = getFeedList(userId, friendList)
    println(feedList)
}
suspend fun getUserId(): String {
    delay(1000L)
    return "1sa13124daadar2"
}
suspend fun getFriendList(userId: String): String {
    println("正在獲取${userId}的朋友列表")
    delay(1000L)
    return "云天明, 維德"
}
suspend fun getFeedList(userId: String, list: String): String {
    println("獲取${userId}的朋友圈($list)")
    delay(1000L)
    return "云天明: 酒好喝嗎?煙好抽嗎?即使是可口可樂(lè),第一次嘗也不好喝,讓人上癮的東西都是這樣;\n維德: 前進(jìn)!前進(jìn)?。〔粨袷侄蔚厍斑M(jìn)?。?!"
}

它的執(zhí)行結(jié)果如下:

start
1sa13124daadar2
正在獲取1sa13124daadar2的朋友列表
云天明, 維德
獲取1sa13124daadar2的朋友圈(云天明, 維德)
云天明: 酒好喝嗎?煙好抽嗎?即使是可口可樂(lè),第一次嘗也不好喝,讓人上癮的東西都是這樣;
維德: 前進(jìn)!前進(jìn)??!不擇手段地前進(jìn)?。?!
end

這段代碼要稍微復(fù)雜一些,這些掛起函數(shù)前后關(guān)聯(lián),前面獲取到的數(shù)據(jù)后面的掛起函數(shù)需要使用到。相應(yīng)的,它們反編譯之后也要復(fù)雜一些。但是沒(méi)關(guān)系,我已經(jīng)把晦澀難懂的代碼重新組裝了一下,方便大家閱讀。同時(shí),在下面的代碼中,每一步在走哪個(gè)分支,都有詳細(xì)的注釋分析,幫助大家理解。

public final class TestSuspendKt {
   @Nullable
   public static final Object showMoments(@NotNull Continuation<? super Unit> continuation) {
      ShowMomentsContinuation showMomentsContinuation;
      label37: {
         if (continuation instanceof ShowMomentsContinuation) {
            //非第一次進(jìn)showMoments,則走這里,continuation已經(jīng)是ShowMomentsContinuation了
            showMomentsContinuation = (ShowMomentsContinuation)continuation;
            if ((showMomentsContinuation.label & Integer.MIN_VALUE) != 0) {
               showMomentsContinuation.label -= Integer.MIN_VALUE;
               break label37;
            }
         }
         //第一次,走這里,初始化ShowMomentsContinuation,將傳入的continuation包起來(lái)
         showMomentsContinuation = new ShowMomentsContinuation(continuation);
         final class ShowMomentsContinuation extends ContinuationImpl {
            int label;
            Object result;
            //存放臨時(shí)數(shù)據(jù)
            Object tempData;
            ShowMomentsContinuation(Continuation continuation) {
                super(continuation);
            }
            public final Object invokeSuspend(Object obj) {
                this.result = obj;
                this.label |= Integer.MIN_VALUE;
                return CpsTestKt.getUserName(this);
            }
        }
      }
      //存放每個(gè)函數(shù)的返回結(jié)果,臨時(shí)放一下
      Object computeResult;
      label31: {
         String userId;
         Object flag;
         label30: {
            //從continuation中把result取出來(lái)
            Object $result = showMomentsContinuation.result;
            flag = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(showMomentsContinuation.label) {
            case 0:
               //第一次,走這里,檢測(cè)異常
               ResultKt.throwOnFailure($result);
               System.out.println("start");
               //將label改成1
               showMomentsContinuation.label = 1;
               //執(zhí)行g(shù)etUserId函數(shù),computeResult用來(lái)接收返回值
               computeResult = getUserId((Continuation)showMomentsContinuation);
               //getUserId是掛起函數(shù),不出意外的話,computeResult的值會(huì)是COROUTINE_SUSPENDED,這里就直接return了
               //showMoments函數(shù)這一次執(zhí)行,就算完成了。
               //恢復(fù)執(zhí)行時(shí),會(huì)走ShowMomentsContinuation 的invokeSuspend,走下面label等于1的邏輯
               if (computeResult == flag) {
                  return flag;
               }
               break;
            case 1:
               //第二次執(zhí)行showMoments時(shí),label已經(jīng)等于1了,走這里. 
               ResultKt.throwOnFailure($result);
               computeResult = $result;
               break;
            case 2:
               //第三次執(zhí)行showMoments時(shí),label已經(jīng)等于2了,走這里. 
               //先將之前暫存的userId取出來(lái),馬上需要用到
               userId = (String)showMomentsContinuation.tempData;
               ResultKt.throwOnFailure($result);
               computeResult = $result;
               break label30;
            case 3:
               //第四次執(zhí)行showMoments時(shí),label已經(jīng)等于3了,走這里. 
               ResultKt.throwOnFailure($result);
               computeResult = $result;
               break label31;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            //第二次執(zhí)行showMoments時(shí),label=1,會(huì)走到這里來(lái),將getUserId函數(shù)回調(diào)回來(lái)的userId保存起來(lái),并輸出
            userId = (String)computeResult;
            System.out.println(userId);
            //將userId放continuation里面暫存起來(lái)
            showMomentsContinuation.tempData = userId;
            //又要執(zhí)行掛起函數(shù)了,這里將label改成2
            showMomentsContinuation.label = 2;
            //開(kāi)始調(diào)用getFriendList
            computeResult = getFriendList(userId, (Continuation)showMomentsContinuation);
            //getFriendList是掛起函數(shù),不出意外的話,computeResult的值會(huì)是COROUTINE_SUSPENDED,這里就直接return了
            //showMoments函數(shù)這一次執(zhí)行,就算完成了。
            //恢復(fù)執(zhí)行時(shí),會(huì)走ShowMomentsContinuation 的invokeSuspend,走上面label等于2的邏輯
            if (computeResult == flag) {
               return flag;
            }
         }
         //第三次執(zhí)行showMoments時(shí),label=2,會(huì)走到這里來(lái),將getFriendList函數(shù)回調(diào)回來(lái)的friendList輸出
         String friendList = (String)computeResult;
         System.out.println(friendList);
         showMomentsContinuation.tempData = null;
         //又要執(zhí)行掛起函數(shù)了,這里將label改成3
         showMomentsContinuation.label = 3;
         //開(kāi)始調(diào)用getFeedList
         computeResult = getFeedList(userId, friendList, (Continuation)showMomentsContinuation);
         //getFeedList是掛起函數(shù),不出意外的話,computeResult的值會(huì)是COROUTINE_SUSPENDED,這里就直接return了
         //showMoments函數(shù)這一次執(zhí)行,就算完成了。
         //恢復(fù)執(zhí)行時(shí),會(huì)走ShowMomentsContinuation 的invokeSuspend,走上面label等于3的邏輯
         if (computeResult == flag) {
            return flag;
         }
      }
      //第四次執(zhí)行showMoments時(shí),label=3,會(huì)走到這里來(lái),將getFeedList函數(shù)回調(diào)回來(lái)的feedList輸出
      String feedList = (String)computeResult;
      System.out.println(feedList);
      System.out.println("end");
      //showMoments函數(shù)這一次執(zhí)行,就算完成了。
      //沒(méi)有剩下的掛起函數(shù)需要執(zhí)行了
      return Unit.INSTANCE;
   }
    //因?yàn)間etUserId、getFriendList、getFeedList中的匿名內(nèi)部類(lèi)邏輯與showMoments中的一模一樣,故沒(méi)有將其重新組織語(yǔ)言
   @Nullable
   public static final Object getUserId(@NotNull Continuation var0) {
      Object $continuation;
      label20: {
        //這里的<undefinedtype>就是在getUserId函數(shù)里生成的new ContinuationImpl匿名內(nèi)部類(lèi)
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestSuspendKt.getUserId(this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         //第一次執(zhí)行g(shù)etUserId時(shí),走這里
         ResultKt.throwOnFailure($result);
         //馬上要開(kāi)始執(zhí)行掛起函數(shù)了,label先改一下
         ((<undefinedtype>)$continuation).label = 1;
         //執(zhí)行delay,正常情況下,會(huì)返回COROUTINE_SUSPENDED,于是getUserId這一次就執(zhí)行完了,return了
         //恢復(fù)時(shí)會(huì)回調(diào)上面的匿名內(nèi)部類(lèi)$continuation中的invokeSuspend
         if (DelayKt.delay(1000L, (Continuation)$continuation) == var3) {
            return var3;
         }
         break;
      case 1:
         //第二次執(zhí)行g(shù)etUserId時(shí),也就是delay執(zhí)行完回來(lái),走這里
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      //拿到數(shù)據(jù),getUserId就算真正的執(zhí)行完了,接著會(huì)去執(zhí)行showMoments函數(shù)中的ShowMomentsContinuation#invokeSuspend,也就是恢復(fù)showMoments,繼續(xù)執(zhí)行showMoments中g(shù)etUserId后面的邏輯
      return "1sa13124daadar2";
   }
   @Nullable
   public static final Object getFriendList(@NotNull String userId, @NotNull Continuation var1) {
      Object $continuation;
      label20: {
         if (var1 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var1;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         $continuation = new ContinuationImpl(var1) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestSuspendKt.getFriendList((String)null, this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         String var2 = "正在獲取" + userId + "的朋友列表";
         System.out.println(var2);
         ((<undefinedtype>)$continuation).label = 1;
         if (DelayKt.delay(1000L, (Continuation)$continuation) == var5) {
            return var5;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "云天明, 維德";
   }
   @Nullable
   public static final Object getFeedList(@NotNull String userId, @NotNull String list, @NotNull Continuation var2) {
      Object $continuation;
      label20: {
         if (var2 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var2;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         $continuation = new ContinuationImpl(var2) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestSuspendKt.getFeedList((String)null, (String)null, this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         String var3 = "獲取" + userId + "的朋友圈(" + list + ')';
         System.out.println(var3);
         ((<undefinedtype>)$continuation).label = 1;
         if (DelayKt.delay(1000L, (Continuation)$continuation) == var6) {
            return var6;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "云天明: 酒好喝嗎?煙好抽嗎?即使是可口可樂(lè),第一次嘗也不好喝,讓人上癮的東西都是這樣;\n維德: 前進(jìn)!前進(jìn)!!不擇手段地前進(jìn)?。?!";
   }
}

觀察源碼,發(fā)現(xiàn)一些東西:

  • 每個(gè)掛起函數(shù)都有一個(gè)匿名內(nèi)部類(lèi),繼承ContinuationImpl,在invokeSuspend中開(kāi)啟狀態(tài)機(jī)
  • 每個(gè)掛起函數(shù)都經(jīng)過(guò)了CPS轉(zhuǎn)換
  • 在掛起之后,當(dāng)前執(zhí)行協(xié)程的這個(gè)線程其實(shí)是空閑的,沒(méi)有代碼交給它執(zhí)行。在invokeSuspend恢復(fù)之后,才繼續(xù)執(zhí)行
  • 每個(gè)掛起函數(shù),都有一個(gè)狀態(tài)機(jī)
  • 掛起函數(shù)中的邏輯被分塊執(zhí)行(也就是狀態(tài)機(jī)那塊的邏輯),分塊的數(shù)量=掛起函數(shù)數(shù)量+1

基本上來(lái)說(shuō),掛起函數(shù)的實(shí)現(xiàn)原理就是上面這些了。

六、在Java中調(diào)用suspend函數(shù)

既然Kotlin是兼容Java的,那么如果我想在Java里面調(diào)用Kotlin的suspend函數(shù)按道理也是可以的。那具體如何調(diào)用呢?

就拿上面的案例舉例,假設(shè)我想在Activity中點(diǎn)擊某個(gè)按鈕時(shí)調(diào)用showMoments這個(gè)suspend函數(shù),該怎么搞?大家先思考一下,稍后給出答案。

//將上面的案例加了個(gè)返回值
suspend fun showMoments(): String {
    println("start")
    val userId = getUserId()
    println(userId)
    val friendList = getFriendList(userId)
    println(friendList)
    val feedList = getFeedList(userId, friendList)
    println(feedList)
    println("end")
    return feedList
}

因?yàn)閟howMoments函數(shù)有suspend關(guān)鍵字,那么最終會(huì)經(jīng)過(guò)CPS轉(zhuǎn)換,有一個(gè)Continuation參數(shù)。在Java中調(diào)用showMoments時(shí),肯定需要把Continuation傳進(jìn)去才行。Continuation是一個(gè)接口,需要傳個(gè)實(shí)現(xiàn)類(lèi)過(guò)去,把getContext和resumeWith實(shí)現(xiàn)起。

TestSuspendKt.showMoments(new Continuation<String>() {
    @NonNull
    @Override
    public CoroutineContext getContext() {
        return (CoroutineContext) Dispatchers.getIO();
    }
    @Override
    public void resumeWith(@NonNull Object result) {
        //這里的result就是showMoments的返回值
        Log.d("xfhy666", "" + result);
    }
});

Java中調(diào)用掛起函數(shù),看起來(lái)就像是調(diào)用了一個(gè)方法,這個(gè)方法需要傳一個(gè)callback過(guò)去,這個(gè)方法的返回值是通過(guò)回調(diào)給出來(lái)的,并且可以自定義該方法運(yùn)行在哪個(gè)線程中。

七、總結(jié)

好了,今天的Kotlin掛起函數(shù)就分析到這里,基本上謎團(tuán)已全部解開(kāi)(除了invokeSuspend是在什么時(shí)候回調(diào)的,后面有機(jī)會(huì)再和大家分享)。

Kotlin的掛起函數(shù),本質(zhì)上就是:CPS+狀態(tài)機(jī)。

  • CPS:掛起函數(shù)比普通函數(shù)多了suspend關(guān)鍵字,Kotlin編譯器會(huì)對(duì)其特殊處理。將該函數(shù)轉(zhuǎn)換成一個(gè)帶有Callback的函數(shù),Callback就是Continuation接口,它的泛型就是原來(lái)函數(shù)的返回值類(lèi)型。轉(zhuǎn)換之后的返回值類(lèi)型是Any?,因?yàn)榧恿藄uspend關(guān)鍵字的不一定會(huì)被掛起,掛起的話返回Intrinsics.COROUTINE_SUSPENDED,偽掛起函數(shù)(里面沒(méi)有其他掛起函數(shù),但還是會(huì)進(jìn)行CPS轉(zhuǎn)換)則是直接返回結(jié)果,這個(gè)結(jié)果可以是任何類(lèi)型,所以返回值只能是Any?
  • 狀態(tài)機(jī):當(dāng)掛起函數(shù)經(jīng)過(guò)編譯之后,會(huì)變成switch和label組成的狀態(tài)機(jī)結(jié)構(gòu)。label代表了當(dāng)前狀態(tài)機(jī)的具體狀態(tài),每改變一次,就代表掛起函數(shù)被調(diào)用一次。在里面會(huì)創(chuàng)建一個(gè)Callback接口,當(dāng)掛起之后,掛起函數(shù)的結(jié)果返回是通過(guò)Callback回調(diào)回來(lái)的,回調(diào)回來(lái)之后,因?yàn)橹靶薷倪^(guò)label,根據(jù)該label來(lái)判斷該繼續(xù)往下走了,執(zhí)行后面的邏輯。上面的Callback就是Continuation,我覺(jué)得它在這里的意思可以翻譯成繼續(xù)執(zhí)行剩余的代碼。

以上就是Kotlin掛起函數(shù)原理示例剖析的詳細(xì)內(nèi)容,更多關(guān)于Kotlin掛起函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android實(shí)現(xiàn)手指觸控圖片縮放功能

    Android實(shí)現(xiàn)手指觸控圖片縮放功能

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)手指觸控圖片縮放功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android自定義viewgroup可滾動(dòng)布局 GestureDetector手勢(shì)監(jiān)聽(tīng)(5)

    Android自定義viewgroup可滾動(dòng)布局 GestureDetector手勢(shì)監(jiān)聽(tīng)(5)

    這篇文章主要為大家詳細(xì)介紹了Android自定義viewgroup可滾動(dòng)布局,GestureDetector手勢(shì)監(jiān)聽(tīng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • Android Studio實(shí)現(xiàn)簡(jiǎn)單的QQ登錄界面的示例代碼

    Android Studio實(shí)現(xiàn)簡(jiǎn)單的QQ登錄界面的示例代碼

    這篇文章主要介紹了Android Studio實(shí)現(xiàn)簡(jiǎn)單的QQ登錄界面的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Android工程:引用另一個(gè)Android工程的方法詳解

    Android工程:引用另一個(gè)Android工程的方法詳解

    本篇文章是對(duì)在Android中引用另一個(gè)Android工程的方法進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下
    2013-05-05
  • 淺談Android插件化

    淺談Android插件化

    插件化技術(shù)最初源于免安裝運(yùn)行 Apk的想法,這個(gè)免安裝的 Apk 就可以理解為插件,而支持插件的 app 我們一般叫 宿主,下面就跟著小編一起學(xué)習(xí)Android插件化吧,希望能幫助到你
    2021-09-09
  • 基于AnDroid FrameLayout的使用詳解

    基于AnDroid FrameLayout的使用詳解

    本篇文章是對(duì)AnDroid FrameLayout的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的方法匯總

    Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的方法匯總

    這篇文章主要介紹了Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的方法匯總,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-06-06
  • Android  Surfaceview的繪制與應(yīng)用

    Android Surfaceview的繪制與應(yīng)用

    這篇文章主要介紹了Android Surfaceview的繪制與應(yīng)用的相關(guān)資料,需要的朋友可以參考下
    2017-07-07
  • Android逆向入門(mén)之常見(jiàn)Davlik字節(jié)碼解析

    Android逆向入門(mén)之常見(jiàn)Davlik字節(jié)碼解析

    Dalvik是Google公司自己設(shè)計(jì)用于Android平臺(tái)的虛擬機(jī)。Dalvik虛擬機(jī)是Google等廠商合作開(kāi)發(fā)的Android移動(dòng)設(shè)備平臺(tái)的核心組成部分之一,本篇文章我們來(lái)詳細(xì)解釋常見(jiàn)Davlik字節(jié)碼
    2021-11-11
  • Android實(shí)現(xiàn)帶有刪除按鈕的EditText示例代碼

    Android實(shí)現(xiàn)帶有刪除按鈕的EditText示例代碼

    本文給大家介紹一個(gè)很實(shí)用的小控件,就是在Android系統(tǒng)的輸入框右邊加入一個(gè)小圖標(biāo),點(diǎn)擊小圖標(biāo)可以清除輸入框里面的內(nèi)容,IOS上面直接設(shè)置某個(gè)屬性就可以實(shí)現(xiàn)這一功能,但是Android原生EditText不具備此功能,所以要想實(shí)現(xiàn)這一功能我們需要重寫(xiě)EditText。下面來(lái)看看吧。
    2016-12-12

最新評(píng)論