AsyncTask被廢棄了,換Coroutine吧


本文主要是學習筆記,有版權問題還請告知刪文

鳴謝:guolin@第一行代碼(第三版)

你是否也在最近的代碼中看見了 AsyncTask 被一條橫杠划掉了
這表明——他要被Google放棄了

Google說讓我們換成協程,也就是Coroutine,我們來看看怎么無縫切換

1. 添加依賴

首先,他並不是在標准庫里,所以我們要添加依賴

dependencies {
      implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
      implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
}

2. 使用協程

2.1 GlobalScope.launch

最簡單的方法就是調用GlobalScope.launch
這里每創建一個協程都是頂層協程!對!沒錯!協程是分層級

GlobalScope.launch {
      //do something
}

這個函數的特點就是:自己悶頭執行,主線程結束,他也結束(雖然有可能沒執行完)

2.2 runBlocking

runBlocking從字面意思就可以理解,運行並阻塞,意思是運行協程,並在攜程未結束時阻塞當前線程

runBlocking{
      //do something            
}

這個函數的特點就是:執行時阻塞當前線程
此方法一般用在測試環境,實際使用中容易有性能問題

2.3 多協程

多協程只要我們這么寫就可以

runBlocking{
      launch{
            //do something#1
      }
      launch{
            //do something#2
      }
      launch{
            //do something#3
      }
}

這樣我們就創建了3個子協程
注意,我們這里的launch和GlobalScope.launch有一些區別,GlobalScope創建的是頂層協程
另外,我們的多協程還是運行在一個線程里面的,也就是3個協程運行在一個線程里面
到這里,相信你們都有了一種奇怪的感覺,一個線程里實現並發???
我要說的是:沒錯!
因此它不需要操作系統的參與,像是子協程有一百萬個,它也不會產生OOM
我測試了1000000個協程總共消耗了5722ms,也就是5秒
高並發效率非常高

2.4 suspend關鍵字(能在協程里調用的函數)

別擔心,這跟Java廢棄的那個強制掛起線程的函數沒什么關系,只是恰巧長得一樣而已@Java suspend()

當你的代碼過多導致可讀性很差的時候,就需要將部分代碼寫成一個函數,這時候就需要用到suspend,這樣我們就可以在協程作用域里面調用該函數
用guolin大神的話說,就是“suspend可以將我們的函數聲明成掛起函數”
我理解的是,掛起函數可以在其內部調用其他的掛起函數,比如:delay()

suspend fun 函數名(參數列表){
      //do something
}

但是suspend並不會讓我們的函數就像在協程作用域里寫的一樣,比如:無法調用launch創建子協程
這時我們就需要調用corountineScope函數

suspend fun 函數名(參數列表) = corountineScope{
      launch{
            //do something
      }
}

corountineScope 是會在其作用域里的程序執行完成前阻塞當前協程的,但它不會影響其他協程,也不會影響線程
而且它只能在 協程作用域 或 掛起函數 下調用

runBlocking {
        launch {
            println("即將被阻塞")
            coroutineScope{
                for (i in 1..3){
                    println("阻塞中")
                    delay(10)
                }
            }
            println("阻塞結束")
        }
        launch {
            println("我沒被阻塞")
        }
    }

這個運行結果就會是這樣的

即將被阻塞
阻塞中
我沒被阻塞
阻塞中
阻塞中
阻塞結束

3. 取消協程(關掉它!!!)

launch函數會返回一個Job類型的對象
Job類里面有一個cancel函數,調用它就可以取消協程

val job = Job()
val scope = CoroutineScope(job)
scope.launch{
      //do something
}
job.cancel()

這就是一般情況下項目里協程的寫法
這里還有一個要注意的,第二行的CoroutineScope(job)並不是CoroutineScope的構造函數,而是一個單獨的函數
至於為啥Kotlin要把這個函數名首字母大寫,我也不知道...

4. 獲得協程運行結果

4.1 async

假設我們在需要進行一個耗時操作,我們將這個任務放到協程里,可我們不知道什么時候能獲得這個結果,而launch最終也只是返回一個Job對象,這樣的機制不利於我們處理結果,所以為了獲得我們需要的結果,async函數就必不可少了,async會新建一個子協程並返回一個Deferred對象,Deferred里有一個await函數,他就會給我們想要的結果

還有一點相信你已經想到了,await會阻塞當前的協程,直到獲得我們想要的結果

寫法如下

runBlocking {
        val result = async { 
            //do something
        }.await()
    }

其中常量result就是我們需要的結果

這里需要注意的是,如果你有多個結果需要獲取,一定要明白其中的邏輯關系然后優化好代碼
比如:我們需要用A和B生成E,C、D生成F,E、F生成result

代碼就應該這么寫

val job = Job()
    val scope = CoroutineScope(job)
    scope.launch {
        val E = async {
            //A和B生成E
        }
        val F = async {
            //C和D生成F
        }
        val result = async {
            //E.await() 和 F.await() 生成
        }
    }

4.2 withContext

寫法如下

runBlocking {
        val result = withContext(Dispatchers.Default) { 
            //do something
        }
    }

還是引用guolin大神的話:withContext可以理解為async的簡化版寫法。
調用 withContext 會立刻執行代碼塊里的代碼,同時將當前的協程阻塞。代碼塊執行完后,會將最后一行的結果當做返回值返回。唯一不同的是他有個參數。

簡而言之,就是沒有 await 函數,沒法靈活控制協程阻塞,除此之外多了個參數,其他和async區別不大

guolin大神還說了:withContext是強制要求輸入參數的。而剛才提到的除了coroutineScope,其他所有的協程作用域構建器也都可以指定參數,只不過不強制

那么這個參數是干嘛的呢???

答案是用來選擇並發線程策略的,這種參數主要有三種可選:Dispatchers.Default、Dispatchers.IO、Dispatchers.Main
是的,你沒聽錯,線程策略,是線程,也就是說,他會創建線程來幫助實現並發,要知道網絡請求是只能在線程里實現並發的,協程是不行的。

Dispatchers.Default 默認的低並發線程策略
Dispatchers.IO 高並發線程策略
Dispatchers.Main 不開啟線程

5. 回調簡寫(suspendCoroutine)

我們回調的通常寫法是:

HttpUtil.sendHttpRequest(address : String , object : HttpCallbackListener {
        override fun onFinish() {
            //do something
        }

        override fun onError() {
            //do something
        }

    })

類似這樣,代碼就很多,動個手指頭都嫌累

Kotlin就給了我們一個解決策略

用suspendCoroutine函數!

suspendCoroutine必須在協程域中或掛起函數里調用
它接受一個lambda表達式參數,主要作用是將當前協程立刻掛起,然后在一個普通線程(guolin大神的話,我理解的是非主線程就行)里執行lambda表達式中的代碼。
lambda表達式又傳入一個Continuation參數,調用它的resume() 或 resumeWithException() 就可以讓協程恢復執行。

代碼示例:

suspend fun request(address: String): String {
        return suspendCoroutine { contination ->
            HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
                override fun onFinish(Response: String) {
                    contination.resume(Response)
                }
                override fun onError(e: Exception) {
                    contination.resumeWithException(e)
                }
            })
        }
    }

然后你就想罵我,這TM不是更多了嗎???

你調用試試...

suspend fun getBaiduResult(){
      try{
            val result = request ( "https://www.baidu.com" )
            //對result進行處理
      }catch{
            //異常處理
      }
}

爽不爽!?

還沒寫完,忙的抽不出時間寫了 T^T


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM