@

前言

你還在用 Hanlder + Message? 或者 AsyncTask? 你還在用 Rxjava?

有人說RxjavaCoroutine是從不同維度解決非同步, 並且Rxjava的強大不止於非同步問題.

好吧, 管它呢. 讓我們擁抱 Coroutine(協程) 吧.

協程的篇幅有點長, 需要自己編寫大量的測試例子. 從入門到都學完, 難度還是挺大的. 所以:我們直接把例子擺出來. 至於其他的, 我們從淺到深,日後..日後..再說 [奸笑]





Kotlin中文站: 請看官網



準備:

lifecycle 都有吧? 例子中需要用到 lifecycleScope;

//coroutines: 協程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
//coroutines for Android
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3' //lifecycle
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'

沒有lifecycle 怎麼辦? 當頁面銷燬時,那些延時任務, 例如Handler, 網路請求 啥的, 需要及時關閉, 它們會影響 Activity 的及時銷燬, 帶來記憶體洩漏的風險. 協程也是如此.

此時我們必須自己管理協程的作用域; 所以最好還是用 lifecycleScope.

//自己定義作用域, 頁面銷燬時, 取消協程.  替換lifecycleScope即可;
val mainScope = MainScope()
override fun onDestroy(){
super.onDestroy()
mainScope.cancel()
}

什麼是協程作用域呢? 類似於生命週期, 當頁面銷燬時, 協程應當隨之停止.

提示:以下是本篇文章正文內容,下面案例可供參考


一、直接上例子

協程能幹嘛? 延時任務, 非同步任務, 定時任務, 並行任務, 協程+Retrofit. 非同步流? 通道?

1.延時任務.

比較常見的業務場景, 幾秒鐘後執行...

還記得handler怎麼寫嗎? 定義 Handler 實現類, 然後

mHandler.sendMessageDelayed()
//或者
mHandler.postDelayed(Runnable{...})

使用協程:

doDelayed()

fun doDelayed() = lifecycleScope.launch {
delay(2000L) //協程掛起2秒
binding.tvTitle.text = "變變變, 我是百變小魔女"
}
  • lifecycleScope: 預設主執行緒. 它會監聽Activity生命週期, 不需要手動 cancel()
  • launch: 非阻塞形式啟動協程.
  • delay(2000): 掛起協程, 2秒後恢復.

然後封裝一下:

//執行延時任務
fun CoroutineScope.doDelayed(timeMillis: Long, block: suspend () -> Unit) = this.launch {
delay(timeMillis)
block.invoke()
} //使用
lifecycleScope.doDelayed(2000L){
binding.tvTitle.text = "變變變, 我是百變小魔女"
}

2.非同步任務

在子執行緒執行任務, 完事回撥主執行緒

程式碼如下:

doOnBack()

// 模擬子執行緒中執行了2秒鐘的任務,
private fun doOnBack() = lifecycleScope.launch {
val result = withContext(Dispatchers.IO){
delay(2000) // 假裝努力幹活中..
"變變變, 我是百變小魔女"
}
binding.tvTitle.text = result
} //或者. async .
private fun doOnBack() = lifecycleScope.launch {
val deferred = async (Dispatchers.IO){
delay(2000) // 假裝努力幹活中..
"變變變, 我是百變小魔女"
}
binding.tvTitle.text = deferred.await()
}
  • withContext(): 不新建協程, 它只指定 執行當前程式碼塊 所需的執行緒;
  • async: 建立一個協程; 它返回一個Deferred, 而lanuch返回Job物件. 多個async可以支援併發, await()是等待任務執行完畢,並返回結果.
  • Dispatchers.IO: 協程排程器. 幾種排程器如下所示
引數 意義
不指定 它從啟動了它的 CoroutineScope 中承襲了上下文
Dispatchers.Main 用於Android. 在UI執行緒中執行
Dispatchers.IO 子執行緒, 適合執行磁碟或網路 I/O操作
Dispatchers.Default 子執行緒,適合 執行 cpu 密集型的工作
Dispatchers.Unconfined 管它呢, 不常用, 先不管

由於非同步任務需要兩段程式碼塊. 子執行緒程式碼,及主執行緒響應; 所以就不封裝了; 並且裡面其實就一句 withContext(Dispatchers.IO) 或者 val deferred = async (Dispatchers.IO)

3.並行任務:

有時多個任務非同步進行, 而我們不清楚哪一個會先完成, 我們需要等待它們的最終結果時.

private suspend fun taskOne(): Int {
delay(2000) //模擬任務執行 2秒
return 1
} private suspend fun taskTwo(): Int {
delay(1500) //模擬任務執行 1.5秒
return 2
} private suspend fun taskExecute(): Int = coroutineScope {
val result1 = async(Dispatchers.IO) { taskOne() }
val result2 = async(Dispatchers.IO) { taskTwo() }
result1.await() + result2.await()
} //使用
lifecycleScope.launch {
val sum = taskExecute()
binding.tvTitle.text = "變變變, 我是百變小魔女$sum"
}

taskOne 執行需要2秒; taskTwo 執行需要1.5秒; 並行, 任務總共耗費約 2秒時間.

suspend: 標誌掛起函式, 掛起函式只能在協程中執行.

4.定時任務:

定時任務簡單啊, delay()不阻塞執行緒啊.

lifecycleScope.launch {
repeat(Int.MAX_VALUE){
delay(1000L)
binding.tvTitle.text = "變變變, 我是百變小魔女$it"
}
}

這個定時任務, 每次執行, 只設置TextView的text.

我們假設我們的業務比較複雜, 每次需要耗費300ms; 如下所示:

lifecycleScope.launch {
repeat(Int.MAX_VALUE){
delay(1000L) //假設我們的任務比較繁重, 每次需要消耗300ms
delay(300L)
binding.tvTitle.text = "變變變, 我是百變小魔女$it"
}
}

顯而易見, 此時至少要 1300ms 才能迴圈一次. 所以上面寫的1000ms只能算是間隔時間. 而任務執行需要時間, 協程掛起,恢復需要時間, 任務進入佇列到執行緒執行也需要時間.

因此:

當定時任務的精確度要求不高, 每次執行的程式碼任務比較輕便. 耗時較少時, 可以用這種方式.

有的時候, 我們鎖屏了, 或者跳到別的頁面了, 我們不需要定時一直執行, 即便更新了UI, 我們也看不到啊! 所以,我們希望頁面離開時, 定時取消. 頁面顯示時,重新啟動定時即可:

我們就以秒殺倒計時為例;

private var endTime: Long = 0	//秒殺截止時間

//秒殺剩餘 5分鐘
endTime = System.currentTimeMillis() + 300 * 1000 lifecycleScope.launchWhenResumed { //onPause 的時候會暫停.
repeat(Int.MAX_VALUE){
val now = System.currentTimeMillis()
binding.tvTitle.text = if(now >= endTime){
"秒殺結束"
}else{
val second = (endTime - now) / 1000 + 1
"秒殺倒計時($second); it=$it"
}
delay(1000L)
}
}

launchWhenResumed: 當頁面 Resumed 時, 啟動協程. onPause()時自動停止. 頁面重新顯示時恢復. 通過觀察這裡的 it, 可以斷定 協程只是被掛起, 而不是銷燬重建.

同樣的還有: launchWhenCreated; launchWhenStarted 當然這是 lifecycle 的知識了.

有時, 我們需要精確的定時器.

可以用, java.util 包下的 Timer, 如下所示; 也可以用 CountDownTimer

val timer = timer("定時Thread_name", false, 2000, 1000){
letUsPrintln("定時器, 這是個子執行緒")
}

當然, 它沒有自動暫停恢復的功能; 取消執行時, 別忘了 cancel();

協程+Retrofit 的封裝還是放到後面吧..


總結

沒啥好總結的, 複製貼上就完事了. 下一篇我們再深入淺出的瞭解協程 [奸笑]

上一篇: Room-資料持久化儲存(進階)

下一篇: Kotlin Coroutine(協程): 二、初識協程