@
前言
你還在用 Hanlder + Message? 或者 AsyncTask? 你還在用 Rxjava?
有人說Rxjava和Coroutine是從不同維度解決非同步, 並且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(協程): 二、初識協程