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 調用