定義工作要求

「入門指南」會說明如何建立簡易的 WorkRequest 並排入佇列。

本指南將說明如何定義及自訂 WorkRequest 物件以處理常見用途,例如如何:

  • 安排一次性與週期性作業
  • 設定工作限制,例如必須連上 Wi-Fi 或充電
  • 保證至少執行一項工作執行作業延遲
  • 設定重試和停止策略
  • 將輸入資料傳送至工作
  • 使用標記將相關工作分組

總覽

在 WorkManager 中透過 WorkRequest 定義工作。如要透過 WorkManager 安排任何工作,您必須先建立 WorkRequest 物件,然後將該物件排入佇列。

Kotlin

val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java

WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

WorkRequest 物件包含 WorkManager 安排和執行工作所需的所有資訊。當中包含工作執行必須符合的限制的排程資訊,例如延遲時間或重複間隔等排程、重試設定,以及在工作需要時加入輸入資料。

WorkRequest 本身是抽象基礎類別,這個類別是兩種延伸時做的類別,可用來建立要求 (OneTimeWorkRequestPeriodicWorkRequest)。正如其名稱所說,OneTimeWorkRequest 很適合用來排程非週期性的工作,而 PeriodicWorkRequest 則適用於排程週期性的重複工作。

安排一次性工作

如果是簡單的工作,無須額外設定,請使用靜態法 from

Kotlin

val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java

WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

如果是較為複雜的工作,您可以使用建構工具:

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

安排急件工作

WorkManager 2.7.0 推出了急件工作的概念。這可讓 WorkManager 執行重要工作,同時讓系統進一步控管資源的存取權。

急件工作以下列特質聞名:

  • 重要性:急件工作符合使用者重視或由使用者啟動的工作。
  • 速度:急件工作最適合可立即開始並在幾分鐘內完成的簡短工作。
  • 配額:限制執行作業時間的系統層級配額,用以決定急件工作是否可啟動。
  • 電源管理電源管理限制 (例如省電模式和休眠) 較不可能影響急件工作。
  • 延遲:系統會立即執行急件工作,前提是系統目前的工作負載可以。這代表延遲時間須敏感,且不能先為後面的執行安排。

急件工作可能的應用方式則是使用者想在聊天應用程式中傳送訊息或附加圖片時。同樣地,處理付款或訂閱流程的應用程式可能也會希望使用急件工作。這是因為這些工作對使用者而言很重要,會在背景快速執行,且必須立即開始,即使使用者關閉應用程式也應繼續執行

配額

系統必須將執行時間分配給急件工作,才能執行。執行作業時間是沒有上限的。而是為每個應用程式分配執行時間配額。如果應用程式使用執行作業的時間達到配額,則在配額更新前,您就無法再執行急件工作。如此一來,Android 就能在應用程式之間更有效地平衡資源。

應用程式可用的執行時間容量取決於「待命值區」和程序重要性。

您可以決定執行配額不允許的急件工作立即執行時,要執行的動作。詳情請參閱下方程式碼片段。

執行急件作業

從 WorkManager 2.7 開始,應用程式可以呼叫 setExpedited(),以宣告用急件工作會讓 WorkRequest 運作盡可能快。以下程式碼片段提供 setExpedited() 的使用範例:

Kotlin

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
    .setInputData(inputData)
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build();

在本範例中,我們會初始化 OneTimeWorkRequest 的例項並呼叫 setExpedited()。這項要求隨後會變成急件處理。在配額允許的情況下,系統會立即在背景執行。如果已使用配額,OutOfQuotaPolicy 參數會指出應以正常的非快速工作方式執行要求。

回溯相容性和前景服務

為了維持急件工作的回溯相容性,WorkManager 可能會在 Android 12 以下的平台版本上執行前景服務。前景服務可以向使用者顯示通知。

工作站中的 getForegroundInfoAsync()getForegroundInfo() 方法可讓 WorkManager 在 Android 12 前呼叫 setExpedited() 時顯示通知。

如要要求系統以急件工作的形式執行工作,則所有 ListenableWorker 都必須導入 getForegroundInfo 方法。

如果您指定 Android 12 或以上的版本,您仍可透過對應的 setForeground 方法使用前景服務。

工作站

工作站無法得知自己的作業是否加速。但是,當 WorkRequest 加速,某些版本的 Android 手機會顯示通知。

如要啟用這項功能,WorkManager 會提供 getForegroundInfoAsync() 方法,您必須強制執行該方法,這樣工作管理員才能適時顯示通知,啟動 ForegroundService

CoroutineWorker

如果您使用 CoroutineWorker,則必須導入 getForegroundInfo()。然後在 doWork() 內將金鑰傳送至 setForeground()。系統將會在 12 版之前的 Android 版本中建立通知。

請參考以下範例:

  class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
   CoroutineWorker(appContext, workerParams) {

   override suspend fun getForegroundInfo(): ForegroundInfo {
       return ForegroundInfo(
           NOTIFICATION_ID, createNotification()
       )
   }

   override suspend fun doWork(): Result {
       TODO()
   }

    private fun createNotification() : Notification {
       TODO()
    }

}

配額政策

您可以控制當應用程式達到其執行配額時,急件工作的運作情況。如要繼續,你可以傳送 setExpedited()

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST:造成工作如一般工作要求的執行。以上程式碼片段示範了這項做法。
  • OutOfQuotaPolicy.DROP_WORK_REQUEST:如果配額不足,將要求取消。

延遲急件作業

當系統叫用給定的急件工作後,系統會盡快執行。不過,如同其他類型的工作,系統可能也會延遲新的急件工作,例如下列情況:

  • 載入:系統負載過高,當系統執行太多工作或系統記憶體不足時,就有可能發生這個情況。
  • 配額:超出急件工作配額上限。急件工作會使用以「應用程式待機分組」為基礎的配額系統,並限制在滾動時間區間內的最大執行時間。急件工作耗用的配額比其他類型的背景工作所使用的配額更為限制。

安排定期性作業

您的應用程式有時可能會需要定期執行特定工作。例如,您可以定期備份資料、在應用程式中下載最新內容,或是將記錄檔上傳至伺服器。

使用 PeriodicWorkRequest 來建立 WorkRequest 物件時,系統會定期執行:

Kotlin

val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java

PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

在這個例子中,作業是以一小時的時間間隔排定工作。

間隔時間範圍是指間隔下限。工作站的確切執行時間取決於您在 WorkRequest 物件中使用的限制,以及系統執行的最佳化作業。

彈性的時間間隔

如果您的工作性質對執行時間來說較為敏感,則可將 PeriodicWorkRequest 設為在每個時間間隔內在彈性週期內執行,如圖 1 所示。

您可以設定定期工作的彈性間隔。您必須定義重複間隔,以及一個會在時間間隔結束時指定特定時間長度的彈性間隔。WorkManager 會在每個週期的彈性間隔期間嘗試執行您的工作。

圖 1. 圖表顯示工作執行時間彈性的重複間隔。

如要利用彈性期間定義定期工作,請在建立 PeriodicWorkRequest 時一併傳送 flexIntervalrepeatInterval。彈性期是從 repeatInterval - flexInterval 開始,直到間隔時間結束。

以下是在每一小時最近 15 分鐘內執行的週期性工作範例。

Kotlin

val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

Java

WorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class,
               1, TimeUnit.HOURS,
               15, TimeUnit.MINUTES)
           .build();

重複間隔必須大於或等於 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,而彈性間隔值必須大於或等於 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS

限制條件對定期工作的影響

您可以將限制條件套用到定期工作。舉例來說,您可以在工作要求中新增一項限制,讓系統只在使用者的裝置充電時才會執行工作。在這種情況下,即使定義的重複間隔經過傳遞,PeriodicWorkRequest 必須等到符合這項條件後才會執行。這可能會導致您工作的特定執行作業延遲,甚至在執行階段未滿足條件時直接略過。

工作限制條件

限制條件會延遲工作,直到達成最佳條件為止。WorkManager 可以使用以下限制。

NetworkType 限制工作執行所需的網路類型。例如 Wi-Fi (UNMETERED)。
BatteryNotLOW 如果設為 true,當裝置處於低耗電模式時,裝置將無法執行。
RequiresCharging 如果設為 true,系統只會在裝置充電時執行你的工作。
DeviceIdle 如果設為 true,使用者的裝置必須處於閒置狀態才能運作。如要執行批次作業,避免對使用者裝置上運行的其他應用程式造成負面影響,這類設定就非常實用。
StorageNotLOW 如果設為 true,當使用者的裝置儲存空間不足時,裝置將無法執行。

如要建立一組限制並將其與部分工作建立關聯,請使用 Contraints.Builder() 建立 Constraints 例項,然後將其指派給您的 WorkRequest.Builder()

舉例來說,下列程式碼會建構工作要求,只在使用者的裝置充電且有 Wi-Fi 時執行:

Kotlin

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

Java

Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresCharging(true)
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();

如果指定多個限制條件,則只有在符合所有限制時,系統才會執行您的工作。

如果工作在執行期間未達到限制,WorkManager 就會停止工作站。符合所有限制後,系統就會重試作業。

延遲工作

如果您的工作沒有限制,或者工作已排入佇列,即符合所有限制,系統可能會選擇立即執行工作。如果您不想立即執行工作,您可以指定在最短初始延遲時間後開始工作。

以下範例說明如何將工作排入佇列後 10 分鐘執行。

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java

WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

以下範例說明如何設定 OneTimeWorkRequest 的初始延遲,但也可以設定 PeriodicWorkRequest 的初始延遲。在這種情況下,只有第一次的週期性工作會延遲。

重試和輪詢政策

如果您需要 WorkManager 重試您的工作,可以從工作站傳回 Result.retry()。接著,您的工作將根據輪詢延遲時間輪詢政策重新安排時間。

  • 「輪詢延遲時間」會指定在第一次嘗試後,嘗試重試工作前應等待的最短時間。這個值不得超過 10 秒 (或 MIN_BACKOFF_MILLIS)。

  • 輪詢政策會定義輪詢延遲情形如何隨著時間增加的重試作業。WorkManager 支援 2 項輪詢政策,LINEAREXPONENTIAL

每個工作要求都有輪詢政策和輪詢延遲時間。預設政策為 EXPONENTIAL,延遲時間為 30 秒,但您可以在工作要求設定中覆寫這項設定。

以下是自訂輪詢延遲和政策的範例。

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setBackoffCriteria(
                       BackoffPolicy.LINEAR,
                       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                       TimeUnit.MILLISECONDS)
               .build();

在這個範例中,輪詢持續時間下限會設為允許下限值,也就是 10 秒。這項政策設為 LINEAR 時,每次重試時,重試間隔會增加約 10 秒。例如,第一次執行完成 Result.retry() 會在 10 秒後嘗試重試,如果隨後繼續傳回 20、30、40,依此類推 Result.retry()。如果輪詢政策設為 EXPONENTIAL,則重試的時間長度將近 20、40、80 等等。

標籤運作

每個工作要求都有一組專屬 ID,可用於識別工作,以便取消工作或觀察進度

如果您有一組邏輯相關的工作,您會發現標記這些工作項目會非常有幫助。標記可讓一組的工作一起運作。

舉例來說,WorkManager.cancelAllWorkByTag(String) 會取消具有特定標記的所有工作要求,而 WorkManager.getWorkInfosByTag(String) 會傳回可用於查看目前工作狀態的 WorkInfo 物件清單。

以下程式碼示範如何在工作中加入「清理」標記:

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

最後,可在單一工作要求中可新增多個標記。這些標記會在內部以一組字串的形式儲存。如要取得與 WorkRequest 相關聯的一組標記,您可以使用 WorkInfo.getTag()

Worker 類別,您可以透過 ListenableWorker.getTag() 擷取其一組標記。

指派輸入資料

您的工作可能需要輸入資料,才能正常運作。例如,處理上傳圖片可能需要的影像的 URI 做為輸入內容。

輸入值會以鍵/值組合儲存在 Data 物件中,而且可以在工作要求中設定。WorkManager 會在執行工作時將輸入 Data 傳送至您的工作。Worker 類別會呼叫 Worker.getInputData() 來存取輸入引數。以下程式碼會示範如何建立需要輸入資料的 Worker 例項,以及如何在工作要求中傳送這項資料。

Kotlin

// Define the Worker requiring input
class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput =
           inputData.getString("IMAGE_URI") ?: return Result.failure()

       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf(
       "IMAGE_URI" to "http://..."
   ))
   .build()

Java

// Define the Worker requiring input
public class UploadWork extends Worker {

   public UploadWork(Context appContext, WorkerParameters workerParams) {
       super(appContext, workerParams);
   }

   @NonNull
   @Override
   public Result doWork() {
       String imageUriInput = getInputData().getString("IMAGE_URI");
       if(imageUriInput == null) {
           return Result.failure();
       }

       uploadFile(imageUriInput);
       return Result.success();
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
      new OneTimeWorkRequest.Builder(UploadWork.class)
           .setInputData(
               new Data.Builder()
                   .putString("IMAGE_URI", "http://...")
                   .build()
           )
           .build();

同樣地,Data 類別可用來輸出傳回值。如要進一步瞭解輸入和輸出資料,請參閱「輸入參數和傳回的值」一節。

後續步驟

「狀態與觀察項目」頁面將進一步說明工作狀態,以及如何監控工作進度。