之後四天,會講講以前面知識做基礎開發時,會遇到的問題
在前面,講到了coroutine是什麼,不妥善處理會有workleak,也介紹了他的exceptiom和取消,以及官方提供的lifecycleScope和viewModelScope
但是昨天coroutine托夢告訴我,他也想要細水長流的愛情呀,那些launch和async的任務都只是過客而已,利用完他就走了,心很累,我覺得吧,我們跟coroutine都甚麼交情了,這事肯定給她安排
那要application內還是application外,首先作為開發者,你要評估你任務的執行時間,如果你要執行的任務比application就應該選workManager
在這之前,我講了許多coroutine的實用工具,也示範了一次性網路請情、socket連線、資料流的概述、paging3應用等等,但這些都只是為了在某個ui或某些情境的短期任務,當然並不是他不好,我也是參考了coroutine的最佳做法介紹的,只是長期任務,我們沒講到呀
那要如何為應用內的長期任務使用coroutine,其實答案已經講出來了,就是在Application class內創建coroutine
class MyApp:Application() {
val applicationScope = CoroutineScope(SupervisorJob())
...
}
注意我們並不需要對此作出取消,因為現在的應用場景是這個corouitne應該要和application存活一樣久
而現在,我們可以利用這個作用域,運行比調用作用域生命週期更長的任務
我直接跟官方blog借範例
//注入applicationScope
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork() {
withContext(ioDispatcher) {
doSomeOtherWork()
externalScope.launch {
// if this can throw an exception, wrap inside try/catch
// or rely on a CoroutineExceptionHandler installed
// in the externalScope's CoroutineScope
veryImportantOperation()
}.join()
}
}
}
//注入applicationScope
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork(): Any { // Use a specific type in Result
withContext(ioDispatcher) {
doSomeOtherWork()
return externalScope.async {
// Exceptions are exposed when calling await, they will be
// propagated in the coroutine that called doWork. Watch
// out! They will be ignored if the calling context cancels.
veryImportantOperation()
}.await()
}
}
}
上面的範例,即使viewModelScope已經destory了,在applicationScope的任務也會持續進行,並且doWork只會在veryImportantOperation完成後才回傳
這個方法是這樣被提出的,很多人覺得用withContext把任務丟去externalScope就可以了,但人生就沒這麼簡單
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork() {
withContext(ioDispatcher) {
doSomeOtherWork()
withContext(externalScope.coroutineContext) {
veryImportantOperation()
}
}
}
}
這個code會遇到兩個問題
//withContext source code
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// compute new context
val oldContext = uCont.context
val newContext = oldContext + context
// always check for cancellation of new context
newContext.ensureActive()
在withContext裡面,會將oldContext和newContext合併,以dispatcher而言,就是切換的動作,但如果像上面的範例呢,這個plus會把job一起換掉,然後就有問題了
⚠️ Disclaimer
If it turns out that the CoroutineContext of your applicationScope matches the GlobalScope or ProcessLifecycleOwner.get().lifecycleScope one, you can directly assign them as follows
class MyApplication : Application() {
val applicationScope = GlobalScope
}
博文給了免責申明,我也貼一下,如果你判斷你的使用需求符合GlobalScope或ProcessLifecycleOwner,你也可以用
更多的細節請看博文
我這篇有講到NonCancellable
另一個解法,是為了給在application外執行的任務,對workManager不熟悉的,請先看這篇,那要在workManager裡使用coroutine,其實也非常簡單,coroutine作為官方首選庫,直接被包在一起,這邊找最新版本
val work_version = "2.6.0"
// Kotlin + coroutines
implementation("androidx.work:work-runtime-ktx:$work_version")
那他被包的有多簡單,首先繼承自CoroutineWorker而不是Worker,可以看到doWork已經被包成suspend function了,而suspend的寫法,也跟之前的一樣
class CoroutineDownloadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
val data = downloadSynchronously("https://www.google.com")
saveData(data)
return Result.success()
}
}
}
對的,非常簡單,其他的做法可以參考這篇workManager的github code sample
那coroutine的job要怎麼處理???
在workManager裡面有個cancel(),可以取消或停止任務,而最棒的是,他會主動在coroutine丟出CancellationException去取消coroutine
coroutine:所以愛會消失對不對?我最後還是被取消了QQ
博文 coroutines-patterns-for-work-that-shouldnt-be-cancelled
WorkManager