為什么要使用協(xié)程
舉幾個開發(fā)中常見的例子
- 從服務器拉取一張圖片,下載,裁剪后展示在Activity上?
- 接問題1, 期間Activity關閉了怎么辦?
- 接問題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é)程。與線程相比,它更輕量、資源占用更少。
就是一個可以用簡單同步代碼寫出安全的異步回調的線程工具庫.