国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

kotlin語(yǔ)法進(jìn)階 - 協(xié)程(一)協(xié)程基礎(chǔ)

這篇具有很好參考價(jià)值的文章主要介紹了kotlin語(yǔ)法進(jìn)階 - 協(xié)程(一)協(xié)程基礎(chǔ)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

一. kotlin協(xié)程的理解

協(xié)程并不是一個(gè)新的概念,而是一個(gè)非常老的概念,很多語(yǔ)言都支持協(xié)程,建議去瀏覽器去了解一下協(xié)程的歷史和基本概念,這里我們只講一下kotlin中的協(xié)程的作用。

從代碼實(shí)現(xiàn)角度來看:kotlin協(xié)程底層是用線程實(shí)現(xiàn)的,是一個(gè)封裝完善供開發(fā)者使用的線程框架。kotlin的一個(gè)協(xié)程可以理解為是運(yùn)行在線程上的一個(gè)執(zhí)行任務(wù)并且該任務(wù)可以在不同的線程間切換,一個(gè)線程可以同時(shí)運(yùn)行多個(gè)協(xié)程
概念圖如下:一個(gè)線程可以同時(shí)運(yùn)行多個(gè)協(xié)程(執(zhí)行任務(wù)),如果有需要,線程1上的協(xié)程可以切換到線程2上去運(yùn)行。反應(yīng)在我們Android開發(fā)中,就是我們的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)庫(kù)操作等耗時(shí)任務(wù)時(shí)運(yùn)行在IO線程的,當(dāng)我們獲取到數(shù)據(jù)以后更新頁(yè)面的操作是運(yùn)行在主線程的
kotlin 協(xié)程,kotlin,kotlin,android,android studio

從開發(fā)者角度來看:kotlin協(xié)程可以實(shí)現(xiàn)以同步的方式去編寫異步執(zhí)行的代碼,解決線程切換回調(diào)的嵌套地獄。協(xié)程掛起時(shí)不需要阻塞線程,幾乎是無代價(jià)的。
反應(yīng)到我們Android開發(fā)就是,以前我們?cè)贗O線程拿到數(shù)據(jù)后,想回到主線程更新UI,我們一般會(huì)使用Handler或者CallBack接口回調(diào)等方式,這樣做的結(jié)果是當(dāng)我們連續(xù)進(jìn)行上述操作后很容易陷入嵌套地獄,嚴(yán)重影響代碼閱讀性和美觀。

二. 協(xié)程的創(chuàng)建方式

我們想在Android Studio中使用協(xié)程,除了引入kotlin支持外,我們還要引入兩個(gè)協(xié)程支持庫(kù)

// 協(xié)程核心庫(kù)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
// 協(xié)程Android支持庫(kù)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"

創(chuàng)建協(xié)程的方式

2.1.) runBlocking:這是一個(gè)頂層函數(shù),會(huì)啟動(dòng)一個(gè)新的協(xié)程并阻塞調(diào)用它的線程,直到里面的代碼執(zhí)行完畢,返回值是泛型T。
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
2.2) CoroutineScope.launch :通過一個(gè)協(xié)程作用域的擴(kuò)展方法launch啟動(dòng)一個(gè)協(xié)程,不會(huì)阻塞調(diào)用它的線程,返回值是Job。
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
2.3) CoroutineScope.async :通過一個(gè)協(xié)程作用域的擴(kuò)展方法async啟動(dòng)一個(gè)協(xié)程,不會(huì)阻塞調(diào)用它的線程,返回值是 Deferred。
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

我們一般使用2)3)兩種方式開啟一個(gè)協(xié)程,1)方式因?yàn)闀?huì)阻塞線程,所以runBlocking函數(shù)我們?cè)陂_發(fā)中基本不會(huì)使用到,但可以用于代碼調(diào)試。
提前說一下async和launch的區(qū)別,后面會(huì)講到并有示例:
返回值不同:async函數(shù)體中最后一行代碼表達(dá)式運(yùn)行結(jié)果會(huì)作為結(jié)果返回,也就是Deferred中的泛型T,我們可以通過其他協(xié)程函數(shù)獲取到這個(gè)執(zhí)行結(jié)果,而launch沒有這樣的返回值。

示例代碼:GlobalScope是一個(gè)協(xié)程庫(kù)中已經(jīng)提供的協(xié)程作用域

runBlocking {
    Log.e("協(xié)程","我們使用runBlocking啟動(dòng)了一個(gè)協(xié)程")
}
GlobalScope.launch {
    Log.e("協(xié)程","我們使用launch啟動(dòng)了一個(gè)協(xié)程")
}
GlobalScope.async {
    Log.e("協(xié)程","我們使用async啟動(dòng)了一個(gè)協(xié)程")
}

三. 協(xié)程作用域與協(xié)程上下文

我們前面提到創(chuàng)建協(xié)程的三種方式中有l(wèi)aunch和async方式都需要使用協(xié)程作用域開啟一個(gè)協(xié)程

3.1 什么是協(xié)程作用域?

協(xié)程作用域CoroutineScope是協(xié)程的運(yùn)行范圍。
如下:CoroutineScope是一個(gè)接口,只有一個(gè)接口屬性CoroutineContext 協(xié)程上下文,所以CoroutineScope其實(shí)是CoroutineContext協(xié)程上下文的封裝。

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

3.2 什么是協(xié)程上下文?

CoroutineContext代表協(xié)程的上下文,我們?cè)诶斫鈪f(xié)程上下文之前,我們先理解一下Context上下文,Android開發(fā)者應(yīng)該對(duì)這個(gè)再屬性不過了,我們的Application、Activity等都被定義成了一個(gè)Context。那我們?cè)趺蠢斫膺@個(gè)Context上下文呢?好比我們上學(xué)考試的時(shí)候進(jìn)行閱讀理解,我們要理解一段話的含義,我們應(yīng)該怎么辦,需要代入原文章,然后看看前面看看后面是怎么回事我們才能懂。代入代碼中,我們運(yùn)行到這段代碼了,我們也需要了解一下這段代碼該怎么執(zhí)行,需要什么信息才能執(zhí)行,這些信息就保存在Context中,我們可以把Context理解為一個(gè)容器,用于保存代碼執(zhí)行所需要的配置信息。所以CoroutineContext就是我們啟動(dòng)一個(gè)協(xié)程需要的配置信息。

我們看一下CoroutineContext的源碼實(shí)現(xiàn),是一個(gè)接口,該接口內(nèi)部有一個(gè)內(nèi)部接口Element(該接口繼承自CoroutineContext,Element接口有一個(gè)key,類似于鍵值對(duì),我們可以用key判斷Element)
我們看到我們可以對(duì)CoroutineContext執(zhí)行 get(根據(jù)key獲取某個(gè)CoroutineContext)、plus(一個(gè)加號(hào)操作符重載返回值是CoroutineContext,這就意味著CoroutineContext+CoroutineContext =CoroutineContext )、minusKey(去掉key對(duì)應(yīng)的CoroutineContext)等操作,看著真像是一個(gè)集合操作,其實(shí)CoroutineContext就類似于一個(gè)集合操作,所以我們可以猜想一下CoroutineContext有很多的子類,這些子類可以相加拼接,minusKey相減構(gòu)成多樣性的CoroutineContext。

public interface CoroutineContext {

    public operator fun <E : Element> get(key: Key<E>): E?

    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else 
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
    
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

    public fun minusKey(key: Key<*>): CoroutineContext

    public interface Key<E : Element>

    public interface Element : CoroutineContext {

        public val key: Key<*>

        public override operator fun <E : Element> get(key: Key<E>): E? 
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}
3.3 協(xié)程作用域與子協(xié)程

我們可以在一個(gè)協(xié)程中開啟另一個(gè)協(xié)程,這樣開啟的協(xié)程就是子協(xié)程。
需要注意的有兩點(diǎn):
1.)子協(xié)程的協(xié)程作用域會(huì)繼承父協(xié)程協(xié)程作用域里的 協(xié)程上下文
2.)如果父協(xié)程取消了,所有的子協(xié)程也會(huì)被取消

我們會(huì)在下面依次驗(yàn)證這兩個(gè)注意點(diǎn)。

3.4 CoroutineContext的那些子類

CoroutineContext有很多子類,這些子類各自有不同的的作用,并且共同構(gòu)成了協(xié)程作用域里的CoroutineContext。
我們先打印一下一個(gè)協(xié)程作用域的的coroutineContext,看打印出來是什么

GlobalScope.launch{
    Log.e("協(xié)程的coroutineContext",this.coroutineContext.toString())
}
打印結(jié)果:
[StandaloneCoroutine{Active}@5a7652d, Dispatchers.Default]

我們一一了解這些子類,并且了解他們對(duì)協(xié)程的影響和作用

3.4.1 )CoroutineDispatcher 協(xié)程調(diào)度器

我們知道協(xié)程運(yùn)行在線程,并且可以切換到其他線程,CoroutineDispatcher確定了相關(guān)的協(xié)程在哪個(gè)線程或哪些線程上執(zhí)行,所以也可以把CoroutineDispatcher叫做協(xié)程的線程調(diào)度器。

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

    @ExperimentalStdlibApi
    public companion object Key : AbstractCoroutineContextKey<ContinuationInterceptor, CoroutineDispatcher>(
        ContinuationInterceptor,
        { it as? CoroutineDispatcher })
}

kotlin給我們提供了四種調(diào)度器

public actual object Dispatchers {
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

Default:默認(rèn)調(diào)度器,CPU密集型任務(wù)調(diào)度器,通常處理一些單純的計(jì)算任務(wù),或者執(zhí)行時(shí)間較短任務(wù)。例如數(shù)據(jù)計(jì)算
IO:IO調(diào)度器,IO密集型任務(wù)調(diào)度器,適合執(zhí)行IO相關(guān)操作。比如:網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫(kù)操作,文件操作等
Main:UI調(diào)度器,只有在UI編程平臺(tái)上有意義,用于更新UI,例如Android中的主線程
Unconfined:非受限調(diào)度器,無所謂調(diào)度器,當(dāng)前協(xié)程可以運(yùn)行在任意線程上

我們通過上面的打印可以知道,GlobalScope的協(xié)程調(diào)度器是Dispatchers.Default,那么我們?nèi)绾胃淖兡兀课覀兦懊娌榭磍aunch和async方法時(shí),看到他們的第一個(gè)參數(shù)都是context: CoroutineContext ,是的,我們可以從這里傳入我們需要的上下文,并且會(huì)覆蓋掉協(xié)程作用域里的上下文。
如下:我們希望開啟一個(gè)協(xié)程運(yùn)行在IO線程上

GlobalScope.launch(Dispatchers.IO){
    Log.e("協(xié)程的coroutineContext",this.coroutineContext.toString())
}
打印結(jié)果:
[StandaloneCoroutine{Active}@db90566, Dispatchers.IO]

那如果我們想在協(xié)程運(yùn)行中改變線程怎么辦,最常見的,網(wǎng)絡(luò)請(qǐng)求在IO線程,而頁(yè)面更新在主線程。kotlin給我們提供了一個(gè)頂層函數(shù)withContext用于改變協(xié)程的上下文并執(zhí)行一段代碼。

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T 

示例代碼:

GlobalScope.launch(Dispatchers.Main) {
    val result = withContext(Dispatchers.IO) {
        //網(wǎng)絡(luò)請(qǐng)求
        "返回結(jié)果"
    }
    mBtn.text = result
}
3.4.2 )CoroutineName 協(xié)程名稱

協(xié)程名稱:顧名思義,就是給協(xié)程取個(gè)名字,這個(gè)沒有什么好說的,就是用來更好的區(qū)分協(xié)程的。

public data class CoroutineName(

    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {

    public companion object Key : CoroutineContext.Key<CoroutineName>

    override fun toString(): String = "CoroutineName($name)"
}

示例代碼:

GlobalScope.launch(Dispatchers.Main + CoroutineName("主協(xié)程")) {
    Log.e("協(xié)程的coroutineContext",this.coroutineContext.toString())
}
打印結(jié)果:
[CoroutineName(主協(xié)程), StandaloneCoroutine{Active}@288ff9, Dispatchers.Main]

我們使用協(xié)程名稱驗(yàn)證子協(xié)程的第一個(gè)注意點(diǎn):子協(xié)程的協(xié)程作用域會(huì)繼承父協(xié)程協(xié)程作用域里的 協(xié)程上下文
我們?cè)谏鲜龃a中開啟一個(gè)子協(xié)程:

GlobalScope.launch(Dispatchers.Main + CoroutineName("主協(xié)程")) {
    Log.e("協(xié)程的coroutineContext" , this.coroutineContext.toString())
    launch {
        Log.e("協(xié)程的coroutineContext2" , this.coroutineContext.toString())
    }
}

打印結(jié)果如下:可以看到,子協(xié)程竟然打印出了和父協(xié)程相同的CoroutineName,說明父協(xié)程的協(xié)程上下文傳播到了子協(xié)程

協(xié)程的coroutineContext: [CoroutineName(主協(xié)程), StandaloneCoroutine{Active}@288ff9, Dispatchers.Main]
協(xié)程的coroutineContext2: [CoroutineName(主協(xié)程), StandaloneCoroutine{Active}@b95b3e, Dispatchers.Main]

我們稍微改一下代碼:

GlobalScope.launch(Dispatchers.Main + CoroutineName("主協(xié)程")) {
    Log.e("協(xié)程的coroutineContext" , this.coroutineContext.toString())
    launch(CoroutineName("子協(xié)程")) {
        Log.e("協(xié)程的coroutineContext2" , this.coroutineContext.toString())
    }
}

打印結(jié)果如下:子協(xié)程設(shè)置的協(xié)程上下文覆蓋掉了從父協(xié)程處繼承的上下文

協(xié)程的coroutineContext: [CoroutineName(主協(xié)程), StandaloneCoroutine{Active}@288ff9, Dispatchers.Main]
協(xié)程的coroutineContext2: [CoroutineName(子協(xié)程), StandaloneCoroutine{Active}@8aced9f, Dispatchers.Main]
3.4.3)Job與協(xié)程的生命周期

我們前面在查看launch和async兩個(gè)擴(kuò)展函數(shù)時(shí),可以看到launch返回結(jié)果是一個(gè)Job,而async的返回結(jié)果是一個(gè)Deferred,Deferred其實(shí)是Job的子類。

public interface Job : CoroutineContext.Element 
public interface Deferred<out T> : Job 

那么Job是什么呢?
協(xié)程啟動(dòng)以后,我們可以得到一個(gè)Job對(duì)象,通過Job對(duì)象我們可以檢測(cè)協(xié)程的生命周期狀態(tài),并且可以操作協(xié)程(比如取消協(xié)程)。我們可以大致把Job理解為協(xié)程本身。
協(xié)程的生命周期:協(xié)程創(chuàng)建以后,處于New(新建)狀態(tài),協(xié)程啟動(dòng)(調(diào)用start()方法)以后,處于Active(活躍) 狀態(tài),協(xié)程及所有子協(xié)程完成任務(wù)以后,處于Completed(完成) 狀態(tài),協(xié)程被取消(調(diào)用cancel()方法)以后,處于Cancelled(取消) 狀態(tài)
我們可以使用job下面的字段檢查協(xié)程的狀態(tài):
isActive 用于判斷協(xié)程是否處于活躍狀態(tài),
isCancelled 用于判斷協(xié)程是否被取消
isCompleted用于判斷協(xié)程是否結(jié)束
除了獲取協(xié)程狀態(tài),還有很多可以用于操縱協(xié)程的函數(shù),例如:
cancel()取消協(xié)程。
start()啟動(dòng)協(xié)程。
await() 等待協(xié)程執(zhí)行完成

我們驗(yàn)證一下協(xié)程的生命周期:

GlobalScope.launch {
    val job = launch(CoroutineName("子協(xié)程")) {

    }
    Log.e("子協(xié)程的狀態(tài)","${job.isActive} ${job.isCancelled} ${job.isCompleted}")
    delay(1000)
    Log.e("子協(xié)程的狀態(tài)2","${job.isActive} ${job.isCancelled} ${job.isCompleted}")
}
打印結(jié)果:
子協(xié)程的狀態(tài): true false false
子協(xié)程的狀態(tài)2: false false true
GlobalScope.launch {
    val job = launch(CoroutineName("子協(xié)程")) {
        delay(5000)
    }
    Log.e("子協(xié)程的狀態(tài)","${job.isActive} ${job.isCancelled} ${job.isCompleted}")
    job.cancel()
    Log.e("取消后子協(xié)程的狀態(tài)","${job.isActive} ${job.isCancelled} ${job.isCompleted}")
}
打印結(jié)果:
子協(xié)程的狀態(tài): true false false
取消后子協(xié)程的狀態(tài): false true false

我們使用協(xié)程的生命周期驗(yàn)證一下子協(xié)程的第二個(gè)注意點(diǎn):如果父協(xié)程取消了,所有的子協(xié)程也會(huì)被取消

var childJob : Job? = null
val parentJob = GlobalScope.launch {
    childJob = launch(CoroutineName("子協(xié)程")) {
        delay(5000)
    }
}
Log.e("父協(xié)程的狀態(tài)" , "${parentJob.isActive} ${parentJob.isCancelled} ${parentJob.isCompleted}")
Handler().postDelayed(Runnable {
    Log.e("子協(xié)程的狀態(tài)" ,
        "${childJob?.isActive} ${childJob?.isCancelled} ${childJob?.isCompleted}")
    parentJob.cancel()
    Log.e("父協(xié)程的狀態(tài)" ,
        "${parentJob.isActive} ${parentJob.isCancelled} ${parentJob.isCompleted}")
    Log.e("子協(xié)程的狀態(tài)" ,
        "${childJob?.isActive} ${childJob?.isCancelled} ${childJob?.isCompleted}")
} , 1000)
打印結(jié)果如下:可以看到父協(xié)程取消以后,子協(xié)程也取消了。
父協(xié)程的狀態(tài): true false false
子協(xié)程的狀態(tài): true false false
父協(xié)程的狀態(tài): false true false
子協(xié)程的狀態(tài): false true false
3.4.4) CoroutineExceptionHandler 協(xié)程異常處理

我們?cè)趯懘a的時(shí)候,肯定會(huì)遇到異常的情況,正常我們處理異常使用try …catch捕獲,但難免會(huì)出現(xiàn)遺漏。CoroutineExceptionHandler 就是協(xié)程專門捕獲異常的類,協(xié)程中出現(xiàn)的異常都會(huì)被捕獲并由CoroutineExceptionHandler的handleException方法返回給我們進(jìn)行處理。

public interface CoroutineExceptionHandler : CoroutineContext.Element {
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>

    public fun handleException(context: CoroutineContext, exception: Throwable)
}

handleException會(huì)返回兩個(gè)參數(shù),第一個(gè)參數(shù)是出現(xiàn)異常的協(xié)程,第二個(gè)參數(shù)是出現(xiàn)的異常。
示例如下:我們手動(dòng)拋出一個(gè)NullPointerException異常,然后創(chuàng)建一個(gè)CoroutineExceptionHandler賦給協(xié)程。

val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    Log.e("捕獲異常", "${coroutineContext[CoroutineName]}$throwable")
}

GlobalScope.launch(Dispatchers.Main + CoroutineName("主協(xié)程")+exceptionHandler) {
    Log.e("協(xié)程的coroutineContext",this.coroutineContext.toString())
    throw NullPointerException()
}
打印結(jié)果:
協(xié)程的coroutineContext: [CoroutineName(主協(xié)程), com.jf.simple.ThirdActivity$onCreate$$inlined$CoroutineExceptionHandler$1@288ff9, StandaloneCoroutine{Active}@b95b3e, Dispatchers.Main]

捕獲異常: CoroutineName(主協(xié)程) :java.lang.NullPointerException
3.4.5) ContinuationInterceptor 協(xié)程攔截器
public interface ContinuationInterceptor : CoroutineContext.Element {

    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
}

顧名思義,是用來攔截協(xié)程的,因?yàn)樯婕暗綊炱鸷瘮?shù)的原理,并且平時(shí)開發(fā)用到的相對(duì)較少,所以這里不展開。

四、協(xié)程的啟動(dòng)模式

我們?cè)诓榭磍aunch和async擴(kuò)展函數(shù)時(shí),還有第二個(gè)參數(shù),start: CoroutineStart,這個(gè)參數(shù)的含義就是協(xié)程的啟動(dòng)模式,

public enum class CoroutineStart {
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED;
}

可以看到CoroutineStart是一個(gè)枚舉類,有四種類型。
DEFAULT默認(rèn)啟動(dòng)模式,協(xié)程創(chuàng)建后立即開始調(diào)度,注意是立即調(diào)度而不是立即執(zhí)行,可能在執(zhí)行前被取消掉。
LAZY懶漢啟動(dòng)模式,創(chuàng)建后不會(huì)有任何調(diào)度行為,直到我們需要它執(zhí)行的時(shí)候才會(huì)產(chǎn)生調(diào)度。需要我們手動(dòng)的調(diào)用Job的start、join或者await等函數(shù)時(shí)才會(huì)開始調(diào)度。
ATOMIC 在協(xié)程創(chuàng)建后立即開始調(diào)度,但它和DEFAULT模式是有區(qū)別的,該模式下協(xié)程啟動(dòng)以后需要執(zhí)行到第一個(gè)掛起點(diǎn)才會(huì)響應(yīng)cancel操作。
UNDISPATCHED協(xié)程在這種模式下會(huì)直接開始在當(dāng)前線程下執(zhí)行,直到運(yùn)行到第一個(gè)掛起點(diǎn)。和ATOMIC很像,但UNDISPATCHED很受調(diào)度器的影響

示例代碼:
DEFAULT:代碼立即打印,說明協(xié)程創(chuàng)建后立即調(diào)度

GlobalScope.launch {
    Log.e("default啟動(dòng)模式", "協(xié)程運(yùn)行")
}

LAZY:未調(diào)用start()方法前,無打印,調(diào)用start()方法后,代碼打印。協(xié)程說明創(chuàng)建后不會(huì)調(diào)度,需要我們手動(dòng)啟動(dòng)。

val lazyJob = GlobalScope.launch(start = CoroutineStart.LAZY){
    Log.e("lazy啟動(dòng)模式", "協(xié)程運(yùn)行")
}
//lazyJob.start()

ATOMIC:協(xié)程運(yùn)行后運(yùn)行到第一個(gè)掛起函數(shù)后才會(huì)響應(yīng)cancel()方法。

val atomicJob = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
    Log.e("atomic啟動(dòng)模式" , "運(yùn)行到掛起函數(shù)前")
    delay(100)
    Log.e("atomic啟動(dòng)模式" , "運(yùn)行到掛起函數(shù)后")
}
atomicJob.cancel()
打印結(jié)果:
atomic啟動(dòng)模式: 運(yùn)行到掛起函數(shù)前

UNDISPATCHED:可以看到結(jié)果和ATOMIC很像,但因?yàn)锳TOMIC和UNDISPATCHED開發(fā)中用的相對(duì)較少,這里不過多區(qū)分,有興趣的可以開啟一個(gè)子協(xié)程添加調(diào)度器執(zhí)行一下代碼,看看區(qū)別。

五、掛起函數(shù)

我們前面提到:kotlin協(xié)程最大的優(yōu)勢(shì)就是以同步的方式寫異步代碼,這就是通過掛起函數(shù)用來實(shí)現(xiàn)

被關(guān)鍵字suspend修飾的函數(shù)稱為掛起函數(shù),掛起函數(shù)只能在協(xié)程或者另一個(gè)掛起函數(shù)中調(diào)用。掛起函數(shù)的特點(diǎn)是“掛起與恢復(fù)”,當(dāng)協(xié)程遇到掛起函數(shù)時(shí),協(xié)程會(huì)被掛起,等掛起函數(shù)執(zhí)行完畢以后,協(xié)程會(huì)恢復(fù)到掛起的地方重新運(yùn)行。
掛起是非阻塞性的掛起,不會(huì)阻塞線程;恢復(fù)不用我們手動(dòng)恢復(fù),而是協(xié)程幫我們完成。

我們?cè)谶@里演示兩個(gè)例子,來理解掛起函數(shù)的使用。(這里不講掛起函數(shù)的原理,后面會(huì)專門開篇。)

5.1 )順序執(zhí)行異步代碼

示例代碼1:我們定義兩個(gè)掛起函數(shù),一個(gè)延遲1s,一個(gè)延遲2s(來模擬網(wǎng)絡(luò)請(qǐng)求),最后都返回一個(gè)整數(shù),我們想把結(jié)果相加。不使用協(xié)程的情況下,因?yàn)檠舆t不一樣,我們需要使用類似回調(diào)的方式去獲取結(jié)果。但使用協(xié)程就不一樣,我們從打印結(jié)果看到,代碼完全是順序執(zhí)行的,measureTimeMillis方法可以測(cè)量運(yùn)行時(shí)間,可以看到運(yùn)行時(shí)間是3s多一點(diǎn):所以整個(gè)過程是這樣的,協(xié)程運(yùn)行到returnNumber1(),檢測(cè)到是一個(gè)掛起函數(shù),協(xié)程掛起,等待returnNumber1()完成,returnNumber1()運(yùn)行完成花了1s,協(xié)程回到returnNumber1()被調(diào)用的地方,獲取結(jié)果,繼續(xù)往下執(zhí)行,行到returnNumber2(),檢測(cè)到是一個(gè)掛起函數(shù),協(xié)程掛起,等待returnNumber2()完成,returnNumber2()運(yùn)行完成花了2s,協(xié)程回到returnNumber2()被調(diào)用的地方,繼續(xù)執(zhí)行。

suspend fun returnNumber1() : Int {
    delay(1000L)
    Log.e("returnNumber1" , "調(diào)用了returnNumber1()方法")
    return 1
}

suspend fun returnNumber2() : Int {
    delay(2000L)
    Log.e("returnNumber1" , "調(diào)用了returnNumber2()方法")
    return 2
}
GlobalScope.launch {
    val time = measureTimeMillis {
        val number1 = returnNumber1()
        Log.e("number1" , "需要獲取number1")
        val number2 = returnNumber2()
        Log.e("number2" , "需要獲取number2")
        val result = number1 + number2
        Log.e("執(zhí)行完畢" , result.toString())
    }
    Log.e("運(yùn)行時(shí)間",time.toString())
}
打印結(jié)果:
returnNumber1: 調(diào)用了returnNumber1()方法
number1: 需要獲取number1
returnNumber1: 調(diào)用了returnNumber2()方法
number2: 需要獲取number2
執(zhí)行完畢: 3
運(yùn)行時(shí)間: 3010
5.2) async實(shí)現(xiàn)并發(fā)

注意,掛起函數(shù)掛起的是當(dāng)前的協(xié)程,不會(huì)影響其他的協(xié)程
我們修改一下上面的代碼,將兩個(gè)掛起函數(shù)放在兩個(gè)子協(xié)程中,最終結(jié)果使用await()獲取,await()方法的作用是等待協(xié)程運(yùn)行結(jié)束并獲取返回結(jié)果,我們前面說過launch和async的區(qū)別,一個(gè)執(zhí)行結(jié)果的返回值,一個(gè)沒有,所以這里使用async。
示例代碼:這里使用async開啟了兩個(gè)子協(xié)程,兩個(gè)子協(xié)程都有掛起函數(shù),所以兩個(gè)子協(xié)程都會(huì)被掛起,但他們的父協(xié)程在調(diào)用await()掛起函數(shù)之前沒有都沒有被掛起,所以可以正常運(yùn)行,兩個(gè)子協(xié)程并發(fā)執(zhí)行,最終通過打印的執(zhí)行時(shí)間也可以看出來,代碼執(zhí)行也就2s

GlobalScope.launch(Dispatchers.Main) {
    val time = measureTimeMillis {
        val deferred1 = async {
            Log.e("--" , "子協(xié)程1運(yùn)行開始")
            returnNumber1()
        }
        Log.e("--" , "開始運(yùn)行第二個(gè)協(xié)程")
        val deferred2 = async {
            Log.e("--" , "子協(xié)程2運(yùn)行開始")
            returnNumber2()
        }
        Log.e("--" , "開始計(jì)算結(jié)果")

        val result = deferred1.await() + deferred2.await()
        Log.e("執(zhí)行完畢" , result.toString())

    }
    Log.e("運(yùn)行時(shí)間" , time.toString())
}

打印結(jié)果如下:
開始運(yùn)行第二個(gè)協(xié)程
開始計(jì)算結(jié)果
子協(xié)程1運(yùn)行開始
子協(xié)程2運(yùn)行開始
returnNumber1: 調(diào)用了returnNumber1()方法
returnNumber1: 調(diào)用了returnNumber2()方法
執(zhí)行完畢: 3
運(yùn)行時(shí)間: 2009

下一節(jié),我們將用最有效的方式講解掛起函數(shù)的原理。文章來源地址http://www.zghlxwxcb.cn/news/detail-704495.html

到了這里,關(guān)于kotlin語(yǔ)法進(jìn)階 - 協(xié)程(一)協(xié)程基礎(chǔ)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Android kotlin實(shí)戰(zhàn)之協(xié)程suspend詳解與使用

    Android kotlin實(shí)戰(zhàn)之協(xié)程suspend詳解與使用

    ????????Kotlin 是一門僅在標(biāo)準(zhǔn)庫(kù)中提供最基本底層 API 以便各種其他庫(kù)能夠利用協(xié)程的語(yǔ)言。與許多其他具有類似功能的語(yǔ)言不同, async ?與? await ?在 Kotlin 中并不是,甚至都不是標(biāo)準(zhǔn)庫(kù)的一部分。此外,Kotlin 的? 掛起函數(shù) ?概念為異步操作提供了比 future 與 pro

    2024年02月03日
    瀏覽(23)
  • Android使用kotlin+協(xié)程+room數(shù)據(jù)庫(kù)的簡(jiǎn)單應(yīng)用

    Android使用kotlin+協(xié)程+room數(shù)據(jù)庫(kù)的簡(jiǎn)單應(yīng)用

    前言:一般主線程(UI線程)中是不能執(zhí)行創(chuàng)建數(shù)據(jù)這些操作的,因?yàn)榈却龝r(shí)間長(zhǎng)。所以協(xié)程就是為了解決這個(gè)問題出現(xiàn)。 第一步:在模塊級(jí)的build.gradle中引入 ? 好了前期工作ok,正式編寫room吧! 第二步:創(chuàng)建表實(shí)體 ?第三部:編寫對(duì)應(yīng)的Dao接口 ?第四步:創(chuàng)建數(shù)據(jù)庫(kù)信息

    2024年02月13日
    瀏覽(23)
  • Kotlin基礎(chǔ)語(yǔ)法

    名稱 釋義 String 字符串 char 字符 Boolean 布爾型 Int 整型 Float 單精度浮點(diǎn)型 Double 雙精度浮點(diǎn)型 List 集合 Set 無重復(fù)元素集合 Map 鍵值對(duì)集合 可讀可寫變量 可讀變量 根據(jù)定義變量時(shí)進(jìn)行初始化,系統(tǒng)可以根據(jù)初始化值自動(dòng)進(jìn)行類型推導(dǎo),進(jìn)而可以省略類型聲明 函數(shù)定義 在Kotl

    2024年02月03日
    瀏覽(19)
  • 大型Android項(xiàng)目架構(gòu):基于組件化+模塊化+Kotlin+協(xié)程+Flow+Retrofit+Jetpack+MVVM架構(gòu)實(shí)現(xiàn)WanAndroid客戶端

    大型Android項(xiàng)目架構(gòu):基于組件化+模塊化+Kotlin+協(xié)程+Flow+Retrofit+Jetpack+MVVM架構(gòu)實(shí)現(xiàn)WanAndroid客戶端

    前言:茍有恒,何必三更眠五更起;最無益,莫過一日曝十日寒。 之前一直想寫個(gè) WanAndroid 項(xiàng)目來鞏固自己對(duì) Kotlin+Jetpack+協(xié)程 等知識(shí)的學(xué)習(xí),但是一直沒有時(shí)間。這里重新行動(dòng)起來,從項(xiàng)目搭建到完成前前后后用了兩個(gè)月時(shí)間,平常時(shí)間比較少,基本上都是只能利用零碎的

    2024年02月09日
    瀏覽(26)
  • Android開發(fā)知識(shí)學(xué)習(xí)——Kotlin進(jìn)階

    Android開發(fā)知識(shí)學(xué)習(xí)——Kotlin進(jìn)階

    申明前綴有construct修飾 如果有一個(gè)主構(gòu)造函數(shù),每個(gè)次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù),可以直接委托或者通過別的構(gòu)造函數(shù) 主構(gòu)造函數(shù):是類頭的一部分,跟在類名后面(可帶參數(shù)),沒有任何注解和可見性修飾符。如: 主構(gòu)造函數(shù)中沒有任何代碼,初始化代碼放在關(guān)鍵

    2024年02月06日
    瀏覽(92)
  • Android初學(xué)之a(chǎn)ndroid studio運(yùn)行java/kotlin程序

    Android初學(xué)之a(chǎn)ndroid studio運(yùn)行java/kotlin程序

    第一步驟: File — New — New Module ,然后彈出一個(gè)框,(左邊)選擇 Java or Kotlin Library ,(右邊)編輯自己的圖書館名、包名、類名,選擇 Java 一個(gè)語(yǔ)言,然后 Finish 如下圖: 然后,就可以看見我新建的 java Library 了,如下圖: 第二步驟:馬上寫個(gè)測(cè)試程序 看看能不能運(yùn)行

    2024年02月11日
    瀏覽(19)
  • 在 Android Studio Java 項(xiàng)目里混合 Kotlin 編程

    在 Android Studio Java 項(xiàng)目里混合 Kotlin 編程

    首先,先搞明白一個(gè)概念,這里的 Java 混合 Kotlin 是指文件層級(jí)的混合,即 Java 代碼還是寫在 .java 文件中,Kotlin 代碼還是寫在 .kt 文件中,只不過是可以在 Java 的代碼中可以調(diào)用自己寫好的 Kotlin 類,從 Java 的角度看,它并不知道它調(diào)用的這個(gè)類是 Kotlin 寫的,這個(gè)類和平時(shí)遇

    2024年02月08日
    瀏覽(20)
  • android studio 打包簽名apk時(shí)報(bào)kotlin版本錯(cuò)誤

    android studio 打包簽名apk時(shí)報(bào)kotlin版本錯(cuò)誤

    報(bào)錯(cuò)信息如下: /Users/abbb/Library/Android/sdk/caches/transforms-3/572ca993caa0789f4046529ddf3eacd2/transformed/jetified-BaseRecyclerViewAdapterHelper-4.0.1/jars/classes.jar!/META-INF/com.github.CymChad.brvah.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.

    2024年01月25日
    瀏覽(29)
  • 【Android Studio插件升級(jí)以后kotlin jdk版本沖突】

    【Android Studio插件升級(jí)以后kotlin jdk版本沖突】

    今天升級(jí)一些插件版本后無法編譯,發(fā)現(xiàn)kotlin jdk 1.8.20和kotlin jdk 1.6.21重復(fù)沖突,網(wǎng)上找了挺多方法都沒生效,千回百轉(zhuǎn)才解決,發(fā)出來希望可以幫助到同樣報(bào)錯(cuò)的同學(xué) FAILURE: Build failed with an exception. What went wrong: Execution failed for task ‘:app:checkDebugDuplicateClasses’. A failure occurred while

    2024年02月12日
    瀏覽(23)
  • Android的Gradle、Studio、Java、Kotlin版本兼容

    Android Gradle 插件和 Android Studio 兼容性 Android Studio 版本 所需插件版本 Hedgehog - 2023.1.1 3.2-8.2 Giraffe - 2022.3.1 3.2-8.1 Flamingo - 2022.2.1 3.2-8.0 Electric Eel - 2022.1.1 3.2-7.4 Dolphin - 2021.3.1 3.2-7.3 Chipmunk - 2021.2.1 3.2-7.2 Bumblebee - 2021.1.1 3.2-7.1 Arctic Fox - 2020.3.1 3.1-7.0 Gradle版本和Java版本對(duì)應(yīng)關(guān)系 Ja

    2024年02月09日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包