Zarządzanie pracą

Po zdefiniowaniuWorkerWorkRequest ostatnim krokiem jest umieszczenie zadania w kolejce. Najprostszym sposobem dodania zadania do kolejki jest wywołanie metody enqueue() WorkManager, przekazując 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);

Zachowaj ostrożność podczas dodawania zadań do kolejki, aby uniknąć duplikacji. Na przykład aplikacja może próbować przesyłać dzienniki do usługi backendu co 24 godziny. Jeśli nie zachowasz ostrożności, możesz wiele razy umieścić to samo zadanie w kolejce, mimo że powinno ono zostać wykonane tylko raz. Aby to osiągnąć, możesz zaplanować pracę jako niepowtarzalną.

Unikalne dzieło

Unikalna praca to zaawansowana koncepcja, która gwarantuje, że w danym momencie masz tylko 1 instancję pracy o określonej nazwie. W odróżnieniu od identyfikatorów unikalne nazwy są czytelne dla człowieka i określane przez dewelopera, a nie generowane automatycznie przez WorkManager. W przeciwieństwie do tagów unikalne nazwy są powiązane tylko z jedną instancją pracy.

Unikalne zadania można stosować zarówno w przypadku pracy jednorazowej, jak i okresowej. Możesz utworzyć unikalną sekwencję pracy, wywołując jedną z tych metod w zależności od tego, czy planujesz pracę cykliczną, czy jednorazową.

Obie te metody przyjmują 3 argumenty:

  • uniqueWorkName – String używany do jednoznacznego identyfikowania prośby o wykonanie zadania.
  • existingWorkPolicy – enum, który informuje WorkManager, co zrobić, jeśli istnieje już niedokończony łańcuch zadań o tej unikalnej nazwie. Więcej informacji znajdziesz w zasadach rozwiązywania konfliktów.
  • work – WorkRequest do zaplanowania.

Dzięki unikalnej pracy możemy rozwiązać wspomniany wcześniej problem z duplikowaniem harmonogramu.

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 i nie zostanie dodane nowe.

Unikalne sekwencje działań mogą być też przydatne, jeśli musisz stopniowo tworzyć długi ciąg zadań. Na przykład aplikacja do edycji zdjęć może umożliwiać użytkownikom cofnięcie długiego łańcucha działań. Każda z tych operacji cofania może zająć trochę czasu, ale musi być wykonana w odpowiedniej kolejności. W takim przypadku aplikacja może utworzyć łańcuch „cofnij” i w razie potrzeby dołączać do niego poszczególne operacje cofania. Więcej informacji znajdziesz w sekcji Łączenie pracy.

Zasady rozwiązywania konfliktów

Podczas planowania unikalnej pracy musisz określić, jakie działanie ma podjąć WorkManager w przypadku konfliktu. Aby to zrobić, podczas dodawania zadania do kolejki przekaż wyliczenie.

W przypadku pracy jednorazowej podajesz ExistingWorkPolicy, który obsługuje 4 opcje rozwiązywania konfliktu.

  • REPLACE istniejące z nowymi. Ta opcja anuluje bieżącą pracę.
  • KEEP istniejącą pracę i zignorować nową.
  • APPEND nową pracę na końcu istniejącej. Ta zasada spowoduje połączenie nowego zadania z istniejącym zadaniem, które będzie wykonywane po zakończeniu istniejącego zadania.

Dotychczasowa praca staje się wymaganiem wstępnym dla nowej pracy. Jeśli istniejąca praca stanie się CANCELLED lub FAILED, nowa praca również będzie CANCELLED lub FAILED. Jeśli chcesz, aby nowe zadanie było wykonywane niezależnie od stanu istniejącego zadania, użyj APPEND_OR_REPLACE.

  • APPEND_OR_REPLACE działa podobnie do APPEND, z tym że nie zależy od stanu pracy wymaganej. Jeśli istniejąca praca ma stan CANCELLED lub FAILED, nowa praca nadal będzie wykonywana.

W przypadku pracy okresowej podajesz wartość ExistingPeriodicWorkPolicy, która obsługuje 2 opcje: REPLACE i KEEP. Te opcje działają tak samo jak ich odpowiedniki w przypadku istniejących zasad dotyczących pracy.

Obserwowanie Twojej pracy

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

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 obiektów WorkInfo, które zawierają id pracy, jej tagi, bieżący State i wszystkie zbiory danych wyjściowych korzystające z Result.success(outputData).

Warianty LiveDataFlow każdej z metod umożliwiają obserwowanie zmian w WorkInfo przez zarejestrowanie odbiorcy. Jeśli na przykład chcesz wyświetlić użytkownikowi komunikat po pomyślnym zakończeniu jakiegoś zadania, możesz skonfigurować go 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();
   }
});

Zapytania dotyczące złożonych zadań

WorkManager w wersji 2.4.0 i nowszych obsługuje złożone zapytania dotyczące zadań w kolejce za pomocą obiektów WorkQuery. WorkQuery umożliwia wyszukiwanie zadań na podstawie kombinacji 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ę „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 AND-ony z pozostałymi. 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(), i odpowiednikiem Flow, getWorkInfosFlow().

Anulowanie i zatrzymywanie pracy

Jeśli nie chcesz już, aby wcześniej dodane do kolejki zadanie zostało wykonane, możesz poprosić o jego anulowanie. Zadanie może anulować name, id lub tag powiązany z tym zadaniem.

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 pracy. Jeśli zadanie jest już ukończone, nic się nie dzieje. W przeciwnym razie stan zadania zmieni się na CANCELLED i nie będzie ono już uruchamiane. Wszystkie zadaniaWorkRequest, które są zależne od tego zadania, również będą CANCELLED.

RUNNING służbowy odbiera połączenie ListenableWorker.onStopped(). Zastąp tę metodę, aby obsłużyć potencjalne czyszczenie. Więcej informacji znajdziesz w artykule Zatrzymywanie działającego procesu.

Zatrzymywanie uruchomionego pracownika

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

  • wyraźnie poprosisz o anulowanie (np. dzwoniąc pod numer WorkManager.cancelWorkById(UUID));
  • W przypadku unikalnej pracy wyraźnie umieszczasz w kolejce nowy element WorkRequestExistingWorkPolicy o wartości REPLACE. Stara subskrypcja WorkRequest zostanie natychmiast anulowana.
  • Ograniczenia dotyczące Twojej pracy nie są już spełnione.
  • System z jakiegoś powodu nakazał aplikacji przerwanie pracy. Może się to zdarzyć, jeśli przekroczysz czas wykonania wynoszący 10 minut. Praca zostanie zaplanowana do ponownego wykonania w późniejszym terminie.

W takich przypadkach instancja robocza zostanie zatrzymana.

Powinieneś wspólnie przerwać wszelkie trwające prace i zwolnić wszystkie zasoby, które są używane przez proces roboczy. Na przykład w tym momencie należy zamknąć otwarte uchwyty do baz danych i plików. Do dyspozycji masz 2 mechanizmy, które pozwalają sprawdzić, kiedy pracownik kończy pracę.

Wywołanie zwrotne onStopped()

WorkManager wywołuje ListenableWorker.onStopped() natychmiast po zatrzymaniu obiektu Worker. Zastąp tę metodę, aby zamknąć wszystkie zasoby, które mogą być w użyciu.

Właściwość isStopped()

Możesz wywołać metodę ListenableWorker.isStopped(), aby sprawdzić, czy pracownik został już zatrzymany. Jeśli w procesie roboczym wykonujesz długotrwałe lub powtarzalne operacje, często sprawdzaj tę właściwość i używaj jej jako sygnału do jak najszybszego zakończenia pracy.

Uwaga: WorkManager ignoruje wartość Result ustawioną przez proces roboczy, który otrzymał sygnał onStop, ponieważ jest on już uznawany za zatrzymany.