管理工作

定義 WorkerWorkRequest 後,最後一步就是將工作加入佇列。要將工作加入佇列,最簡單方法就是呼叫 WorkManager enqueue() 方法,並傳遞您要執行的 WorkRequest

Kotlin

val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)

Java

WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);

因此,建議您在將工作排入佇列時謹慎小心,以免重複。 例如,應用程式可能會嘗試每 24 小時將記錄上傳至後端服務。如果不留意,即便該項工作只需要執行一次,您仍有可能將相同的工作多次排入佇列。 為達到這項目標,您可以在安排工作時將其設為不重複工作

不重複工作

「不重複的工作」是強大有效的概念,可確保每個執行個體只能有一個特定的「名稱」。與 ID 不同的是,不重複名稱方便使用者辨識而且是由開發人員指定,而不是由 WorkManager 自動產生。不同於標記,不重複名稱只會與單一工作例項相關聯。

不重複工作可套用至一次性和週期性工作。取決於您要安排週期性或一次性工作,您可以呼叫下列其中一種方法,建立不重複工作序列。

這兩種方法都接受 3 個引數:

  • uniqueWorkName - 用來識別工作要求的 String
  • existingWorkPolicy - 如果已有該專屬名稱的相關作業尚未完成,enum 會指示 WorkManager 處置方式。詳情請參閱衝突解決政策
  • work - 要安排的 WorkRequest

我們可以透過不重複工作,解決先前提到的重複安排問題。

Kotlin

val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

Java

PeriodicWorkRequest sendLogsWorkRequest = new
      PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
              .setConstraints(new Constraints.Builder()
              .setRequiresCharging(true)
          .build()
      )
     .build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

現在,如果 sendLogs 工作已在佇列中,而程式碼同時執行,系統仍會保留現有工作,不會新增任何工作。

如果您需要逐步建構較長的工作鏈,不重複工作序列也相當實用。例如,相片編輯應用程式可能會讓使用者復原很長的動作。每項復原作業可能需要一些時間才能完成,但必須以正確的順序執行。在這種情況下,應用程式可以建立「復原」鏈結,並視需要將每項復原作業附加到鏈結中。詳情請參閱將工作鏈結在一起一文。

衝突解決政策

安排不重複工作時,您必須告知 WorkManager 在發生衝突時應採取的動作。如要這麼做,請在將工作加入佇列時傳遞列舉值。

針對一次性工作,您必須提供支援 4 個處理衝突選項的 ExistingWorkPolicy

  • REPLACE:以新工作取代現有工作。這個選項會取消現有工作。
  • KEEP:保留現有工作並忽略新工作。
  • APPEND:將新工作附加到現有工作的結尾。這項政策會導致新工作鏈結到現有的工作,並在現有工作結束後執行。

現有工作會成為新工作的「先決條件」。如果現有工作變成 CANCELLEDFAILED,則新工作也會變為 CANCELLEDFAILED。如果您希望新工作不受現有工作的狀態影響而一律執行,請改用 APPEND_OR_REPLACE

  • APPEND_OR_REPLACEAPPEND 的功能類似,差別在於前者不受先決條件工作狀態的影響。如果現有工作是 CANCELLEDFAILED,新工作仍會執行。

針對週期性工作,您必須提供支援以下 2 個選項的 ExistingPeriodicWorkPolicyREPLACEKEEP。這些選項的運作方式與 ExistingWorkPolicy 相同。

觀察你的工作

將工作加入佇列後,您隨時可以透過 nameid 或與該工作相關的 tag 查詢 WorkManager,以檢查工作狀態。

Kotlin

// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

Java

// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>

查詢會傳回 WorkInfo 物件的 ListenableFuture,包括工作的 id、標記、目前的 State,以及使用 Result.success(outputData) 的任何輸出資料集。

每一種方法的 LiveDataFlow 變化版本都可讓您註冊事件監聽器,藉此觀察 WorkInfo 的變更。舉例來說,如果您想要在部分工作順利完成後向使用者顯示訊息,可以按照下列方式設定:

Kotlin

workManager.getWorkInfoByIdFlow(syncWorker.id)
          .collect{ workInfo ->
              if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
                  Snackbar.make(requireView(),
                      R.string.work_completed, Snackbar.LENGTH_SHORT)
                      .show()
              }
          }

Java

workManager.getWorkInfoByIdLiveData(syncWorker.id)
        .observe(getViewLifecycleOwner(), workInfo -> {
    if (workInfo.getState() != null &&
            workInfo.getState() == WorkInfo.State.SUCCEEDED) {
        Snackbar.make(requireView(),
                    R.string.work_completed, Snackbar.LENGTH_SHORT)
                .show();
   }
});

複雜的工作查詢

WorkManager 2.4.0 以上版本支援使用 WorkQuery 物件,對佇列工作執行複雜的查詢。WorkQuery 支援透過標記、狀態和不重複工作名稱的組合來查詢工作。

以下範例會說明如何找到包含狀態為 FAILEDCANCELLED 的「syncTag」標記,且不重複工作名稱為「preProcess」或「sync」的所有工作。

Kotlin

val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

Java

WorkQuery workQuery = WorkQuery.Builder
       .fromTags(Arrays.asList("syncTag"))
       .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(Arrays.asList("preProcess", "sync")
     )
    .build();

ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);

WorkQuery 中的每個元件 (標記、狀態或名稱) 都具有 AND 關係。而元件中的每個值都具有 OR 邏輯關係,例如:(name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)

WorkQuery 也適用於 LiveData 對等項目 getWorkInfosLiveData(),以及 Flow 對等項目 getWorkInfosFlow()

取消及停止工作

如果不再需要執行已排入佇列的工作,您可要求取消工作。要取消工作,可以使用 nameid 或與其相關聯的 tag

Kotlin

// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")

Java

// by id
workManager.cancelWorkById(syncWorker.id);

// by name
workManager.cancelUniqueWork("sync");

// by tag
workManager.cancelAllWorkByTag("syncTag");

實際上,WorkManager 會檢查工作的 State。如果工作已經完成,則不會有任何異動。反之,工作狀態會變更為 CANCELLED,且日後也不會執行該工作。取決於這項工作的所有 WorkRequest 工作也都會變為 CANCELLED

RUNNING 工作會收到對 ListenableWorker.onStopped() 的呼叫。覆寫這個方法即可處理任何潛在的清理作業。詳情請參閱停止執行中的工作站

停止執行中的工作站

以下幾個不同的原因可能會導致 WorkManager 停止執行 Worker

  • 您明確要求取消工作 (例如,透過呼叫 WorkManager.cancelWorkById(UUID) 取消)。
  • 不重複工作來說,您明確透過 REPLACEExistingWorkPolicy,將新的 WorkRequest 加入佇列。系統會立即將舊的 WorkRequest 視為取消,
  • 也不會再滿足您的工作限制條件。
  • 系統因故指示應用程式停止工作。如果超過 10 分鐘的執行期限,就可能發生這種情況。這項工作會安排於稍後重試。

在下列情況下,工作站將會停止運作。

您應配合取消所有進行中的工作,並釋出工作站所占用的任何資源。舉例來說,您目前應關閉資料庫和檔案的開放式控制代碼。有兩種機制可讓您瞭解工作站何時停止運作。

onStopped() 回呼

工作站停止後,WorkManager 就會叫用 ListenableWorker.onStopped()。請覆寫這個方法,以關閉您可能占用的任何資源。

isStopped() 屬性

您可以呼叫 ListenableWorker.isStopped() 方法,檢查工作站是否已停止。如果您在工作站中執行長時間或重複的作業,請務必經常查看這個屬性,並將該屬性視為盡快停止工作的信號。

注意:WorkManager 會忽略由收到 onStop 信號的工作站所設定的 Result,因為系統已將該工作站視為停止。

觀察停止原因狀態

如要偵錯 Worker 停止的原因,可以呼叫 WorkInfo.getStopReason() 記錄停止原因:

Kotlin

workManager.getWorkInfoByIdFlow(syncWorker.id)
  .collect { workInfo ->
      if (workInfo != null) {
        val stopReason = workInfo.stopReason
        logStopReason(syncWorker.id, stopReason)
      }
  }

Java

  workManager.getWorkInfoByIdLiveData(syncWorker.id)
    .observe(getViewLifecycleOwner(), workInfo -> {
        if (workInfo != null) {
          int stopReason = workInfo.getStopReason();
          logStopReason(syncWorker.id, workInfo.getStopReason());
        }
  });