Suspend函數(shù)與回調(diào)的互相轉(zhuǎn)換示例詳解
前言
我們再來一期關(guān)于kotlin協(xié)程的故事,我們都知道在Coroutine沒有出來之前,我們對于異步結(jié)果的處理都是采用回調(diào)的方式進(jìn)行,一方面回調(diào)層次過多的話,容易導(dǎo)致“回調(diào)地獄”,另一方法也比較難以維護(hù)。當(dāng)然,我們并不是否定了回調(diào)本身,回調(diào)本身同時(shí)也是具備很多優(yōu)點(diǎn)的,比如符合代碼閱讀邏輯,同時(shí)回調(diào)本身也是比較可控的。這一期呢,我們就是來聊一下,如何把回調(diào)的寫法變成suspend函數(shù),同時(shí)如何把suspend函數(shù)變成回調(diào),從而讓我們更加了解kotlin協(xié)程背后的故事
回調(diào)變成suspend函數(shù)
來一個(gè)回調(diào)
我們以一個(gè)回調(diào)函數(shù)作為例子,當(dāng)我們normalCallBack在一個(gè)子線程中做一些處理,比如耗時(shí)函數(shù),做完就會(huì)通過MyCallBack回調(diào)onCallBack,這里返回了一個(gè)Int類型,如下:
var myCallBack:MyCallBack?= null interface MyCallBack{ fun onCallBack(result: Int) } fun normalCallBack(){ thread { // 比如做一些事情 myCallBack?.onCallBack(1) } }
轉(zhuǎn)化為suspend函數(shù)
此時(shí)我們可以通過suspendCoroutine函數(shù),內(nèi)部其實(shí)是通過創(chuàng)建了一個(gè)SafeContinuation并放到了我們suspend函數(shù)本身(block本身)啟動(dòng)了一個(gè)協(xié)程,我們之前在聊一聊Kotlin協(xié)程"低級"api 這篇文章介紹過
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> -> val safe = SafeContinuation(c.intercepted()) block(safe) safe.getOrThrow() } }
這時(shí)候我們就可以直接寫為,從而將回調(diào)消除,變成了一個(gè)suspend函數(shù)。
suspend fun mySuspend() = suspendCoroutine<Int> { thread { // 比如做一些事情 it.resume(1) } }
當(dāng)然,如果我們想要支持一下外部取消,比如當(dāng)前頁面銷毀時(shí),發(fā)起的網(wǎng)絡(luò)請求自然也就不需要再請求了,就可以通過suspendCancellableCoroutine創(chuàng)建,里面的Continuation對象就從SafeContinuation(見上文)變成了CancellableContinuation,變成了CancellableContinuation有一個(gè)invokeOnCancellation方便,支持在協(xié)程體被銷毀時(shí)的邏輯。
public suspend inline fun <T> suspendCancellableCoroutine( crossinline block: (CancellableContinuation<T>) -> Unit ): T = suspendCoroutineUninterceptedOrReturn { uCont -> val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE) /* * For non-atomic cancellation we setup parent-child relationship immediately * in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but * properly supports cancellation. */ cancellable.initCancellability() block(cancellable) cancellable.getResult() }
此時(shí)我們就可以寫出以下代碼
suspend fun mySuspend2() = suspendCancellableCoroutine<Int> { thread { // 比如做一些事情 it.resume(1) } it.invokeOnCancellation { // 取消邏輯 } }
suspend函數(shù)變成回調(diào)
見到了回調(diào)如何變成suspend函數(shù),那么我們反過來呢?有沒有辦法?當(dāng)然有啦!當(dāng)時(shí)suspend函數(shù)中有很多種區(qū)分,我們一一區(qū)分一下
//直接返回的suspend函數(shù) suspend fun myNoSuspendFunc():Int{ return 1 }
//調(diào)用suspendCoroutine后直接resume的suspend函數(shù) suspend fun myNoSuspendFunc() = suspendCoroutine<Int> { continuation -> continuation.resume(1) }
//調(diào)用suspendCoroutine后異步執(zhí)行的suspend函數(shù)(這里異步可以是單線程也可以是多線程,跟線程本身無關(guān),只要是異步就會(huì)觸發(fā)掛起) suspend fun myRealSuspendFunc() = suspendCoroutine<Int> { thread { Thread.sleep(300) it.resume(2) }
那么我們來想一下,這里真正發(fā)起掛起的函數(shù)是哪個(gè)?通過代碼其實(shí)我們可以猜到,真正掛起的函數(shù)只有最后一個(gè)myRealSuspendFunc,其他都不是真正的掛起,這里的掛起是什么意思呢?我們從協(xié)程的狀態(tài)就可以知道,當(dāng)前處于CoroutineSingletons.COROUTINE_SUSPENDED時(shí),就是掛起狀態(tài)。我們回歸一下,一個(gè)suspend函數(shù)有哪幾種情況
這里的1,2,3就分別對應(yīng)著上文demo中的例子
- 直接返回結(jié)果,不需要進(jìn)入狀態(tài)機(jī)判斷,因?yàn)楸旧砭蜎]有啟動(dòng)協(xié)程
- 進(jìn)入了協(xié)程,但是不需要進(jìn)行SUSPEND狀態(tài)就已經(jīng)有了結(jié)果,所以直接返回了結(jié)果
- 進(jìn)入了SUSPEND狀態(tài),之后才能獲取結(jié)果
這里我們就不貼出來源碼了,感興趣可自己看Coroutine的實(shí)現(xiàn),這里我們要明確一個(gè)概念,一個(gè)Suspend函數(shù)的運(yùn)行機(jī)制,其實(shí)并不依靠了協(xié)程本身。
對應(yīng)代碼表現(xiàn)就是,這個(gè)函數(shù)的返回結(jié)果可能就是直接返回結(jié)果本身,另一種就是通過回調(diào)本身通知外部(這里我們還會(huì)以例子說明)
suspend函數(shù)轉(zhuǎn)換為回調(diào)
這里有兩種情況,我們分別以kotlin代碼跟java代碼表示:
kotlin代碼
由于kotlin可以直接通過suspend的擴(kuò)展函數(shù)startCoroutine啟動(dòng)一個(gè)協(xié)程,
fun myRealSuspendCallBack(){ ::myRealSuspendFunc.startCoroutine(object :Continuation<Int>{ 當(dāng)前環(huán)境 override val context: CoroutineContext get() = Dispatchers.IO 結(jié)果 override fun resumeWith(result: Result<Int>) { if(result.isSuccess){ myCallBack?.onCallBack(result.getOrDefault(0)) } } }) }
其中Result就是一個(gè)內(nèi)聯(lián)類,屬于kotlin編譯器添加的裝飾類,在這里我們無論是1,2,3的情況,都可以在resumeWith 中獲取到結(jié)果,在這里通過callback回調(diào)即可
@JvmInline public value class Result<out T> @PublishedApi internal constructor( @PublishedApi internal val value: Any? ) : Serializable {
Java代碼
這里我們更正一個(gè)誤區(qū),就是suspend函數(shù)只能在kotlin中使用/Coroutine協(xié)程只能在kotlin中使用,這個(gè)其實(shí)是錯(cuò)誤的,java代碼也能夠調(diào)起協(xié)程,只不過麻煩了一點(diǎn),至少官方是沒有禁止的。 比如我們需要調(diào)用startCoroutine,可直接調(diào)用
ContinuationKt.startCoroutine();
當(dāng)然,我們也能夠直接調(diào)用suspend函數(shù)
Object result = CallBack.INSTANCE.myRealSuspendFunc(new Continuation<Integer>() { @NonNull @Override public CoroutineContext getContext() { //這里啟動(dòng)的環(huán)境其實(shí)協(xié)程沒有用到,讀者們可以思考一下為什么!這里就當(dāng)一個(gè)謎題啦!可以在評論區(qū)說出你的想法(我會(huì)在評論區(qū)解答) return (CoroutineContext) Dispatchers.getIO(); //return EmptyCoroutineContext.INSTANCE; } @Override public void resumeWith(@NonNull Object o) { 情況3 Log.e("hello","resumeWith result is "+ o +" is main "+ (Looper.myLooper() == Looper.getMainLooper())); // 回調(diào)處理即可 myCallBack?.onCallBack(result) } }); if(result != IntrinsicsKt.getCOROUTINE_SUSPENDED()){ 情況1,2 Log.e("hello","func result is "+ result); // 回調(diào)處理即可 myCallBack?.onCallBack(result) }
這里我們需要注意的是,這里java代碼比kotlin多了一個(gè)判斷,同時(shí)resumeWith的參數(shù)不再是Result,而是一個(gè)Object
if(result != IntrinsicsKt.getCOROUTINE_SUSPENDED()){ }
這里脫去了kotlin給我們添加的各種外殼,其實(shí)這就是真正的對于suspend結(jié)果的處理(只不過kotlin幫我們包了一層)
我們上文說過,suspend函數(shù)對應(yīng)的三種情況,這里的1,2都是直接返回結(jié)果的,因?yàn)闆]有走到SUSPEND狀態(tài)(IntrinsicsKt.getCOROUTINE_SUSPENDED())這里需要讀者好好閱讀上文,因此 result != IntrinsicsKt.getCOROUTINE_SUSPENDED(),就會(huì)直接走到這里,我們就直接拿到了結(jié)果
if(result != IntrinsicsKt.getCOROUTINE_SUSPENDED()){ }
如果屬于情況3,那么這里的result就不再是一個(gè)結(jié)果,而是當(dāng)前協(xié)程的狀態(tài)標(biāo)記罷了,此時(shí)當(dāng)協(xié)程完成執(zhí)行的時(shí)候(調(diào)用resume的時(shí)候),就會(huì)回調(diào)到resumeWith,這里的Object類型o才是經(jīng)過SUSPEND狀態(tài)的結(jié)果!
總結(jié)
經(jīng)過我們suspend跟回調(diào)的互相狀態(tài),能夠明白了suspend背后的邏輯與掛起的細(xì)節(jié),希望能幫到你
以上就是Suspend函數(shù)與回調(diào)的互相轉(zhuǎn)換示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Suspend函數(shù)回調(diào)轉(zhuǎn)換的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 反射注解與動(dòng)態(tài)代理綜合使用詳解
本篇文章主要介紹了Android 反射注解與動(dòng)態(tài)代理綜合使用詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04android通過藍(lán)牙接收文件打開時(shí)無法自動(dòng)選擇合適的應(yīng)用程序
android 通過藍(lán)牙接收文件,從歷史傳輸記錄打開,無法自動(dòng)選擇合適的應(yīng)用程序,比如video player打開.3gp、.mp4文件等等2013-06-06Android短信接收監(jiān)聽、自動(dòng)回復(fù)短信操作例子
本文實(shí)現(xiàn)了短信接收監(jiān)聽,當(dāng)接收到短信時(shí),可自動(dòng)回復(fù)短信,或自動(dòng)回?fù)茈娫?,同時(shí)監(jiān)聽短信的發(fā)送狀態(tài)2014-04-04Android TouchListener實(shí)現(xiàn)拖拽刪實(shí)例代碼
這篇文章主要介紹了Android TouchListener實(shí)現(xiàn)拖拽刪實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02Android性能優(yōu)化之plt?hook與native線程監(jiān)控詳解
這篇文章主要為大家介紹了Android性能優(yōu)化之plt?hook與native線程監(jiān)控詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android 7.0以上版本實(shí)現(xiàn)應(yīng)用內(nèi)語言切換的方法
本篇文章主要介紹了Android 7.0以上版本實(shí)現(xiàn)應(yīng)用內(nèi)語言切換的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02Android中 webView調(diào)用JS出錯(cuò)的解決辦法
這篇文章主要介紹了Android中 webView調(diào)用JS出錯(cuò)的解決辦法,需要的朋友可以參考下2015-01-01Android開發(fā)筆記之:深入理解Cursor相關(guān)的性能問題
本篇文章是對Android中Cursor相關(guān)的性能問題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05基于Android SQLiteOpenHelper && CRUD 的使用
本篇文章小編為大家介紹,基于Android SQLiteOpenHelper && CRUD的使用。需要的朋友可以參考一下2013-04-04