Kotlin掛起函數(shù)應(yīng)用介紹
學(xué)習(xí)了極客時(shí)間課程,記錄下學(xué)習(xí)輸出。
一、CPS轉(zhuǎn)換
掛起函數(shù),比普通的函數(shù)多了 suspend 關(guān)鍵字。通過suspend 關(guān)鍵字,Kotlin 編譯器就會(huì)特殊對(duì)待這個(gè)函數(shù),將其轉(zhuǎn)換成一個(gè)帶有 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 里訪問一個(gè) Kotlin 掛起函數(shù) getUserInfo(),會(huì)看到 getUserInfo() 的類型是 (Continuation)-> Object,接收 Continuation 為參數(shù),返回值是 Object。而在這里,函數(shù)簽名的變化可以分為兩個(gè)部分:函數(shù)簽名的變化可以分為兩個(gè)部分:函數(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)用的時(shí)候,continuation 都會(huì)作為最后一個(gè)參數(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)換是等價(jià)的轉(zhuǎn)換。suspend () -> String 轉(zhuǎn)換成 (Continuation) -> Any?。
掛起函數(shù)經(jīng)過 CPS 轉(zhuǎn)換后,它的返回值有一個(gè)重要作用:標(biāo)志該掛起函數(shù)有沒有被掛起。
其實(shí)掛起函數(shù)也能不被掛起。
首先只要有 suspend 修飾的函數(shù),它就是掛起函數(shù)。
suspend fun getUserInfoContent(): String {
withContext(Dispatchers.IO) {
delay(1000L)
}
return "UserInfo"
}執(zhí)行到 withContext{} 的時(shí)候,就會(huì)返回 CoroutineSingletons.COROUTINE_SUSPENDED 表示函數(shù)被掛起了。
下面的函數(shù)則是偽掛起函數(shù)
suspend fun getUserInfoContent2(): String {
return "UserInfo"
}因?yàn)樗姆椒w跟普通函數(shù)一樣。它跟一般的掛起函數(shù)有個(gè)區(qū)別:在執(zhí)行的時(shí)候,它并不會(huì)被掛起,因?yàn)樗褪莻€(gè)普通函數(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 是用來存儲(chǔ)當(dāng)前掛起函數(shù)執(zhí)行結(jié)果;
invokeSuspend 這個(gè)函數(shù),是整個(gè)狀態(tài)機(jī)的入口,它會(huì)將執(zhí)行流程轉(zhuǎn)交給 fetchContent 進(jìn)行再次調(diào)用;
userInfoContent, friendListContent用來存儲(chǔ)歷史掛起函數(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 最終會(huì)調(diào)用 fetchContent;
如果是初次運(yùn)行,會(huì)創(chuàng)建一個(gè) ContinuationImpl對(duì)象,completion 作為參數(shù);這相當(dāng)于用一個(gè)新的 Continuation 包裝了舊的 Continuation;
如果不是初次運(yùn)行,直接將 completion 賦值給 continuation;這說明 continuation 在整個(gè)運(yùn)行期間,只會(huì)產(chǎn)生一個(gè)實(shí)例,這能極大地節(jié)省內(nèi)存開銷(對(duì)比 CallBack)。
// result 接收協(xié)程的運(yùn)行結(jié)果 var result = continuation.result // suspendReturn 接收掛起函數(shù)的返回值 var suspendReturn: Any? = null // CoroutineSingletons 是個(gè)枚舉類 // 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í)行完后,都會(huì)檢查是否發(fā)生異常;
fetchContent 里的原本的代碼,被拆分到狀態(tài)機(jī)里各個(gè)狀態(tài)中,分開執(zhí)行;getUserInfoContent(continuation)、getFriendListContent(user, continuation)、getFeedListContent(friendList, continuation) 三個(gè)函數(shù)調(diào)用的是同一個(gè) continuation 實(shí)例;
var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
如果一個(gè)函數(shù)被掛起了,它的返回值會(huì)是 CoroutineSingletons.COROUTINE_SUSPENDED;
在掛起函數(shù)執(zhí)行的過程中,狀態(tài)機(jī)會(huì)把之前的結(jié)果以成員變量的方式保存在 continuation 中。
本質(zhì)上來說,Kotlin 協(xié)程就是通過 label 代碼段嵌套,配合 switch 巧妙構(gòu)造出一個(gè)狀態(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 的這種用法只是一種特殊用法。它的作用:它是一個(gè)只有在掛起函數(shù)作用域下,才能訪問的頂層的不可變的變量。這里的 inline,意味著它的具體實(shí)現(xiàn)會(huì)被直接復(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é)程上下文的手段。它的具體實(shí)現(xiàn),其實(shí)是 Kotlin 編譯器來完成的。
我們?cè)趻炱鸷瘮?shù)當(dāng)中無法直接訪問 Continuation 對(duì)象,但可以訪問到 Continuation 當(dāng)中的 coroutineContext。要知道,正常情況下,我們想要訪問 Continuation.coroutineContext,首先是要拿到 Continuation 對(duì)象的。但是,Kotlin 官方通過“suspend inline val coroutineContext”這個(gè)頂層變量,讓我們開發(fā)者能直接拿到 coroutineContext,卻對(duì) Continuation 毫無感知。
掛起函數(shù)與 CoroutineContext 確實(shí)有著緊密的聯(lián)系。每個(gè)掛起函數(shù)當(dāng)中都會(huì)有 Continuation,而每個(gè) Continuation 當(dāng)中都會(huì)有 coroutineContext。并且,我們?cè)趻炱鸷瘮?shù)當(dāng)中,就可以直接訪問當(dāng)前的 coroutineContext。
到此這篇關(guān)于Kotlin掛起函數(shù)應(yīng)用介紹的文章就介紹到這了,更多相關(guān)Kotlin掛起函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)選項(xiàng)菜單子菜單
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)選項(xiàng)菜單子菜單,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
Android實(shí)現(xiàn)View滑動(dòng)的6種方式
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)View滑動(dòng)的6種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Android利用ScaleTransition實(shí)現(xiàn)吹氣球動(dòng)畫
這篇文章主要為大家介紹了如何將利用ScaleTransition實(shí)現(xiàn)一個(gè)吹氣球的動(dòng)畫,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2022-04-04
android listview進(jìn)階實(shí)例分享
這篇文章主要介紹了android listview進(jìn)階實(shí)例分享,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
Android復(fù)選框?qū)υ捒蛴梅▽?shí)例簡(jiǎn)析
這篇文章主要介紹了Android復(fù)選框?qū)υ捒蛴梅?結(jié)合實(shí)例形式簡(jiǎn)單分析了Android復(fù)選對(duì)話框的創(chuàng)建與使用技巧,需要的朋友可以參考下2016-01-01
Android拖拽助手ViewDragHelper的創(chuàng)建與使用實(shí)例
ViewDragHelper是針對(duì) ViewGroup 中的拖拽和重新定位 views 操作時(shí)提供了一系列非常有用的方法和狀態(tài)追蹤,下面這篇文章主要給大家介紹了關(guān)于Android拖拽助手ViewDragHelper的創(chuàng)建與使用的相關(guān)資料,需要的朋友可以參考下2022-05-05

