Definiowanie próśb o pracę

Z przewodnika dla początkujących dowiesz się, jak utworzyć prosty WorkRequest i umieścić go w kolejce.

Z tego przewodnika dowiesz się, jak definiować i dostosowywać obiekty WorkRequest pod kątem typowych przypadków użycia, takich jak:

  • Planowanie zadań jednorazowych i cyklicznych
  • Określ ograniczenia w pracy – np. wymaganie dostępu do Wi-Fi lub ładowania
  • Zagwarantowanie minimalnego opóźnienia w realizacji zadań.
  • Ustaw strategie ponawiania prób i powtórzeń
  • Przekazywanie danych wejściowych do pracy
  • Grupuj powiązane zadania za pomocą tagów

Przegląd

Zadanie jest definiowane w usłudze WorkManager przez WorkRequest. Aby zaplanować pracę z WorkManagerem, musisz najpierw utworzyć obiekt WorkRequest, a potem umieścić go w kolejce.

Kotlin


val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java


WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

Obiekt WorkRequest zawiera wszystkie informacje potrzebne przez WorkManager do planowania i uruchamiania zadań. Obejmuje on ograniczenia, które muszą być spełnione, aby można było wykonywać zadania, informacje o planowaniu, takie jak opóźnienia lub powtarzanie interwałów, konfiguracja ponownych prób. Mogą też zawierać dane wejściowe, jeśli są one potrzebne w Twojej pracy.

Sama w sobie WorkRequest jest abstrakcyjną klasą bazową. Istnieją 2 implementacje pochodne tej klasy, których możesz użyć do utworzenia żądania: OneTimeWorkRequest i PeriodicWorkRequest. Jak sama nazwa wskazuje, funkcja OneTimeWorkRequest przydaje się do planowania pracy stałej, a PeriodicWorkRequest – do planowania pracy powtarzanej w określonych odstępach czasu.

Planowanie pracy jednorazowej

Do prostej pracy, która nie wymaga dodatkowej konfiguracji, użyj metody statycznej from:

Kotlin


val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java


WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

Do bardziej złożonych zadań możesz użyć kreatora:

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

Planowanie prac przyspieszonych

W usłudze WorkManager 2.7.0 wprowadzono koncepcję pracy przyspieszonej. Dzięki temu usługa WorkManager będzie mogła wykonywać ważne zadania, a system da systemowi większą kontrolę nad dostępem do zasobów.

Praca przyspieszona wyróżnia się tymi cechami:

  • Ważność: przyspieszona praca pasuje do zadań, które są ważne dla użytkownika lub są inicjowane przez niego.
  • Szybkość: przyspieszona praca najlepiej sprawdza się w przypadku krótkich zadań, które rozpoczynają się od razu i kończą w ciągu kilku minut.
  • Limity: limit na poziomie systemu, który ogranicza czas wykonywania na pierwszym planie, określa, czy przyspieszone zadanie może zostać uruchomione.
  • Zarządzanie energią: ograniczenia dotyczące zarządzania energią, takie jak Oszczędzanie baterii czy Uśpienie, mają mniejsze szanse na przyspieszenie pracy.
  • Czas oczekiwania: system od razu wykonuje przyspieszoną pracę, pod warunkiem że pozwala na to bieżące zadanie systemu. Oznacza to, że są one wrażliwe na czas oczekiwania i nie można ich zaplanować w późniejszym czasie.

Przyspieszenie procesu może mieć miejsce w aplikacji do obsługi czatu, gdy użytkownik chce wysłać wiadomość lub załączony obraz. Aplikacja, która obsługuje proces płatności lub subskrypcji, również może chcieć przyspieszyć pracę. Dzieje się tak, ponieważ te zadania są ważne dla użytkownika, wykonywane szybko w tle, muszą się rozpoczynać od razu i nadal być wykonywane nawet po zamknięciu aplikacji przez użytkownika.

Limity

System musi przydzielić czas wykonywania do zadania przyspieszonego, aby zostało ono uruchomione. Czas wykonania nie jest nieograniczony. Każda aplikacja ma określony limit czasu działania. Gdy aplikacja wykorzysta swój czas wykonywania i osiągnie przydzielony limit, nie będzie można wykonywać przyspieszonych działań, dopóki limit nie zostanie odświeżony. Dzięki temu Android może skuteczniej równoważyć zasoby między aplikacjami.

Czas wykonywania dostępny dla aplikacji zależy od zasobnika gotowości i znaczenia procesu.

Możesz określić, co ma się zdarzyć, gdy limit wykonania nie pozwala na natychmiastowe uruchomienie przyspieszonego zadania. Szczegółowe informacje znajdziesz w poniższych fragmentach.

Wykonywanie przyspieszonych prac

Od wersji WorkManager 2.7 aplikacja może wywoływać setExpedited(), aby zadeklarować, że WorkRequest powinno działać najszybciej jak to możliwe za pomocą zadania przyspieszonego. Ten fragment kodu zawiera przykład użycia atrybutu setExpedited():

Kotlin

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
    .setInputData(inputData)
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build();

W tym przykładzie zainicjujemy instancję OneTimeWorkRequest i wywołamy ją setExpedited(). Taka prośba zostanie uznana za przyspieszoną pracę. Jeśli limit na to pozwala, natychmiast zacznie działać w tle. Po wykorzystaniu limitu parametr OutOfQuotaPolicy wskazuje, że żądanie powinno być wykonywane w zwykły sposób.

Zgodność wsteczna i usługi na pierwszym planie

Aby zachować wsteczną zgodność w przypadku przyspieszonych zadań, WorkManager może uruchamiać usługę na pierwszym planie na platformie w wersjach starszych niż Android 12. Usługi działające na pierwszym planie mogą wyświetlać użytkownikowi powiadomienia.

Metody getForegroundInfoAsync() i getForegroundInfo() w instancji roboczej umożliwiają WorkManager wyświetlanie powiadomień, gdy wywołasz setExpedited() przed Androidem 12.

Każdy element ListenableWorker musi implementować metodę getForegroundInfo, jeśli chcesz, aby zadanie zostało uruchomione jako przyspieszone.

Jeśli kierujesz aplikację na Androida 12 lub nowszego, usługi działające na pierwszym planie pozostają dostępne za pomocą odpowiedniej metody setForeground.

Instancja robocza

Pracownicy nie wiedzą, czy ich praca została przyspieszona. Instancje robocze mogą jednak wyświetlać powiadomienie w niektórych wersjach Androida, gdy WorkRequest został przyspieszony.

Aby to włączyć, WorkManager udostępnia metodę getForegroundInfoAsync(), którą musisz wdrożyć, aby w razie potrzeby usługa WorkManager mogła wyświetlać powiadomienie z prośbą o uruchomienie ForegroundService.

Moduł roboczy Coroutine

Jeśli używasz interfejsu CoroutineWorker, musisz zaimplementować getForegroundInfo(). Następnie przekaż go do setForeground() w ciągu doWork(). Spowoduje to utworzenie powiadomienia w wersjach Androida starszych niż 12.

Przeanalizuj ten przykład:

  class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
   CoroutineWorker(appContext, workerParams) {

   override suspend fun getForegroundInfo(): ForegroundInfo {
       return ForegroundInfo(
           NOTIFICATION_ID, createNotification()
       )
   }

   override suspend fun doWork(): Result {
       TODO()
   }

    private fun createNotification() : Notification {
       TODO()
    }

}

Zasady dotyczące limitów

Możesz określić, co ma się wydarzyć w przypadku przyspieszonej pracy po osiągnięciu przez aplikację limitu wykonania. Aby kontynuować, daj setExpedited():

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, co powoduje, że zadanie jest uruchamiane jako zwykłe żądanie robocze. Pokazuje to powyżej fragment kodu.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, co powoduje anulowanie żądania, jeśli limit jest niewystarczający.

Przykładowa aplikacja

Pełny przykład tego, jak WorkManager 2.7.0 wykorzystuje przyspieszoną pracę, znajdziesz w narzędziu WorkManagerSample na GitHubie.

Odroczona praca przyspieszona

System próbuje wykonać określone przyspieszone zadanie jak najszybciej po jego wywołaniu. Jednak podobnie jak w przypadku innych typów zadań system może jednak opóźnić rozpoczęcie nowej, przyspieszonej pracy, jak w tych przypadkach:

  • Obciążenie: obciążenie systemu jest zbyt duże, co może się zdarzyć, gdy uruchomionych jest zbyt wiele zadań lub gdy w systemie nie ma wystarczającej ilości pamięci.
  • Limit: limit przyspieszonych zadań został przekroczony. Przyspieszona praca wykorzystuje system limitów oparty na zasobnikach gotowości aplikacji i ogranicza maksymalny czas wykonywania w rozległym przedziale czasu. Limity używane w przypadku szybszej pracy są bardziej restrykcyjne niż limity innych typów zadań w tle.

Planowanie pracy okresowej

Aplikacja może czasami wymagać uruchomienia określonych działań. Możesz na przykład okresowo tworzyć kopie zapasowe danych, pobierać nowe treści z aplikacji lub przesyłać logi na serwer.

Oto jak za pomocą PeriodicWorkRequest utworzyć obiekt WorkRequest uruchamiany okresowo:

Kotlin


val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java


PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

W tym przykładzie praca jest planowana z jednogodzinnym odstępem.

Odstęp czasu jest zdefiniowany jako minimalny czas między powtórzeniami. Dokładny czas wykonania instancji roboczej zależy od ograniczeń używanych w obiekcie WorkRequest oraz od optymalizacji wykonywanych przez system.

Elastyczne interwały uruchamiania

Jeśli charakter Twojej pracy wymaga czasu, możesz skonfigurować PeriodicWorkRequest tak, aby działał w okresie elastycznym w każdym przedziale czasu, jak pokazano na rysunku 1.

Możesz ustawić interwał elastyczny dla zadania okresowego. Definiujesz interwał powtarzania i przedział elastyczny, który na końcu tego interwału określa określony czas. WorkManager próbuje uruchomić zadanie w pewnym momencie w interwałach Flex w każdym cyklu.

Rysunek 1. Diagram przedstawia powtarzające się odstępy z okresem elastycznym, w którym można wykonywać zadanie.

Aby zdefiniować pracę okresową z okresem elastycznym, podczas tworzenia PeriodicWorkRequest przekazujesz wartość flexInterval wraz z wartością repeatInterval. Okres elastyczny rozpoczyna się repeatInterval - flexInterval i kończy na końcu interwału.

Poniżej znajduje się przykład pracy okresowej, która może być wykonywana w ciągu ostatnich 15 minut każdego 1-godzinnego okresu.

Kotlin


val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

Java


WorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class,
               1, TimeUnit.HOURS,
               15, TimeUnit.MINUTES)
           .build();

Interwał powtarzania musi być większy niż lub równy PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, a interwał elastyczny nie może być mniejszy niż PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS.

Wpływ ograniczeń na pracę okresową

Możesz stosować ograniczenia do pracy okresowej. Możesz na przykład dodać do żądania służbowego ograniczenie, które będzie działać tylko wtedy, gdy urządzenie użytkownika się ładuje. W takim przypadku, nawet jeśli minie zdefiniowany interwał powtórzeń, PeriodicWorkRequest nie będzie działać, dopóki ten warunek nie zostanie spełniony. Może to spowodować opóźnienie wykonania określonego zadania, a nawet pominięcie go, jeśli warunki nie zostaną spełnione w danym przedziale czasu.

Ograniczenia w pracy

Ograniczenia powodują, że praca jest odroczona do momentu spełnienia optymalnych warunków. W usłudze WorkManager dostępne są poniższe ograniczenia.

Typ sieci Ogranicza typ sieci wymagany do działania. Na przykład Wi-Fi (UNMETERED).
Niski poziom baterii Jeśli zasada ma wartość Prawda, praca nie jest możliwa, jeśli urządzenie działa w trybie słabej baterii.
WymagaŁadowania Jeśli zasada ma wartość Prawda, praca będzie możliwa tylko podczas ładowania urządzenia.
DeviceIdle Jeśli zasada ma wartość Prawda, urządzenie użytkownika musi być nieaktywne, aby można było kontynuować pracę. Może to być przydatne podczas wykonywania operacji wsadowych, które w przeciwnym razie mogłyby mieć negatywny wpływ na wydajność innych aplikacji uruchomionych na urządzeniu użytkownika.
Mało miejsca na dane Jeśli zasada ma wartość Prawda, praca nie jest możliwa, jeśli na urządzeniu jest za mało miejsca.

Aby utworzyć zestaw ograniczeń i powiązać go z pracą, utwórz instancję Constraints za pomocą Contraints.Builder() i przypisz ją do WorkRequest.Builder().

Na przykład ten kod tworzy żądanie służbowe, które jest uruchamiane tylko wtedy, gdy urządzenie użytkownika się ładuje i jest połączone z siecią Wi-Fi:

Kotlin


val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

Java


Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresCharging(true)
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();

Jeśli określisz wiele ograniczeń, praca będzie kontynuowana tylko wtedy, gdy będą spełnione wszystkie te ograniczenia.

Jeśli ograniczenie przestanie działać w trakcie wykonywania Twojej pracy, WorkManager zatrzyma instancję roboczą. Po spełnieniu wszystkich ograniczeń zadanie zostanie podjęta jeszcze raz.

Opóźniona praca

Jeśli Twoja praca nie ma żadnych ograniczeń lub jeśli są one spełnione w momencie umieszczenia w kolejce, system może je od razu rozpocząć. Jeśli nie chcesz uruchamiać zadania natychmiast, możesz określić, jak ma się rozpocząć po minimalnym opóźnieniu początkowym.

Oto przykład, jak ustawić uruchamianie zadania przynajmniej 10 minut po umieszczeniu go w kolejce.

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java


WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

W tym przykładzie pokazujemy, jak ustawić początkowe opóźnienie dla właściwości OneTimeWorkRequest, ale możesz też ustawić takie opóźnienie dla elementu PeriodicWorkRequest. W takim przypadku tylko pierwsze uruchomienie okresowej pracy zostanie opóźnione.

Zasada ponawiania i wycofywania

Jeśli chcesz, aby usługa WorkManager ponawiała próbę wykonania zadania, możesz zwrócić z instancji roboczej Result.retry(). Twoja praca zostanie wtedy przełożona zgodnie z opóźnieniem zwłoki i zasadami zwłoki.

  • Opóźnienie ponawiania określa minimalny czas oczekiwania przed ponowną próbą wykonania zadania po pierwszej próbie. Ta wartość nie może być krótsza niż 10 sekund (lub MIN_BACKOFF_MILLIS).

  • Zasada ponawiania określa, jak powinno zwiększać się opóźnienie ponowienia w przypadku kolejnych prób ponowienia. WorkManager obsługuje 2 zasady ponawiania: LINEAR i EXPONENTIAL.

Każde zlecenie robocze ma określoną zasadę zwrócenia i czas do ponowienia. Domyślna zasada to EXPONENTIAL z 30-sekundowym opóźnieniem, ale możesz to zmienić w konfiguracji żądania roboczego.

Oto przykład dostosowywania czasu do ponowienia i zasady.

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

Java


WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setBackoffCriteria(
                       BackoffPolicy.LINEAR,
                       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                       TimeUnit.MILLISECONDS)
               .build();

W tym przykładzie minimalne opóźnienie do ponowienia jest ustawione na minimalną dopuszczalną wartość, która wynosi 10 sekund. Ponieważ zasada ma wartość LINEAR, interwał ponawiania zwiększa się o około 10 sekund przy każdej nowej próbie. Na przykład pierwsze uruchomienie z użyciem funkcji Result.retry() zostanie próbowane ponownie po 10 sekundach, a potem 20, 30, 40 itd., jeśli po kolejnych próbach zadanie to będzie nadal zwracać wartość Result.retry(). Gdyby zasada ponawiania była ustawiona na EXPONENTIAL, sekwencja czasu trwania ponawiania byłaby bliższa wartości 20, 40, 80 itd.

Działanie tagów

Każde zlecenie robocze ma unikalny identyfikator, który można później zidentyfikować na potrzeby anulowania zadania lub obserwowania jego postępu.

Jeśli masz grupę logicznie powiązanych zadań, pomocne może okazać się otagowanie tych elementów. Tagowanie pozwala pracować z grupą żądań pracy.

Na przykład WorkManager.cancelAllWorkByTag(String) anuluje wszystkie żądania robocze z danym tagiem, a WorkManager.getWorkInfosByTag(String) zwraca listę obiektów WorkInfo, których można użyć do określenia bieżącego stanu pracy.

Poniższy kod pokazuje, jak dodać tag „czyszczenia” do swojej pracy:

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java


WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

Do jednego żądania roboczego można dodać wiele tagów. Wewnętrznie tagi te są przechowywane w postaci ciągów znaków. Aby uzyskać zestaw tagów powiązanych z WorkRequest, możesz użyć funkcji WorkInfo.getTags().

Z klasy Worker możesz pobrać jej zestaw tagów za pomocą metody ListenableWorker.getTags().

Przypisz dane wejściowe

Do działania wymagane są dane wejściowe. Na przykład praca związana z przesyłaniem obrazu może wymagać przesłania identyfikatora URI obrazu jako danych wejściowych.

Wartości wejściowe są przechowywane w obiekcie Data jako pary klucz-wartość i można je ustawiać w żądaniu roboczym. WorkManager dostarczy dane wejściowe Data podczas wykonywania zadania. Klasa Worker ma dostęp do argumentów wejściowych przez wywołanie Worker.getInputData(). Poniższy kod pokazuje, jak utworzyć instancję Worker, która wymaga danych wejściowych, i jak wysłać je w żądaniu roboczym.

Kotlin


// Define the Worker requiring input
class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput =
           inputData.getString("IMAGE_URI") ?: return Result.failure()

       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf(
       "IMAGE_URI" to "http://..."
   ))
   .build()

Java


// Define the Worker requiring input
public class UploadWork extends Worker {

   public UploadWork(Context appContext, WorkerParameters workerParams) {
       super(appContext, workerParams);
   }

   @NonNull
   @Override
   public Result doWork() {
       String imageUriInput = getInputData().getString("IMAGE_URI");
       if(imageUriInput == null) {
           return Result.failure();
       }

       uploadFile(imageUriInput);
       return Result.success();
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
      new OneTimeWorkRequest.Builder(UploadWork.class)
           .setInputData(
               new Data.Builder()
                   .putString("IMAGE_URI", "http://...")
                   .build()
           )
           .build();

Klasy Data można też używać do zwracania wartości. Informacje o danych wejściowych i wyjściowych znajdziesz w sekcji o parametrach wejściowych i zwracanych wartościach.

Dalsze kroki

Na stronie Stany i obserwacja znajdziesz więcej informacji o stanach pracy i sposobach monitorowania postępów.