Zarządzanie pracą

Gdy zdefiniujesz Worker i WorkRequest, ostatnim krokiem jest dodanie zadania do kolejki. Najprostszym sposobem na dodanie zadania do kolejki jest wywołanie metody enqueue() WorkManagera i przekazanie WorkRequest, które chcesz uruchomić.

Kotlin

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

Java

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

Dodając zadanie do kolejki, zachowaj ostrożność, aby uniknąć duplikowania. Na przykład aplikacja może próbować przesyłać swoje logi do usługi backendu co 24 godziny. Jeśli nie będziesz uważać, możesz dodać to samo zadanie do kolejki wiele razy, mimo że powinno ono zostać wykonane tylko raz. Aby to osiągnąć, możesz zaplanować zadanie jako unikalne zadanie.

Unikalne zadanie

Unikalne zadanie to zaawansowana koncepcja, która gwarantuje, że w danym momencie będzie istniała tylko 1 instancja zadania o określonej nazwie. W przeciwieństwie do identyfikatorów unikalne nazwy są czytelne dla człowieka i określane przez dewelopera, a nie generowane automatycznie przez WorkManagera. W przeciwieństwie do tagów unikalne nazwy są powiązane tylko z 1 instancją zadania.

Unikalne zadanie można zastosować zarówno do zadań jednorazowych, jak i okresowych. Aby utworzyć unikalną sekwencję zadań, wywołaj jedną z tych metod w zależności od tego, czy planujesz zadanie powtarzające się, czy jednorazowe.

Obie te metody przyjmują 3 argumenty:

  • uniqueWorkNameString używany do jednoznacznego identyfikowania żądania zadania.
  • existingWorkPolicyenum, który informuje WorkManagera, co ma zrobić , jeśli istnieje już niedokończony łańcuch zadań o tej unikalnej nazwie. Więcej informacji znajdziesz w zasadach dotyczących rozwiązywania konfliktów.
  • workWorkRequest, które ma zostać zaplanowane.

Dzięki unikalnemu zadaniu możemy rozwiązać problem z duplikowaniem planowania, o którym wspomnieliśmy wcześniej.

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);

Jeśli kod zostanie uruchomiony, gdy zadanie sendLogs jest już w kolejce, istniejące zadanie zostanie zachowane, a nowe nie zostanie dodane.

Unikalne sekwencje zadań mogą być też przydatne, jeśli musisz stopniowo tworzyć długi łańcuch zadań. Na przykład aplikacja do edycji zdjęć może umożliwiać użytkownikom cofanie długiego łańcucha działań. Każda z tych operacji cofania może potrwać jakiś czas, ale muszą one zostać wykonane w odpowiedniej kolejności. W takim przypadku aplikacja może utworzyć łańcuch „cofnij” i w razie potrzeby dołączyć do niego każdą operację cofania. Więcej informacji znajdziesz w artykule Łączenie zadań w łańcuchy.

Zasady rozwiązywania konfliktów

Podczas planowania unikalnego zadania musisz poinformować WorkManagera, jakie działanie ma podjąć w przypadku konfliktu. Aby to zrobić, podczas dodawania zadania do kolejki przekaż enum.

W przypadku zadania jednorazowego podaj ExistingWorkPolicy, które obsługuje 4 opcje rozwiązywania konfliktu.

  • REPLACE zastępuje istniejące zadanie nowym. Ta opcja anuluje istniejące zadanie.
  • KEEP zachowuje istniejące zadanie i ignoruje nowe.
  • APPEND dodaje nowe zadanie na końcu istniejącego. Ta zasada spowoduje, że nowe zadanie zostanie połączone z istniejącym i będzie wykonywane po jego zakończeniu.

Istniejące zadanie staje się wymaganiem wstępnym dla nowego zadania. Jeśli istniejące zadanie zostanie CANCELLED lub FAILED, nowe zadanie również zostanie CANCELLED lub FAILED. Jeśli chcesz, aby nowe zadanie było wykonywane niezależnie od stanu istniejącego zadania, użyj zamiast tego APPEND_OR_REPLACE.

  • APPEND_OR_REPLACE działa podobnie jak APPEND, ale nie zależy od wymagania wstępnego stanu pracy. Jeśli istniejące zadanie zostanie CANCELLED lub FAILED, nowe zadanie nadal będzie wykonywane.

W przypadku zadania okresowego podaj ExistingPeriodicWorkPolicy, które obsługuje 2 opcje: REPLACE i KEEP. Te opcje działają tak samo jak ich odpowiedniki w ExistingWorkPolicy.

Obserwowanie zadań

W dowolnym momencie po dodaniu zadania do kolejki możesz sprawdzić jego stan, wysyłając zapytanie do WorkManagera według jego name, id lub tag powiązanego z nim.

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>>

Zapytanie zwraca ListenableFuture obiektu WorkInfo, który zawiera id zadania, jego tagi, jego bieżący State i wszelkie wyjściowe zbiory danych używające Result.success(outputData).

Warianty LiveData i Flow każdej z metod umożliwiają obserwowanie zmian w WorkInfo przez zarejestrowanie odbiorcy. Jeśli na przykład chcesz wyświetlić użytkownikowi komunikat, gdy jakieś zadanie zostanie wykonane, możesz skonfigurować je w ten sposób:

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();
   }
});

Złożone zapytania dotyczące zadań

WorkManager w wersji 2.4.0 i nowszych obsługuje złożone zapytania dotyczące zadań w kolejce za pomocą WorkQuery obiektów. WorkQuery umożliwia wysyłanie zapytań o zadania na podstawie kombinacji ich tagów, stanu i unikalnej nazwy zadania.

Poniższy przykład pokazuje, jak znaleźć wszystkie zadania z tagiem „syncTag”, które są w stanie FAILED lub CANCELLED i mają unikalną nazwę zadania „preProcess” lub „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);

Każdy komponent (tag, stan lub nazwa) w WorkQuery jest łączony z innymi za pomocą operatora AND. Każda wartość w komponencie jest OR-ed. Na przykład: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...).

WorkQuery działa też z odpowiednikiem LiveData, getWorkInfosLiveData(), oraz odpowiednikiem Flow, getWorkInfosFlow().

Anulowanie i zatrzymywanie zadań

Jeśli nie chcesz już, aby wcześniej dodane do kolejki zadanie zostało wykonane, możesz poprosić o jego anulowanie. Zadanie można anulować według jego name, id lub tag powiązanego z nim.

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 sprawdza State zadania. Jeśli zadanie zostało już zakończone, nic się nie stanie. W przeciwnym razie stan zadania zostanie zmieniony na CANCELLED i zadanie nie zostanie wykonane w przyszłości. Wszystkie zadania WorkRequest, które są zależne od tego zadania, również zostaną CANCELLED.

RUNNING zadanie otrzymuje wywołanie ListenableWorker.onStopped(). Zastąp tę metodę, aby obsłużyć potencjalne czyszczenie. Więcej informacji znajdziesz w artykule Zatrzymywanie działającego workera.

Zatrzymywanie działającego workera

Istnieje kilka powodów, dla których WorkManager może zatrzymać działającego Worker:

  • Wyraźnie poprosiłeś(-aś) o jego anulowanie (np. przez wywołanie WorkManager.cancelWorkById(UUID)).
  • W przypadku unikalnego zadania, wyraźnie dodałeś(-aś) do kolejki nowe WorkRequest z ExistingWorkPolicy ustawionym na REPLACE. Stare WorkRequest jest natychmiast uznawane za anulowane.
  • Ograniczenia zadania nie są już spełnione.
  • System polecił aplikacji zatrzymać zadanie z jakiegoś powodu. Może się to zdarzyć, jeśli przekroczysz limit czasu wykonania wynoszący 10 minut. Zadanie jest zaplanowane do ponowienia w późniejszym czasie.

W tych warunkach worker zostaje zatrzymany.

Powinieneś(-aś) przerwać wszystkie trwające zadania i zwolnić wszystkie zasoby, które są używane przez workera. W tym momencie należy na przykład zamknąć otwarte uchwyty do baz danych i plików. Masz do dyspozycji 2 mechanizmy, które pozwalają sprawdzić, kiedy worker się zatrzymuje.

Wywołanie zwrotne onStopped()

WorkManager invokes ListenableWorker.onStopped() natychmiast po zatrzymaniu workera. Zastąp tę metodę, aby zamknąć wszystkie zasoby, które mogą być używane.

Właściwość isStopped()

Możesz wywołać metodę ListenableWorker.isStopped(), aby sprawdzić, czy worker został już zatrzymany. Jeśli w workerze wykonujesz długotrwałe lub powtarzające się operacje, często sprawdzaj tę właściwość i używaj jej jako sygnału do jak najszybszego zatrzymania zadania.

Uwaga: WorkManager ignoruje Result ustawiony przez workera który otrzymał sygnał onStop, ponieważ worker jest już uznawany za zatrzymanego.