如何使用Kotlin提高生產力-協(xié)程

為什么要使用協(xié)程

舉幾個開發(fā)中常見的例子

  1. 從服務器拉取一張圖片,下載,裁剪后展示在Activity上?
  2. 接問題1, 期間Activity關閉了怎么辦?
  3. 接問題1, 如果是多張圖片怎么同時展示在Activity上?

問題1 我先用Java描述下

    //  Bolts Task寫法
    public fun demoMothed1ForBolts() {
        Task.callInBackground(Callable<String?> {
            // 1. 獲取圖片URL
            return@Callable getUrl()
        }).continueWith(Continuation<String?, Bitmap?> {
            // 2. 獲取圖片
            val connection = URL(it.result).openConnection() as HttpURLConnection
            return@Continuation if (connection.responseCode == 200) BitmapFactory.decodeStream(connection.inputStream) else null
        }, Task.BACKGROUND_EXECUTOR).continueWith(Continuation<Bitmap?, Bitmap?> {
            // 3. 裁剪圖片
            it.result?.let {
                return@Continuation Bitmap.createBitmap(it, it.width / 2, it.height / 2, it.width / 2, it.height / 2)
            }
            return@Continuation null
        }, Task.BACKGROUND_EXECUTOR).continueWith(Continuation<Bitmap?, Any> {
            // 4. 展示圖片
            it.result?.let {
                findViewById<ImageView>(R.id.imageView1).setImageBitmap(it)
            }
            null
        }, Task.UI_THREAD_EXECUTOR)
    }

再用kotlin+協(xié)程描述下

    public fun demoMothed1ForCoroutine() = coroutineScope.launch { val bitmap = async(Dispatchers.IO) { createTargetBitmap() } findViewById (R.id.imageView1).setImageBitmap(bitmap.await()) } private fun createTargetBitmap(): Bitmap? {
        getUrl()?.let {
            val connection = URL(it).openConnection() as HttpURLConnection
            val sourceBitmap = if (connection.responseCode == 200) BitmapFactory.decodeStream(connection.inputStream) else null
            sourceBitmap?.let {
                return Bitmap.createBitmap(it, it.width / 2, it.height / 2, it.width / 2, it.height / 2)
            }
            return null;
        }
    }

因為依賴了Facebook的開源的第三方線程庫Bolts,感覺Java的代碼調理還算清晰,和使用協(xié)程看起來差異不大.
但是如果不使用這個線程庫或者使用系統(tǒng)提供的AsyncTask,這里面會涉及到3次異步回調,代碼可讀性可能會大大降低. 也就是Java代碼要達到協(xié)程的條理性必須依賴一些好的線程框架.

問題2

我仔細看了下Bolts的API,沒有發(fā)現(xiàn)有方法讓我可以取消正在運行的Task.
但是協(xié)程因為有域的概念,取消協(xié)程域,那么運行在這個域中的所有任務都會被取消了,從而可以防止在Activity被Destory后Task還在運行.

    override fun onDestroy() {
        super.onDestroy()
        coroutineScope.cancel()
    }

問題3

  listOf(async { createTargetBitmap(url1) }, async { createTargetBitmap(url2) }).awaitAll();

同樣如果是多個任務同時進行的話,使用線程回調不太好處理.
回調回來的時候我需要去檢查其他線程里面的資源是否準備好,然后再去刷新.

協(xié)程怎么用

結合上面的栗子,可以看出協(xié)程是很好上手的,看看協(xié)程里面的關鍵字

1. suspend

掛起.

只能修飾方法 suspend fun

表示該方法只能在協(xié)程里面調用
表示該方法方法體內可以使用其他suspend方法
注意 這個關鍵字只是告訴編譯器這是一個掛起函數(shù),并沒有實際執(zhí)行.

2. CoroutineScope

協(xié)程作用域.

一般使用launch或者async創(chuàng)建的協(xié)程任務或者叫子域,都是運行在域里面,而域的作用其實就是幫助我們管理這些子域.

Android推薦使用CoroutineScope的方式運行協(xié)程,這樣當外部組件生命周期結束的時候我們可以取消掉正在執(zhí)行的任務.

override fun onDestroy() { super.onDestroy() coroutineScope.cancel() }
使用CoroutineScope運行,如果其中的一個子域拋出異常會導致整個協(xié)程作用域結束執(zhí)行.

3.CoroutineContext/Dispatchers

協(xié)程上下文

協(xié)程作用域,子域構造函數(shù)里面都需要一個上下文,用來指名協(xié)程具體運行的線程

public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if (context[Job] != null) context else context + Job())
而Dispatchers和CoroutineContext的關系就像是 線程和線程池的關系(ExecutorService/Executors),類似于一個靜態(tài)工廠.

Type 作用
Dispatchers.Main 主線程
Dispatchers.IO IO子線程
Dispatchers.Default
Dispatchers.Unconfined CPU密集類型子線程

4. CoroutineStart

啟動模式

這個屬性用的較少,一般都是使用默認模式CoroutineStart.DEFAULT. 使用CoroutineStart.LAZY 這個屬性可以讓定義和啟動分開,類似于Java里面的

Thread t = new Thread(){...};t.start()
使用Kotlin

val one = async(start = CoroutineStart.LAZY) { doSomethingUseful() }    one.start() 

5.launch/async await

子域

async和launch 的參數(shù)完全一致. 都是接受一個協(xié)程上下文,協(xié)程啟動模式,閉包協(xié)程域

context: CoroutineContext = EmptyCoroutineContext,    start: CoroutineStart = CoroutineStart.DEFAULT,    block: suspend CoroutineScope.() -> T

他們的區(qū)別是 launch不關心返回值, async會在await的時候返回值.

如果拿不準直接使用async await組合即可,這是一個常規(guī)模式,存在多鐘語言異步中.

6. withContext

子域

使用withContext 可以自動切回到調用線程的子域

coroutineScope.launch(Dispatchers.Main) { // 切換到IO線程 val image = withContext(Dispatchers.IO) { getImage(imageId) } // 執(zhí)行完成這里自動回到Main線程 avatarIv.setImageBitmap(image) }

7. runBlocking

阻塞式子域

這種方式會阻塞當前線程,所以一般也不用.

協(xié)程是什么

協(xié)程(Coroutine),協(xié)程是一種并發(fā)設計模式,本質上是更輕量級的線程。在一個線程上可以同時跑多個協(xié)程。與線程相比,它更輕量、資源占用更少。

就是一個可以用簡單同步代碼寫出安全的異步回調的線程工具庫.

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容