Zarządzanie pracą

Po zdefiniowaniuWorkerWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequestWorkRequest 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ąć powielania. 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 dodać to samo zadanie do kolejki, 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 pracy.
  • 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 wykorzystaniu unikalnych prac 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 w kolejce jest już zadanie sendLogs, 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 łańcuch 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 poinformować WorkManager, jakie działanie ma podjąć 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, 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 jak APPEND, z tym że nie zależy od stanu pracy wymaganej. Jeśli istniejąca praca jest 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 obiektu WorkInfo, który zawiera 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 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 na numer 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 procesu Worker

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 termin 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ą w posiadaniu procesu roboczego. 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 się zatrzymuje.

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 obiekcie Worker 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 obiekt Worker, który otrzymał sygnał onStop, ponieważ jest on już uznawany za zatrzymany.

Obserwowanie stanu przyczyny zatrzymania

Aby sprawdzić, dlaczego Worker zostało zatrzymane, możesz zarejestrować przyczynę zatrzymania, wywołując funkcję 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());
        }
  });