Управление работой

После определения Worker и WorkRequest последний шаг — поставить работу в очередь. Самый простой способ поставить работу в очередь — вызвать метод enqueue() класса WorkManager, передав ему WorkRequest , который нужно выполнить.

Котлин

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

Ява

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

Будьте осторожны при добавлении задач в очередь, чтобы избежать дублирования. Например, приложение может пытаться загружать свои журналы в бэкенд-сервис каждые 24 часа. Если вы не будете внимательны, вы можете добавлять одну и ту же задачу в очередь много раз, даже если она должна быть выполнена всего один раз. Для достижения этой цели вы можете запланировать задачу как уникальную .

Уникальная работа

Уникальная работа — это мощная концепция, гарантирующая наличие только одного экземпляра работы с определённым именем в любой момент времени. В отличие от идентификаторов, уникальные имена понятны человеку и указываются разработчиком, а не генерируются автоматически WorkManager. В отличие от тегов , уникальные имена связаны только с одним экземпляром работы.

Уникальная работа может применяться как к разовым, так и к периодическим работам. Вы можете создать уникальную последовательность работ, вызвав один из этих методов, в зависимости от того, планируете ли вы повторяющуюся или разовую работу.

Оба эти метода принимают 3 аргумента:

  • uniqueWorkNameString используемая для уникальной идентификации запроса на работу.
  • existingWorkPolicyenum , сообщающее WorkManager, что делать, если уже существует незавершённая цепочка работ с этим уникальным именем. Подробнее см. в политике разрешения конфликтов .
  • work - WorkRequest для планирования.

Используя уникальную работу, мы можем исправить проблему дублирования расписания, о которой упоминалось ранее.

Котлин

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

Ява

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, какие действия следует предпринять при возникновении конфликта. Это делается путём передачи перечисления при постановке работы в очередь.

Для разовой работы вы предоставляете ExistingWorkPolicy , которая поддерживает 4 варианта обработки конфликта.

  • REPLACE существующую работу новой. Эта опция отменяет существующую работу.
  • KEEP существующую работу и игнорируйте новую работу.
  • APPEND новую работу в конец существующей. Эта политика привяжет вашу новую работу к существующей и запустит её после завершения.

Существующая работа становится предпосылкой для новой работы. Если текущая работа становится CANCELLED или FAILED , новая работа также становится CANCELLED или FAILED . Если вы хотите, чтобы новая работа выполнялась независимо от статуса существующей, используйте APPEND_OR_REPLACE .

  • Функция APPEND_OR_REPLACE работает аналогично функции APPEND , за исключением того, что она не зависит от статуса предварительных работ. Если текущая работа имеет значение CANCELLED или FAILED , новая работа продолжает выполняться.

Для работы за период вы предоставляете ExistingPeriodicWorkPolicy , который поддерживает два параметра: REPLACE и KEEP . Эти параметры работают так же, как их аналоги ExistingWorkPolicy .

Наблюдение за вашей работой

В любой момент после постановки работы в очередь вы можете проверить ее статус, отправив запрос в WorkManager по ее name , id или связанному с ней tag .

Котлин

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

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

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

Ява

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

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

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

Запрос возвращает ListenableFuture объекта WorkInfo , который включает id работы, ее теги, ее текущее State и любой выходной набор данных, использующий Result.success(outputData) .

Варианты LiveData и Flow каждого из методов позволяют отслеживать изменения в WorkInfo , регистрируя прослушиватель. Например, если вы хотите отображать сообщение пользователю после успешного завершения какой-либо работы, это можно настроить следующим образом:

Котлин

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

Ява

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 поддерживает запросы к заданиям по комбинации их тегов, состояния и уникального имени задания.

В следующем примере показано, как можно найти все работы с тегом «syncTag» , которые находятся в состоянии FAILED или CANCELLED и имеют уникальное имя работы « preProcess » или « sync ».

Котлин

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)

Ява

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() .

Отмена и остановка работы

Если вам больше не нужно выполнять ранее поставленную в очередь задачу, вы можете запросить её отмену. Отменить задачу можно по её name , id или связанному с ней tag .

Котлин

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

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

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

Ява

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

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

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

WorkManager проверяет State работы. Если работа уже завершена , ничего не происходит. В противном случае состояние работы меняется на CANCELLED , и она больше не будет выполняться. Все задания WorkRequest , зависящие от этой работы, также будут CANCELLED .

RUNNING процесс получает вызов метода ListenableWorker.onStopped() . Переопределите этот метод для обработки потенциальной очистки. Подробнее см. в разделе «Остановка выполняющегося процесса» .

Остановить работающего работника

Существует несколько причин, по которым работающий Worker может быть остановлен WorkManager:

  • Вы явно запросили отмену (например, вызвав WorkManager.cancelWorkById(UUID) ).
  • В случае уникальной работы вы явно добавили в очередь новый WorkRequest с ExistingWorkPolicy , равным REPLACE . Старый WorkRequest немедленно считается отмененным.
  • Ограничения вашей работы больше не соблюдаются.
  • По какой-то причине система дала указание вашему приложению остановить работу. Это может произойти, если вы превысите срок выполнения в 10 минут. Повторная попытка запланирована на более позднее время.

При выполнении этих условий ваш Worker останавливается.

Вам следует совместно прервать всю текущую работу и освободить все ресурсы, удерживаемые вашим Worker. Например, на этом этапе следует закрыть открытые дескрипторы баз данных и файлов. Существует два механизма, позволяющих определить, останавливается ли ваш Worker.

обратный вызов onStopped()

WorkManager вызывает метод ListenableWorker.onStopped() сразу после остановки вашего Worker. Переопределите этот метод, чтобы закрыть все ресурсы, которые вы могли удерживать.

свойство isStopped()

Вы можете вызвать метод ListenableWorker.isStopped() , чтобы проверить, остановлен ли ваш воркер. Если вы выполняете в воркере длительные или повторяющиеся операции, следует регулярно проверять это свойство и использовать его как сигнал для скорейшей остановки работы.

Примечание: WorkManager игнорирует Result , установленный Worker, получившим сигнал onStop, поскольку Worker уже считается остановленным.

Наблюдать за состоянием причины остановки

Чтобы отладить причину остановки Worker , вы можете зарегистрировать причину остановки, вызвав WorkInfo.getStopReason() :

Котлин

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

Ява

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