
為什么需要協(xié)程
??舉例一個異步編程中最常見的場景:后臺線程執(zhí)行一個A任務,下一個B任務依賴于A任務的執(zhí)行結果,所以必須等待上一個任務執(zhí)行完成后才能開始執(zhí)行。直接上代碼。
// 模擬本地創(chuàng)建 Token
fun requestToken(cb: (Token) -> Unit){
//todo
}
//組裝Http請求
fun createHttpUtils(token: Token, item: Item, cb: (HttpUtils) -> Unit): {
//todo
}
//進行網(wǎng)絡請求
fun postUrl(httpUtils: HttpUtils) {
//todo
}
??如果在代碼中調用 會怎么樣呢?
fun postItem() {
requestToken{ token ->
createHttpUtils(token) { httpUtils->
postUrl(httpUtils)
}
}
}
?? 可以看到 我們 嵌套了好幾層來實現(xiàn)一個網(wǎng)絡請求,可讀性變差,代碼也很容易出現(xiàn)問題。kotlin中的 協(xié)程可以幫助我們減少代碼嵌套,優(yōu)化線程/線程池控制。
協(xié)程使用
修改示例的方法,引入kotlin 中的協(xié)程我們再看一下 代碼會發(fā)生哪些變化
// 關于suspend 后邊總結,掛起函數(shù)的重點
suspend fun requestToken(): Token { ... } // 掛起函數(shù)
// 掛起函數(shù)
suspend fun createHttpUtils(token: Token): HttpUtils{ ... }
fun postUrl(httpUtils: HttpUtils) { ... }
// 代碼使用
fun postItem() {
// 注意:開發(fā)過程中不能直接使用GlobalScope.launch
GlobalScope.launch {
// 看似三個任務并行處理的,但其實是串聯(lián)執(zhí)行的
val token = requestToken()
val httpUtils= createHttpUtils(token)
postUrl(httpUtils)
}
}
??上述代碼,像代碼中的GlobalScope.launch不建議在日常開發(fā)中使用,此處只是為了簡單代碼展示。因為 GlobalScope 創(chuàng)建的協(xié)程沒有父協(xié)程,除非執(zhí)行完成或手動取消,該協(xié)程都會繼續(xù)運行,而實際上在 Android 中協(xié)程的運行需要跟 Activity/Fragment 的生命周期綁定。
??看完上邊的代碼使用,就已經(jīng)可以在項目中簡單使用了
了解協(xié)程
項目中引入
// 協(xié)程 https://github.com/Kotlin/kotlinx.coroutines
kotlin_coroutines : [group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-android', version: versions.kotlin_coroutines],
kotlin_coroutines_core : [group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: versions.kotlin_coroutines],
協(xié)程概念
協(xié)程通過將復雜性放入庫來簡化異步編程。程序的邏輯可以在協(xié)程中順序地表達,而底層庫會為我們解決其異步性。該庫可以將用戶代碼的相關部分包裝為回調、訂閱相關事件、在不同線程(甚至不同機器)上調度執(zhí)行,而代碼則保持如同順序執(zhí)行一樣簡單。 - 協(xié)程官方文檔翻譯
??協(xié)程的開發(fā)人員 Roman Elizarov 如下描述協(xié)程的:協(xié)程就像非常輕量級的線程。線程是由系統(tǒng)調度的,線程切換或線程阻塞的開銷都比較大。而協(xié)程依賴于線程,但是協(xié)程掛起時不需要阻塞線程,幾乎是無代價的,協(xié)程是由開發(fā)者控制的。所以協(xié)程也像用戶態(tài)的線程,非常輕量級,一個線程中可以創(chuàng)建任意個協(xié)程。
協(xié)程知識點
根據(jù)官方網(wǎng)站了解重要的知識點
掛起函數(shù)
//有suspend修飾符標記,這表示兩個函數(shù)都是掛起函數(shù)
suspend fun requestToken(): Token { ... }
suspend fun createHttpUtils(token: Token): HttpUtils{ ... }
??掛起函數(shù)能夠以與普通函數(shù)相同的方式獲取參數(shù)和返回值,但是調用函數(shù)可能掛起協(xié)程,掛起函數(shù)掛起協(xié)程時,不會阻塞協(xié)程所在的線程。掛起函數(shù)執(zhí)行完成后會恢復協(xié)程,后面的代碼才會繼續(xù)執(zhí)行。但是掛起函數(shù)只能在協(xié)程中或其他掛起函數(shù)中調用。
知識點
?? 要想明白協(xié)程的內在,還是需要看源碼
GlobalScope.launch
// launch 函數(shù)源碼
// CoroutineContext: 協(xié)程上下文 是一些元素的集合,包括 Job 和 CoroutineDispatcher 等元素。
// CoroutineDispatcher : 協(xié)程調度器,決定協(xié)程所在的線程或線程池 (類型 :Dispatchers.Default、Dispatchers.IO、Dispatchers.Main和Dispatchers.Unconfined)
// CoroutineScope.launch 默認使用Dispatchers.Default
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
對于源碼簡單的解釋 主要看注釋部分。
創(chuàng)建方式
- CoroutineScope.launch : CoroutineScope.launch {} 是最常用的 Coroutine builders,不阻塞當前線程,在后臺創(chuàng)建一個新協(xié)程,也可以指定協(xié)程調度器
- runBlocking : runBlocking {}是創(chuàng)建一個新的協(xié)程同時阻塞當前線程,直到協(xié)程結束。(個人很少用....)
- ** withContext**: withContext {}不會創(chuàng)建新的協(xié)程,在指定協(xié)程上運行掛起代碼塊,并掛起該協(xié)程直至代碼塊運行完成。
- **async ** : 類似CoroutineScope.launch,區(qū)別是 async 返回的Deferred 類型,Deferred 是 Job的子類,帶有返回值,但是 CoroutineScope.launch 返回的 Job類型,所以 CoroutineScope.launch沒有返回值。