Kotlin 協(xié)程與掛起函數(shù)及suspend關(guān)鍵字深入理解
1.掛起函數(shù)
掛起函數(shù)在Kotlin協(xié)程中是一個(gè)比較重要的知識(shí)點(diǎn),協(xié)程的非阻塞式、Channel、Flow等API都對(duì)它有充分的理解才能在學(xué)習(xí)時(shí)事半功倍。
已知的是Kotlin協(xié)程的特點(diǎn)是輕量和非阻塞, 單靠這兩個(gè)點(diǎn)就能說明Kotlin協(xié)程就的優(yōu)勢(shì)嗎,不一定。這里先提出一個(gè)結(jié)論:掛起函數(shù)是Kotlin協(xié)程的最大優(yōu)勢(shì)。 下面對(duì)于掛起函數(shù)的講解就圍繞這句話展開。
以獲取省市區(qū)Code為例,獲取區(qū)域Code的前提是要有城市Code,城市Code的前提是要有省份Code,請(qǐng)求結(jié)果通過CallBack返回。
public class RequestCode { public static void main(String[] args) { getProvincesCode(provincesCode -> { getCityCode(provincesCode, cityCode -> { getAreaCode(cityCode, areaCode -> { }); }); }); } /** * 獲取省份Code * * @param callBack */ private static void getProvincesCode(CallBack callBack) { callBack.onSuccess("100000"); } /** * 獲取城市Code * * @param provincesCode * @param callBack */ private static void getCityCode(String provincesCode, CallBack callBack) { callBack.onSuccess("100010"); } /** * 獲取區(qū)域code * * @param cityCode * @param callBack */ private static void getAreaCode(String cityCode, CallBack callBack) { callBack.onSuccess("100011"); } }
上面的代碼在開發(fā)中都遇到過,代碼可以優(yōu)化的更簡(jiǎn)潔更易讀,這里主要是證明掛起函數(shù)的優(yōu)勢(shì),就不做優(yōu)化了。
上面的代碼可以看到三層嵌套是比較復(fù)雜的,如果再加上獲取國(guó)家、區(qū)域街道的話嵌套層級(jí)會(huì)更深,這樣就會(huì)對(duì)可讀性、可擴(kuò)展性、可維護(hù)性都有影響。那么用Kotlin這個(gè)代碼要怎么寫呢?
現(xiàn)在我用delay(1000L)
替代CallBack
模擬網(wǎng)絡(luò)請(qǐng)求
fun main() = runBlocking { val provincesCode = getProvincesCode() val cityCode = getCityCode(provincesCode) val areaCode = getAreaCode(cityCode) } /** * 獲取省份Code * */ private suspend fun getProvincesCode(): String { withContext(Dispatchers.IO) { delay(1000L) } return "100000" } /** * 獲取城市Code * * @param provincesCode */ private suspend fun getCityCode(provincesCode: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "100010" } /** * 獲取區(qū)域code * * @param cityCode */ private suspend fun getAreaCode(cityCode: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "100011" }
Kotlin實(shí)現(xiàn)了同步方式完成異步請(qǐng)求,這種方式的實(shí)現(xiàn)要?dú)w功于具體的函數(shù)的實(shí)現(xiàn),在這三個(gè)函數(shù)中可以發(fā)現(xiàn)他們的定義與普通函數(shù)的區(qū)別是多了一個(gè)suspend
關(guān)鍵字,它的意思就是掛起。
再回頭看main
函數(shù),前面加上了runBlocking
也就是說建立了一個(gè)協(xié)程的作用域,這是因?yàn)?code>suspend的出現(xiàn),因?yàn)?code>suspend的作用就是掛起和恢復(fù),而掛起和恢復(fù)是需要上下文的,因此就需要定義一個(gè)作用域,這里選擇了runBlocking
。
在函數(shù)中還有一個(gè)地方withContext(Dispatchers.IO)
這主要是執(zhí)行線程的切換,除了IO
線程以外還有Main
、default
等線程。
在Kotlin中掛起和恢復(fù)是成對(duì)出現(xiàn)的,因?yàn)榧热粫?huì)被掛起那肯定也會(huì)被恢復(fù),而協(xié)程的非阻塞也是因?yàn)閽炱鸷突謴?fù)能力,那么掛起和恢復(fù)又是怎么樣的一個(gè)含義呢?
首先執(zhí)行getProvincesCode()
這是一個(gè)掛起函數(shù),因?yàn)橛昧?code>Dispatchers.IO切換到了IO線程因此這個(gè)等待時(shí)間就在IO線程執(zhí)行,當(dāng)?shù)却龝r(shí)間結(jié)束后(CallBack回調(diào)結(jié)果)provincesCode
收到返回的結(jié)果,這個(gè)函數(shù)中suspend
掛起,main
函數(shù)中收到結(jié)果就是恢復(fù)能力,
我們看一下debug日志:
- 這是
getProvincesCode
的協(xié)程日志,可以獲取以下幾個(gè)信息:
- 當(dāng)前協(xié)程的名字是:coroutine#1
- 協(xié)程運(yùn)行在主線程:main
- 任務(wù)被掛起
withContext(Dispatchers.IO)
切換線程
任務(wù)切換到DefaultDispatcher-worker-1
線程繼續(xù)執(zhí)行
return "?。?00000"
回到主線程執(zhí)行
我們總結(jié)一下上面的日志:
main
→DefaultDispatcher-worker-1
的過程是掛起;DefaultDispatcher-worker-1
→main
的過程是恢復(fù);
那么掛起和恢復(fù)的含義也有非常明確了:
- 掛起: 只是將程序執(zhí)行流程轉(zhuǎn)移到了其他線程,主線程并不會(huì)阻塞。我們知道Android中如果阻塞超過一定時(shí)間就會(huì)出現(xiàn)無響應(yīng),上面的代碼在運(yùn)行過程中并不會(huì)導(dǎo)致無響應(yīng)的發(fā)生,在代碼執(zhí)行的過程中我們還可以做其他事情的,因?yàn)槿蝿?wù)進(jìn)入到子線程后主線程的狀態(tài)是空閑的。
- 恢復(fù): 當(dāng)子線程的任務(wù)執(zhí)行完畢后再回到主線程的過程就叫做恢復(fù)。
掛起和恢復(fù)的能力是掛起函數(shù)特有的能力,普通函數(shù)時(shí)不具備的,如果在一個(gè)普通函數(shù)中僅僅加上suspend
關(guān)鍵字會(huì)發(fā)現(xiàn)其實(shí)并沒有什么用,編譯器會(huì)告知這種定義是多余的。
上面還提出了一個(gè)結(jié)論——掛起函數(shù)是Kotlin協(xié)程的最大優(yōu)勢(shì), 首先因?yàn)楹瘮?shù)的執(zhí)行過程中可以切換線程,其次函數(shù)的運(yùn)行不會(huì)影響主線程導(dǎo)致主線程被阻塞。
2.深入理解suspend
上面掛起函數(shù),掛起函數(shù)主要就是主線程切換到子線程,這個(gè)過程又是如何實(shí)現(xiàn)的?
已知掛起函數(shù)是依靠關(guān)鍵字suspend
,我們將帶有這個(gè)關(guān)鍵子的函數(shù)轉(zhuǎn)換成Java代碼進(jìn)行分析:
private static final Object getProvincesCode(Continuation var0) { ... CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO(); ... return "?。?00000"; }
代碼比較長(zhǎng),只保留了需要的地方。
getProvincesCode
里面多了一個(gè)參數(shù)Continuation
,意思是延續(xù),而suspend
沒有了,那么Continuation
又是什么
public interface Continuation<in T> { /** * 協(xié)程的上下文. */ public val context: CoroutineContext /** * 恢復(fù)執(zhí)行相應(yīng)的協(xié)程,傳遞一個(gè)成功或失敗的[result]作為最后一個(gè)掛起點(diǎn)的返回值 */ public fun resumeWith(result: Result<T>) }
從Continuation
的源碼發(fā)現(xiàn)一個(gè)問題,它跟我們開頭的CallBack
是類似的,其中resumeWith
和onSucess
的功能也是一樣的,區(qū)別在于Continuation
是有一個(gè)泛型的參數(shù)
public interface CallBack { public void onSuccess(String response); } public interface Continuation<in T> { public fun resumeWith(result: Result<T>) }
由此可以得出結(jié)論:Continuation
本質(zhì)上就是CallBack
只是多了一個(gè)帶有泛型的參數(shù)。
通過上面的分析可以得出結(jié)論 :掛起函數(shù)的本質(zhì)就是Callback
我們已知Continuation
的本質(zhì)是CallBack
,Continuation
是延續(xù),就是接下來要做的事情,那么在省市區(qū)獲取的代碼其實(shí)就是這樣一個(gè)流程:
getProvincesCode(object : Continuation<String> { override fun resumeWith(result: Result<String>) { val provinceCode = result.getOrNull() println("provincesCode:$provinceCode") getCityCode(provinceCode, object : Continuation<String> { override fun resumeWith(result: Result<String>) { val cityCode = result.getOrNull() println("cityCode:$cityCode") getAreaCode(cityCode, object : Continuation<String> { override fun resumeWith(result: Result<String>) { val areaCode = result.getOrNull() println("areaCode:$areaCode") } }) } }) } })
這個(gè)過程是編譯器在后面幫我們做的,實(shí)際編碼中這種編碼方式并不會(huì)出現(xiàn),畢竟掛起函數(shù)解決的就是這個(gè)問題。
3.協(xié)程與掛起函數(shù)
需要說明的是協(xié)程與掛起函數(shù)并不是同一個(gè)東西,我們?cè)僮铋_始使用suspend
實(shí)現(xiàn)省市區(qū)三級(jí)聯(lián)動(dòng)的時(shí)候用到了runBlocking
,掛起函數(shù)的調(diào)用是需要一個(gè)協(xié)程作用域的,除了這點(diǎn)之外,在runBlocking
的源碼中還有一點(diǎn)要注意:
public actual fun <T> runBlocking( context: CoroutineContext, block: suspend CoroutineScope.() -> T): T { ... }
第二個(gè)參數(shù)中有一個(gè)suspend
,所以可以理解CoroutineScope.() -> T
也是一個(gè)掛起函數(shù)那么被suspend
關(guān)鍵字修飾的掛起函數(shù)可以運(yùn)行在runBlocking
中也就不難理解了。
那么我們就可以得到一個(gè)結(jié)論:掛起和恢復(fù)是協(xié)程的底層能力,而掛起函數(shù)是一種表現(xiàn)形式,通過suspend關(guān)鍵字修飾的函數(shù)可以讓我們?cè)谏蠈雍芊奖愕膶?shí)現(xiàn)這種底層能力。
4.掛起函數(shù)是Kotlin協(xié)程的最大優(yōu)勢(shì)
開篇就提出了這個(gè)結(jié)論,最后再來總結(jié)一下這個(gè)能力:
- 掛起函數(shù)在執(zhí)行中可以切換線程且不會(huì)對(duì)主線程造成阻塞;
- 掛起函數(shù)有掛起和恢復(fù)的能力,可以極大地簡(jiǎn)化異步編程,實(shí)現(xiàn)同步執(zhí)行異步任務(wù);
- 掛起函數(shù)是Kotlin中特有的能力;
5.總結(jié)
- 定義一個(gè)掛起函數(shù)只需要在普通函數(shù)上加上
suspend
關(guān)鍵字,而添加這個(gè)關(guān)鍵字之后函數(shù)類型就會(huì)被改變,如suspend (Int) -> Double”與“(Int) -> Double
并不是同一個(gè)類型; - 掛起函數(shù)具有掛起和恢復(fù)的能力,那么就會(huì)出現(xiàn)同一行代碼會(huì)在兩個(gè)線程中執(zhí)行,Kotlin編譯器會(huì)在后臺(tái)進(jìn)行編譯;
- 掛起函數(shù)的本質(zhì)就是CallBack。只是說,Kotlin 底層用了一個(gè)更加高大上的名字,叫 Continuation;
- 掛起和恢復(fù)是一種底層能力,而上層的表現(xiàn)形式是掛起函數(shù);
- 掛起函數(shù)只能在協(xié)程中被調(diào)用或者在掛起函數(shù)中調(diào)用,而協(xié)程中的block也是一個(gè)掛起函數(shù)。
以上就是Kotlin 協(xié)程與掛起函數(shù)及suspend關(guān)鍵字深入理解的詳細(xì)內(nèi)容,更多關(guān)于Kotlin 協(xié)程掛起函數(shù)suspend的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android studio將Module打包成Jar的方法
這篇文章主要介紹了Android studio將Module打包成Jar的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10Android獲取點(diǎn)擊屏幕的位置坐標(biāo)
這篇文章主要為大家詳細(xì)介紹了Android獲取點(diǎn)擊屏幕的位置坐標(biāo),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05android開發(fā)環(huán)境搭建詳解(eclipse + android sdk)
這篇文章主要介紹了android開發(fā)環(huán)境搭建詳解(eclipse + android sdk),需要的朋友可以參考下2014-05-05Android 開源項(xiàng)目側(cè)邊欄菜單(SlidingMenu)使用詳解
SlidingMenu的是一種比較新的設(shè)置界面或配置界面效果,在主界面左滑或者右滑出現(xiàn)設(shè)置界面,能方便的進(jìn)行各種操作.目前有大量的應(yīng)用都在使用這一效果。如Evernote、Google+、Foursquare等,國(guó)內(nèi)的豌豆夾,人人,360手機(jī)助手等都使用SlidingMenu的界面方案。2016-05-05android studio 新手入門教程(三)Github( ignore忽略規(guī)則)的使用教程圖解
這篇文章主要介紹了android studio 新手入門教程(三)Github( ignore忽略規(guī)則)的使用教程圖解,需要的朋友可以參考下2017-12-12Android 深入探究自定義view之流式布局FlowLayout的使用
FlowLayout(int align, int hgap, int vgap)創(chuàng)建一個(gè)新的流布局管理器,它具有指定的對(duì)齊方式以及指定的水平和垂直間隙,意思就是說從左上角開始添加原件,依次往后排,第一行擠滿了就換一行接著排2021-11-11Android中獲取網(wǎng)頁表單中的數(shù)據(jù)實(shí)現(xiàn)思路及代碼
在Android中獲取網(wǎng)頁里表單中的數(shù)據(jù)具體實(shí)現(xiàn)代碼如下,感興趣的各位可以參考過下哈,希望對(duì)大家有所幫助2013-06-06Android中阻止AlertDialog關(guān)閉實(shí)例代碼
這篇文章主要介紹了Android阻止AlertDialog關(guān)閉實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03