文章目錄
-
-
- 1.kotlin協程簡介
- 2.kotlin協程的特點
- 3.啟動協程的方式
-
-
- 3.1 runBlocking
- 3.2 launch
- 3.3 async/await
-
-
1.kotlin協程簡介
Kotlin協程的主要作用是像寫同步代碼一樣寫異步代碼。
避免回調地獄。
2.kotlin協程的特點
可控制:協程能做到可被控制的發起子任務
輕量級:協程非常小,占用資源比線程還小
文法糖:使多任務或多線程切換不再使用文法糖
3.啟動協程的方式
- 啟動協程需要添加依賴
// Gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
1:runBlocking:T // 用于執行協程任務,通常隻用于啟動最外層協程
2:launch:Job // 用于執行協程任務
3:async/await:Deferred // 用于執行協程任務,并執行得到結果
launch、async 需要實作 CoroutineScope 接口通過CoroutineScope 來開啟協程
// CoroutineScope 讓MainActivity帶有作用域
class MainActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
}
override fun onDestroy() {
// 這樣在 Activity 退出的時候,對應的作用域就會被取消,所有在該 Activity 中發起的請求都會被取消掉。
cancel()
super.onDestroy()
}
3.1 runBlocking
- runBlocking 的作用于最外層,也就是作用于協程的範圍。可以建立一個阻塞目前線程的協程. 是以它主要被用來在main函數中或者測試中使用, 作為連接配接函數.
-
假設一個三方登入的場景
第一步:請求token
第二步:請求使用者權限
第三步:三方登陸
三次請求都按照前後順序成功以後才算登入成功。
假設三個線程對應三個請求
class RequestToken : Runnable {
override fun run() {
Log.e(TAG, "RequestToken")
}
}
class RequestUserPermission : Runnable {
override fun run() {
Thread.sleep(200)
Log.e(TAG, "RequestUserPermission")
}
}
class SignWithWX : Runnable {
override fun run() {
Log.e(TAG, "SignWithWX")
}
}
采用回調的方式是這樣的。
RequestToken(){
// 請求token回調
successCallBack(
// 請求權限
RequestUserPermission(){
// 請求permission回調
successCallBack(){
// 三方登入
SignWithWX(){
// 三方登入回調
// success
}
}
}
)
}
上面的代碼隻是簡單的寫了成功的回調還沒有處理失敗的回調,這種回調嵌套的代碼看着感覺很亂
下面采用 runBlocking 的方式是這樣的。runBlocking 預設是在目前線程開啟協程的。runBlocking會阻塞目前線程,等到runBlocking全部執行完成後才會繼續執行
private fun login() = runBlocking {
RequestToken().run()
RequestUserPermission().run()
SignWithWX().run()
Log.e(TAG, "--end--")
}
上面runBlocking的代碼執行順序Log如下:即使RequestUserPermission 在請求的時候 sleep了200依然是按照順序執行的
MainActivity: RequestToken
MainActivity: RequestUserPermission
MainActivity: SignWithWX
MainActivity: --end--
非協程的線程中可以用Thread.sleep() 來使線程睡眠,協程中可以用 delay(100) 方法。非協程子產品中是禁止調用 delay()方法的。
3.2 launch
CoroutineScope的擴充函數,傳回Job。launch可以開啟協程,也可以在其他協程中開啟協程。**launch不會阻塞目前線程。**這個和runBlocking是有差別的
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
啟動launch的方式如下
private fun loadMain() = launch(Dispatchers.Main) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.Main-->$name")
}
private fun loadIO() = launch(Dispatchers.IO) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.IO-->$name")
}
private fun loadDefault() = launch(Dispatchers.Default) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.Default-->$name")
}
private fun loadUnconfined() = launch(Dispatchers.Unconfined) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.Unconfined-->$name")
}
launch有幾個參數可以選擇,代表了協程在哪個線程中執行,以及協程中斷以後是在哪個線程中恢複。
- Dispatchers.Main // 主線程
- Dispatchers.IO // 采用on-demand建立的線程池, 用于網絡或者是讀寫檔案的工作.
- Dispatchers.Default // 代表使用JVM上的共享線程池, 其大小由CPU核數決定, 不過即便是單核也有兩個線程. 通常用來做CPU密集型工作
Dispatchers.Unconfined // 不指定特定線程預設目前線程
列印的日志如下:
Dispatchers.IO-->DefaultDispatcher-worker-1
Dispatchers.Unconfined-->main
Dispatchers.Default-->DefaultDispatcher-worker-2
Dispatchers.Main-->main
- launch 傳回的是一個 job 提供了一些方法來控制和擷取任務的屬性
start()
cancel()
cancel()
join()
3.3 async/await
async 和 launch 一樣都是用來建立一個 Coroutine 的。通過 async 函數傳回的 Defeered 對象可以擷取 Coroutine 的傳回值。
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
// start = CoroutineStart.LAZY 需要通過 start開啟
val d1 = async(start = CoroutineStart.LAZY) { RequestUserPermission().run() }
val d2 = async(start = CoroutineStart.LAZY) { SignWithWX().run() }
d1.start()
}
-
async{} 會在對應的 CoroutineContext 下建立一個新的協程,并且放回一個Deferred,通過 Deferred 可以異步擷取結果,也就是調用Deffered 的 await() 方法。
下面代碼Log列印出的值為 “testReturn”
非 start = CoroutineStart.LAZY 不用調用 start() 方法開啟
private fun loadMain() = launch {
val d1 = async<String>{ testReturn() }
val result = d1.await()
Log.e(TAG, "await=$result")
}
private fun testReturn(): String {
return "testReturn"
}
await()方法是 suspend 修飾的方法,Kotlin協程中隻有這一個關鍵字。suspend修飾的方法隻能被suspend修飾的方法調用。
public suspend fun await(): T
在 launch 裡面會建立一個新的 CoroutineContext,如果沒有傳入 Context 則使用的EmptyCoroutineContext,通過 newCoroutineContext() 函數會配置設定一個預設的 Dispatcher,也就是Dispatcher.default,預設的全局 Dispatcher,會在jvm 層級共享線程池,會建立等于cpu 核心數目的線(但是至少建立兩個子線程)。接着判斷 CoroutineStart 是否 Lazy 模式,如果 Lazy 模式,則該Coroutine 不會立馬執行,需要你主動掉了 Job.start() 之後才會執行。
- suspend 修飾的函數或者 lambda 隻能被 suspend 修飾的函數 lambda 調用