Kotlin掛起函數(shù)應(yīng)用介紹
學(xué)習(xí)了極客時間課程,記錄下學(xué)習(xí)輸出。
一、CPS轉(zhuǎn)換
掛起函數(shù),比普通的函數(shù)多了 suspend 關(guān)鍵字。通過suspend 關(guān)鍵字,Kotlin 編譯器就會特殊對待這個函數(shù),將其轉(zhuǎn)換成一個帶有 Callback 的函數(shù),這里的 Callback 就是 Continuation 接口。
例、CPS 轉(zhuǎn)換:
suspend fun getUserInfo(): Any { return "UserInfo" } -----> fun getUserInfo(ct:Continuation): Any? { ct.resumeWith("UserInfo") return Unit }
PS 轉(zhuǎn)換過程中,函數(shù)的類型發(fā)生了變化:suspend ()->Any 變成了 (Continuation)-> Any?。這意味著,如果你在 Java 里訪問一個 Kotlin 掛起函數(shù) getUserInfo(),會看到 getUserInfo() 的類型是 (Continuation)-> Object,接收 Continuation 為參數(shù),返回值是 Object。而在這里,函數(shù)簽名的變化可以分為兩個部分:函數(shù)簽名的變化可以分為兩個部分:函數(shù)參數(shù)的變化和函數(shù)返回值的變化。
1.CPS 參數(shù)變化
suspend() 變成 (Continuation)
suspend fun getUserInfoContent(): String { withContext(Dispatchers.IO) { delay(1000L) } return "UserInfo" } suspend fun getFriendListContent(user: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "Friend1, Friend2" } suspend fun getFeedListContent(user: String, list: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "{FeddList...}" } suspend fun fetchContent() { val userInfoContent = getUserInfoContent() val friendListContent = getFriendListContent(userInfoContent) val feedListContent = getFeedListContent(userInfoContent, friendListContent) }
上述代碼轉(zhuǎn)換成java代碼如下:
public final class TestCoroutionKt { @Nullable public static final Object getUserInfoContent(@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 TestCoroutionKt.getUserInfoContent(this); } }; } Object $result = ((<undefinedtype>)$continuation).result; Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(((<undefinedtype>)$continuation).label) { case 0: ResultKt.throwOnFailure($result); CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO(); Function2 var10001 = (Function2)(new Function2((Continuation)null) { int label; @Nullable public final Object invokeSuspend(@NotNull Object $result) { Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(this.label) { case 0: ResultKt.throwOnFailure($result); this.label = 1; if (DelayKt.delay(1000L, this) == var2) { return var2; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } return Unit.INSTANCE; } @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); } }); ((<undefinedtype>)$continuation).label = 1; if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var3) { return var3; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } return "UserInfo"; } @Nullable public static final Object getFriendListContent(@NotNull String var0, @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 TestCoroutionKt.getFriendListContent((String)null, this); } }; } Object $result = ((<undefinedtype>)$continuation).result; Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(((<undefinedtype>)$continuation).label) { case 0: ResultKt.throwOnFailure($result); CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO(); Function2 var10001 = (Function2)(new Function2((Continuation)null) { int label; @Nullable public final Object invokeSuspend(@NotNull Object $result) { Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(this.label) { case 0: ResultKt.throwOnFailure($result); this.label = 1; if (DelayKt.delay(1000L, this) == var2) { return var2; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } return Unit.INSTANCE; } @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); } }); ((<undefinedtype>)$continuation).label = 1; if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var4) { return var4; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } return "Friend1, Friend2"; } @Nullable public static final Object getFeedListContent(@NotNull String var0, @NotNull String var1, @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 TestCoroutionKt.getFeedListContent((String)null, (String)null, this); } }; } Object $result = ((<undefinedtype>)$continuation).result; Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(((<undefinedtype>)$continuation).label) { case 0: ResultKt.throwOnFailure($result); CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO(); Function2 var10001 = (Function2)(new Function2((Continuation)null) { int label; @Nullable public final Object invokeSuspend(@NotNull Object $result) { Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(this.label) { case 0: ResultKt.throwOnFailure($result); this.label = 1; if (DelayKt.delay(1000L, this) == var2) { return var2; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } return Unit.INSTANCE; } @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); } }); ((<undefinedtype>)$continuation).label = 1; if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var5) { return var5; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } return "{FeddList...}"; } @Nullable public static final Object fetchContent(@NotNull Continuation var0) { Object $continuation; label37: { if (var0 instanceof <undefinedtype>) { $continuation = (<undefinedtype>)var0; if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) { ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE; break label37; } } $continuation = new ContinuationImpl(var0) { // $FF: synthetic field Object result; int label; Object L$0; @Nullable public final Object invokeSuspend(@NotNull Object $result) { this.result = $result; this.label |= Integer.MIN_VALUE; return TestCoroutionKt.fetchContent(this); } }; } Object var10000; label31: { String userInfoContent; Object var6; label30: { Object $result = ((<undefinedtype>)$continuation).result; var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(((<undefinedtype>)$continuation).label) { case 0: ResultKt.throwOnFailure($result); ((<undefinedtype>)$continuation).label = 1; var10000 = getUserInfoContent((Continuation)$continuation); if (var10000 == var6) { return var6; } break; case 1: ResultKt.throwOnFailure($result); var10000 = $result; break; case 2: userInfoContent = (String)((<undefinedtype>)$continuation).L$0; ResultKt.throwOnFailure($result); var10000 = $result; break label30; case 3: ResultKt.throwOnFailure($result); var10000 = $result; break label31; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } userInfoContent = (String)var10000; ((<undefinedtype>)$continuation).L$0 = userInfoContent; ((<undefinedtype>)$continuation).label = 2; var10000 = getFriendListContent(userInfoContent, (Continuation)$continuation); if (var10000 == var6) { return var6; } } String friendListContent = (String)var10000; ((<undefinedtype>)$continuation).L$0 = null; ((<undefinedtype>)$continuation).label = 3; var10000 = getFeedListContent(userInfoContent, friendListContent, (Continuation)$continuation); if (var10000 == var6) { return var6; } } String var3 = (String)var10000; return Unit.INSTANCE; } }
每一次函數(shù)調(diào)用的時候,continuation 都會作為最后一個參數(shù)傳到掛起函數(shù)里,Kotlin 編譯器幫我們做的,我們開發(fā)者是無感知。
2.CPS 返回值變化
final Object getUserInfoContent(@NotNull Continuation var0) final Object getFriendListContent(@NotNull String var0, @NotNull Continuation var1) final Object getFeedListContent(@NotNull String var0, @NotNull String var1, @NotNull Continuation var2)
suspend fun getUserInfoContent(): String {} fun getUserInfoContent(cont: Continuation): Any? {}
經(jīng)過 CPS 轉(zhuǎn)換后,完整的函數(shù)簽名如下:
suspend fun getUserInfoContent(): String {} fun getUserInfoContent(cont: Continuation<String>): Any? {}
Kotlin 編譯器的 CPS 轉(zhuǎn)換是等價的轉(zhuǎn)換。suspend () -> String 轉(zhuǎn)換成 (Continuation) -> Any?。
掛起函數(shù)經(jīng)過 CPS 轉(zhuǎn)換后,它的返回值有一個重要作用:標(biāo)志該掛起函數(shù)有沒有被掛起。
其實掛起函數(shù)也能不被掛起。
首先只要有 suspend 修飾的函數(shù),它就是掛起函數(shù)。
suspend fun getUserInfoContent(): String { withContext(Dispatchers.IO) { delay(1000L) } return "UserInfo" }
執(zhí)行到 withContext{} 的時候,就會返回 CoroutineSingletons.COROUTINE_SUSPENDED 表示函數(shù)被掛起了。
下面的函數(shù)則是偽掛起函數(shù)
suspend fun getUserInfoContent2(): String { return "UserInfo" }
因為它的方法體跟普通函數(shù)一樣。它跟一般的掛起函數(shù)有個區(qū)別:在執(zhí)行的時候,它并不會被掛起,因為它就是個普通函數(shù)。
二、掛起函數(shù)的反編譯
@Nullable public static final Object fetchContent(@NotNull Continuation var0) { Object $continuation; label37: { if (var0 instanceof <undefinedtype>) { $continuation = (<undefinedtype>)var0; if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) { ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE; break label37; } } $continuation = new ContinuationImpl(var0) { // $FF: synthetic field Object result; int label; Object L$0; @Nullable public final Object invokeSuspend(@NotNull Object $result) { this.result = $result; this.label |= Integer.MIN_VALUE; return TestCoroutionKt.fetchContent(this); } }; } Object var10000; label31: { String userInfoContent; Object var6; label30: { Object $result = ((<undefinedtype>)$continuation).result; var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(((<undefinedtype>)$continuation).label) { case 0: ResultKt.throwOnFailure($result); ((<undefinedtype>)$continuation).label = 1; var10000 = getUserInfoContent((Continuation)$continuation); if (var10000 == var6) { return var6; } break; case 1: ResultKt.throwOnFailure($result); var10000 = $result; break; case 2: userInfoContent = (String)((<undefinedtype>)$continuation).L$0; ResultKt.throwOnFailure($result); var10000 = $result; break label30; case 3: ResultKt.throwOnFailure($result); var10000 = $result; break label31; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } userInfoContent = (String)var10000; ((<undefinedtype>)$continuation).L$0 = userInfoContent; ((<undefinedtype>)$continuation).label = 2; var10000 = getFriendListContent(userInfoContent, (Continuation)$continuation); if (var10000 == var6) { return var6; } } String friendListContent = (String)var10000; ((<undefinedtype>)$continuation).L$0 = null; ((<undefinedtype>)$continuation).label = 3; var10000 = getFeedListContent(userInfoContent, friendListContent, (Continuation)$continuation); if (var10000 == var6) { return var6; } } String var3 = (String)var10000; return Unit.INSTANCE; }
label 是用來代表協(xié)程狀態(tài)機(jī)當(dāng)中狀態(tài);
result 是用來存儲當(dāng)前掛起函數(shù)執(zhí)行結(jié)果;
invokeSuspend 這個函數(shù),是整個狀態(tài)機(jī)的入口,它會將執(zhí)行流程轉(zhuǎn)交給 fetchContent 進(jìn)行再次調(diào)用;
userInfoContent, friendListContent用來存儲歷史掛起函數(shù)執(zhí)行結(jié)果。
if (var0 instanceof <undefinedtype>) { $continuation = (<undefinedtype>)var0; if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) { ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE; break label37; } }
$continuation = new ContinuationImpl(var0) { // $FF: synthetic field Object result; int label; Object L$0; @Nullable public final Object invokeSuspend(@NotNull Object $result) { this.result = $result; this.label |= Integer.MIN_VALUE; return TestCoroutionKt.fetchContent(this); } };
invokeSuspend 最終會調(diào)用 fetchContent;
如果是初次運行,會創(chuàng)建一個 ContinuationImpl對象,completion 作為參數(shù);這相當(dāng)于用一個新的 Continuation 包裝了舊的 Continuation;
如果不是初次運行,直接將 completion 賦值給 continuation;這說明 continuation 在整個運行期間,只會產(chǎn)生一個實例,這能極大地節(jié)省內(nèi)存開銷(對比 CallBack)。
// result 接收協(xié)程的運行結(jié)果 var result = continuation.result // suspendReturn 接收掛起函數(shù)的返回值 var suspendReturn: Any? = null // CoroutineSingletons 是個枚舉類 // COROUTINE_SUSPENDED 代表當(dāng)前函數(shù)被掛起了 val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED
continuation.label 是狀態(tài)流轉(zhuǎn)的關(guān)鍵,continuation.label 改變一次,就代表了掛起函數(shù)被調(diào)用了一次;每次掛起函數(shù)執(zhí)行完后,都會檢查是否發(fā)生異常;
fetchContent 里的原本的代碼,被拆分到狀態(tài)機(jī)里各個狀態(tài)中,分開執(zhí)行;getUserInfoContent(continuation)、getFriendListContent(user, continuation)、getFeedListContent(friendList, continuation) 三個函數(shù)調(diào)用的是同一個 continuation 實例;
var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
如果一個函數(shù)被掛起了,它的返回值會是 CoroutineSingletons.COROUTINE_SUSPENDED;
在掛起函數(shù)執(zhí)行的過程中,狀態(tài)機(jī)會把之前的結(jié)果以成員變量的方式保存在 continuation 中。
本質(zhì)上來說,Kotlin 協(xié)程就是通過 label 代碼段嵌套,配合 switch 巧妙構(gòu)造出一個狀態(tài)機(jī)結(jié)構(gòu)。
三、Continuation
public interface Continuation<in T> { public val context: CoroutineContext public fun resumeWith(result: Result<T>) } @Suppress("WRONG_MODIFIER_TARGET") public suspend inline val coroutineContext: CoroutineContext get() { throw NotImplementedError("Implemented as intrinsic") }
注意上面的suspend inline val coroutineContext,suspend 的這種用法只是一種特殊用法。它的作用:它是一個只有在掛起函數(shù)作用域下,才能訪問的頂層的不可變的變量。這里的 inline,意味著它的具體實現(xiàn)會被直接復(fù)制到代碼的調(diào)用處。
suspend fun testContext() = coroutineContext
@Nullable public static final Object testContext(@NotNull Continuation $completion) { return $completion.getContext(); }
“suspend inline val coroutineContext”,本質(zhì)上就是 Kotlin 官方提供的一種方便開發(fā)者在掛起函數(shù)當(dāng)中,獲取協(xié)程上下文的手段。它的具體實現(xiàn),其實是 Kotlin 編譯器來完成的。
我們在掛起函數(shù)當(dāng)中無法直接訪問 Continuation 對象,但可以訪問到 Continuation 當(dāng)中的 coroutineContext。要知道,正常情況下,我們想要訪問 Continuation.coroutineContext,首先是要拿到 Continuation 對象的。但是,Kotlin 官方通過“suspend inline val coroutineContext”這個頂層變量,讓我們開發(fā)者能直接拿到 coroutineContext,卻對 Continuation 毫無感知。
掛起函數(shù)與 CoroutineContext 確實有著緊密的聯(lián)系。每個掛起函數(shù)當(dāng)中都會有 Continuation,而每個 Continuation 當(dāng)中都會有 coroutineContext。并且,我們在掛起函數(shù)當(dāng)中,就可以直接訪問當(dāng)前的 coroutineContext。
到此這篇關(guān)于Kotlin掛起函數(shù)應(yīng)用介紹的文章就介紹到這了,更多相關(guān)Kotlin掛起函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android利用ScaleTransition實現(xiàn)吹氣球動畫
這篇文章主要為大家介紹了如何將利用ScaleTransition實現(xiàn)一個吹氣球的動畫,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2022-04-04

Android拖拽助手ViewDragHelper的創(chuàng)建與使用實例