WorkManager 是官方推薦用來在背景處理 persistent 工作的 API。所謂的 persistent 工作指的是,即使 app 重啟或是 device 重啟,仍然需要安排處理的工作。本文章將介紹如何利用 WorkManager 來排程工作。
Table of Contents
Background Works
Android 提供數種方式讓開發者處理背景工作。在進一步地深入 WorkManager 之前,讓我們先來了解有哪些不同的背景工作型態。在下圖中,我們可以將背景工作分為三種型態(詳情請參照官網):
- Immediate:需要馬上被處理的工作,而且很快就會結束。
- Long-Running:需要一些時間才能處理完成的工作,有可能大於 10 分鐘。
- Deferrable:不需要立即處理的工作。可以安排在某個時間點來處理的工作。或是需要定期處理的工作。
在這三種型態中,每一種還可以細分為(詳情請參照官網):
- Persistent work:即使 app 重啟或是 device 重啟,仍然需要安排處理的工作。例如,我們希望可以確保將重要的資料完整地寫回資料庫,即使 process 強制結束也不會被中斷。
- Impersistent work:在 process 結束後,不需要安排處理的工作。

最終總共有六種背景工作型態(詳情請參照官網):
| Category | Persistent | Impersistent |
|---|---|---|
| Immediate | WorkManager | Coroutines |
| Long running | WorkManager | 不推薦 |
| Deferrable | WorkManager | 不推薦 |
所有的 persistent 工作應該使用 WorkManager 來實作。而 immedidate 的 impersistent 工作,則應該使用 coroutines 來實作。
其中 long running 和 deferrable 的 impersistent 工作被標註為不推薦使用。因為 impersistent 工作不應該是 long running 或 deferrable 工作。Long running 工作所需時間較長,所以有可能因為 process 被終止而中斷。Deferrable 工作會在之後某個時間點被執行,所以有可能在被執行之前,app 就被重啟了。所以 long running 和 deferrable 工作應該只被考慮為 persisent 工作。
WorkManager
WorkManager 將 WorkRequest 儲存到資料庫出來實作 persistent 工作。所以即使 device 重啟後,WorkManager 可以從資料庫中取得 WorkRequest 資料,並重新排程。
除了 WorkManager 之外,Android 還提供其他方式來處理背景工作。在 Modern background execution in Android 這篇文章中,分析了幾種需要使用背景工作的情況,以及建議的解決方式。

Dependency
首先,我們引入 WorkManager 的 dependency。WorkManager 支援三種實作工作的方法。請根據你選定的方法,將相對應的 dependency 引入到你的 build.gradle。
第一種是純 Java 的工作。
dependencies {
implementation "androidx.work:work-runtime:2.7.0"
}第二種是支援 Kotlin suspend 的工作。
dependencies {
implementation "androidx.work:work-runtime-ktx:2.7.0"
}第三種是支援 RxJava2 的工作。
dependencies {
implementation "androidx.work:work-runtime-rxjava2:2.7.0"
}在本文章,我們只會介紹第二種,也就是支援 Kotlin suspend 的工作。
建立工作
要建立一個工作,我們只需要繼承 CoroutineWorker,並且實作 doWork() 即可。doWork() 會在背景中被 WorkManager 執行,並且最後要回傳一個 Result。
- 如果工作執行成功,就回傳
Result.success()。 - 如果工作執行失敗,就回傳
Result.failure()。 - 如果工作執行失敗,想要讓 WorkManager 過一段時間再重試一次工作的話,就回傳
Result.retry()。
在下方的程式碼中,我們建立一個 LogWorker。在 doWork() 中,它從 inputData 中取得輸入的資料。這樣我們就可以在每次建立工作時,傳入不同的資料。
package com.waynestalk.workmanagerexample
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
class LogWorker(appContext: Context, workerParameters: WorkerParameters) :
CoroutineWorker(appContext, workerParameters) {
companion object {
const val MESSAGE = "message"
}
private val tag = javaClass.canonicalName
override suspend fun doWork(): Result {
val message = inputData.getString(MESSAGE) ?: return Result.failure()
Log.d(tag, message)
return Result.success()
}
}排程一次性工作
OneTimeWorkRequest可以用來建立只會被執行一次的工作。我們可以透過 OneTimeWorkRequestBuilder 來建立它,再呼叫 WorkManager.enqueue() 來排程工作。
在建立請求時,我們可以用 .setInputData() 傳入資料給工作,而 doWork() 可以用 inputData 來取得這些資料。另外,.setInitialDelay() 可以設定工作在多久後才會開始被執行。
val workRequest = OneTimeWorkRequestBuilder<LogWorker>()
.setInputData(workDataOf(LogWorker.MESSAGE to "This is an one time work"))
.setInitialDelay(1, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueue(workRequest)排程週期性工作
PeriodicWorkRequest 可以建立一個週期性被執行的WorkRequest。我們可以透過 PeriodicWorkRequestBuilder 來建立它,並且要指定 interval 時間和 flex 時間。
- Interval:是指每個週期的時間長度。不可小於 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS。
- Flex:是指工作會在這個時間內的任何時間點被啟動,並執行完畢。不可小於 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS。

val workRequest = PeriodicWorkRequestBuilder<LogWorker>(
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, TimeUnit.MILLISECONDS,
)
.setInputData(workDataOf(LogWorker.MESSAGE to "This is a periodic work"))
.setInitialDelay(1, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueue(workRequest)工作的 Constraints
當工作有設定 Constraints 時,只有任務符合所有條件時,才會被執行。可以設定的條件如下:
- NetworkType:當符合需求的網路狀態,工作才會被執行。
- CONNECTED
- METERED
- NOT_REQUIRED
- NOT_ROAMING
- TEMPORARILY_UNMETERED
- UNMETERED
- BatteryNotLow:設定為 true 時,當裝置不在 low battery 模式時,工作才會被執行。
- RequiresCharging:設定為 true 時,當裝置在充電時,工作才會被執行。
- DeviceIdle:設定為 true 時,當裝置在閒置時,工作才會被執行。
- StorageNotLow:設定為 true 時,當裝置的儲存空間不低時,工作才會被執行。
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest = OneTimeWorkRequestBuilder<LogWorker>()
.setInputData(workDataOf(LogWorker.MESSAGE to "This is an one time work with network constraint"))
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueue(workRequest)工作重試
在 doWork() 中,有可能因為某些原因無法執行工作。但是,我們不想直接回傳 Result.failure(),反而希望 WorkManager 可以在重試工作。這時我們可以回傳 Result.retry()。在 doWork() 中,還可以透過 runAttemptCount 取得已經重試的次數。
package com.waynestalk.workmanagerexample
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
class RetryLogWork(appContext: Context, workerParameters: WorkerParameters) :
CoroutineWorker(appContext, workerParameters) {
companion object {
const val MESSAGE = "message"
}
private val tag = javaClass.canonicalName
override suspend fun doWork(): Result {
val message = inputData.getString(MESSAGE) ?: return Result.failure()
Log.d(tag, "$message, retry count: $runAttemptCount")
return Result.retry()
}
}並且在建立 WorkRequest 時,還要設定 backoff delay 和 backoff policy。
- Backoff delay:第一次重試前,需要的等待時間。不可小於 OneTimeWorkRequest.MIN_BACKOFF_MILLIS。
- Backoff policy:每次重試時,backoff delay 增加的方式。
- LINEAR:Backoff delay * runAttemptCount
- EXPONENTIAL:Backoff delay * (2 ^ (runAttemptCount – 1))
val workRequest = OneTimeWorkRequestBuilder<RetryLogWork>()
.setInputData(workDataOf(RetryLogWork.MESSAGE to "This is a retry work"))
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS,
)
.build()
WorkManager.getInstance(context).enqueue(workRequest);排程唯一性的工作
有時候我們會多次排程同一個工作,但是當已經有一個工作已經被排程時,我們可以讓新的工作取代現有的工作,或是直接忽略新的工作。呼叫 WorkManager.enqueueUniqueWork() 來排程唯一性的一次性工作,或是呼叫 WorkManager.enqueueUniquePeriodicWork() 來排程唯一性的週期性工作。
ExistingWorkPolicy 為當已經有一個現有的工作在排程時,應該要採取的行為。
- REPLACE:以新工作取代現有工作。這個選項會取消現有工作。
- KEEP:保留現有工作並忽略新工作。
- APPEND:將新工作串連到現有工作的後面,並在現有工作結束後執行。如果現有工作變成 CANCELLED 或 FAILED,則新工作也會變為 CANCELLED 或 FAILED。
- APPEND_OR_REPLACE:與 APPEND 類似,差別在於,如果現有工作是 CANCELLED 或 FAILED,新工作仍會執行。
val workRequest = OneTimeWorkRequestBuilder<LogWorker>()
.setInputData(workDataOf(LogWorker.MESSAGE to "This is an unique one time work"))
.setInitialDelay(5, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
"unique_one_time",
ExistingWorkPolicy.REPLACE,
workRequest,
)排程一連串的工作
最後,WorkManager 還可以讓我們排程一連串的工作。首先要呼叫 WorkManager.beginWith(),之後都是呼叫 WorkManager.then()。這兩個方法都可以接收一個 WorkRequest 或是 List<WorkRequest>。當傳入的是 List<WorkRequest> 時,list 中的工作會被平行地執行,而且當 list 中的工作都執行完後,才會開始下一個 WorkRequest.then() 的工作。
val workRequest1 = OneTimeWorkRequestBuilder<LogWorker>()
.setInputData(workDataOf(LogWorker.MESSAGE to "Work 1"))
.build()
val workRequest2 = OneTimeWorkRequestBuilder<LogWorker>()
.setInputData(workDataOf(LogWorker.MESSAGE to "Work 2"))
.build()
val workRequest3 = OneTimeWorkRequestBuilder<LogWorker>()
.setInputData(workDataOf(LogWorker.MESSAGE to "Work 3"))
.build()
WorkManager.getInstance(context)
.beginWith(listOf(workRequest1, workRequest2))
.then(workRequest3)
.enqueue()
}結語
WorkManager 保證我們想要執行的程式碼會被執行,即使 device 重啟、app 重啟、或是 app 被終止。所以對於當 app 要儲存重要的資料時,WorkManager 提供了相當好的解決方案。









