Definiowanie próśb o pracę

W przewodniku dla początkujących opisaliśmy, jak utworzyć WorkRequest i dodać go do kolejki.

Z tego przewodnika dowiesz się, jak definiować i dostosowywać WorkRequestobiekty , aby obsługiwać typowe przypadki użycia, np.:

  • Planowanie jednorazowych i cyklicznych zadań
  • Określanie ograniczeń dotyczących pracy, np. wymagania połączenia Wi-Fi lub ładowania
  • gwarantować minimalne opóźnienie w wykonywaniu pracy;
  • Ustawianie strategii ponawiania i wycofywania
  • Przekazywanie danych wejściowych do zadania
  • Grupowanie powiązanych zadań za pomocą tagów

Przegląd

Praca jest definiowana w bibliotece WorkManager za pomocą elementu WorkRequest. Aby zaplanować dowolną pracę za pomocą WorkManagera, musisz najpierw utworzyć obiekt WorkRequest, a następnie dodać go do kolejki.

Kotlin

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

Java

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

Obiekt WorkRequest zawiera wszystkie informacje potrzebne usłudze WorkManager do zaplanowania i uruchomienia zadania. Zawiera ograniczenia, które muszą być spełnione, aby zadanie mogło zostać uruchomione, informacje o harmonogramie, takie jak opóźnienia lub powtarzające się interwały, konfigurację ponawiania i dane wejściowe, jeśli zadanie ich wymaga.

WorkRequest jest abstrakcyjną klasą bazową. Istnieją 2 implementacje pochodne tej klasy, których możesz użyć do utworzenia żądania: OneTimeWorkRequestPeriodicWorkRequest. Jak sama nazwa wskazuje, funkcja OneTimeWorkRequest przydaje się do planowania niepowtarzalnych zadań, a PeriodicWorkRequest jest bardziej odpowiednia do planowania zadań, które powtarzają się w określonych odstępach czasu.

Planowanie jednorazowej pracy

W przypadku podstawowych zadań, które nie wymagają dodatkowej konfiguracji, użyj metody statycznej from:

Kotlin

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

Java

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

W przypadku bardziej złożonych zadań możesz użyć narzędzia do tworzenia:

Kotlin

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

Java

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

Planowanie prac priorytetowych

W wersji 2.7.0 biblioteki WorkManager wprowadzono pojęcie prac priorytetowych. Dzięki temu usługa WorkManager może wykonywać ważne zadania, a system ma większą kontrolę nad dostępem do zasobów.

Prace priorytetowe charakteryzują się tymi cechami:

  • Ważność: prace priorytetowe są odpowiednie w przypadku zadań, które są ważne dla użytkownika lub które zostały przez niego zainicjowane.
  • Szybkość: prace priorytetowe najlepiej sprawdzają się w przypadku krótkich zadań, które rozpoczynają się natychmiast i kończą w ciągu kilku minut.
  • Limity przydziału: limit na poziomie systemu, który ogranicza czas wykonywania na pierwszym planie i określa, czy zadanie priorytetowe może się rozpocząć.
  • Zarządzanie zasilaniem: Ograniczenia zarządzania zasilaniem, takie jak Oszczędzanie baterii i Tryb uśpienia, rzadziej wpływają na prace priorytetowe.
  • Opóźnienie: system natychmiast wykonuje prace priorytetowe, o ile bieżące obciążenie systemu na to pozwala. Oznacza to, że są one wrażliwe na opóźnienia i nie można ich zaplanować do późniejszego wykonania.

Potencjalnym przypadkiem użycia prac priorytetowych może być aplikacja do obsługi czatu, w której użytkownik chce wysłać wiadomość lub załączony obraz. Podobnie aplikacja, która obsługuje proces płatności lub subskrypcji, może również chcieć korzystać z prac priorytetowych. Dzieje się tak, ponieważ te zadania są ważne dla użytkownika, szybko wykonują się w tle, muszą się rozpocząć natychmiast i powinny być wykonywane nawet wtedy, gdy użytkownik zamknie aplikację.

Limity

Zanim system będzie mógł uruchomić przyspieszone zadanie, musi przydzielić mu czas wykonania. Czas wykonywania nie jest nieograniczony. Każda aplikacja otrzymuje limit czasu wykonania. Gdy aplikacja wykorzysta czas wykonania i osiągnie przydzielony limit, nie będzie można wykonywać prac priorytetowych do czasu odświeżenia limitu. Dzięki temu Android może skuteczniej równoważyć zasoby między aplikacjami.

Ilość czasu wykonywania dostępnego dla aplikacji zależy od zasobnika gotowości i znaczenia procesu.

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

Wykonywanie prac priorytetowych

Od wersji 2.7 biblioteki WorkManager aplikacja może wywoływać funkcję setExpedited(), aby zadeklarować, że WorkRequest ma działać jak najszybciej przy użyciu zadania priorytetowego. Poniższy fragment kodu pokazuje, jak używać setExpedited():

Kotlin

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

WorkManager.getInstance(context)
    .enqueue(request)

Java

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

W tym przykładzie inicjujemy instancję klasy OneTimeWorkRequest i wywołujemy na niej metodę setExpedited(). To żądanie jest wtedy traktowane jako pilne. Jeśli limit na to pozwala, zacznie się ono od razu wykonywać w tle. Jeśli limit został wykorzystany, parametr OutOfQuotaPolicy wskazuje, że żądanie powinno zostać wykonane w normalnym trybie, bez przyspieszenia.

Zgodność wsteczna i usługi działające na pierwszym planie

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

Metody getForegroundInfoAsync()getForegroundInfo() w klasie Worker umożliwiają wyświetlanie powiadomienia przez WorkManager, gdy wywołasz metodę setExpedited() na urządzeniach z Androidem w wersji starszej niż 12.

Każdy ListenableWorker musi implementować metodę getForegroundInfo, jeśli chcesz, aby zadanie było wykonywane jako zadanie priorytetowe.

W przypadku kierowania na Androida 12 lub nowszego usługi na pierwszym planie pozostają dostępne za pomocą odpowiedniej metody setForeground.

Instancja robocza

Pracownicy nie wiedzą, czy wykonywana przez nich praca jest przyspieszona. Jednak w niektórych wersjach Androida pracownicy mogą wyświetlać powiadomienie, gdy WorkRequest zostanie przyspieszona.

Aby to umożliwić, biblioteka WorkManager udostępnia metodę getForegroundInfoAsync(), którą musisz zaimplementować, aby biblioteka WorkManager mogła wyświetlać powiadomienie o rozpoczęciu ForegroundService w razie potrzeby.

CoroutineWorker

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

Przyjrzyj się temu przykładowi:

  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 się stanie z pracami priorytetowymi, gdy aplikacja osiągnie limit wykonania. Aby kontynuować, możesz przejść setExpedited():

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, co powoduje, że zadanie jest wykonywane jako zwykłe żądanie pracy. Widać to w poprzednim fragmencie kodu.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, co powoduje anulowanie żądania, jeśli nie ma wystarczającego limitu.

Odroczone prace priorytetowe

System próbuje wykonać dane zadanie priorytetowe jak najszybciej po jego wywołaniu. Podobnie jak w przypadku innych rodzajów zadań system może jednak odłożyć rozpoczęcie nowych zadań w trybie ekspresowym, np. w tych przypadkach:

  • Obciążenie: obciążenie systemu jest zbyt duże, co może się zdarzyć, gdy jest już uruchomionych zbyt wiele zadań lub gdy system nie ma wystarczającej ilości pamięci.
  • Limit: przekroczono limit zadań priorytetowych. Prace priorytetowe korzystają z systemu limitów opartego na grupach czuwania aplikacji i ograniczają maksymalny czas wykonywania w ramach ruchomego okna czasowego. Limity używane w przypadku prac priorytetowych są bardziej restrykcyjne niż limity używane w przypadku innych typów zadań w tle.

Planowanie okresowych prac

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

Oto jak za pomocą funkcji PeriodicWorkRequest utworzyć obiekt WorkRequest, który będzie wykonywany 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 zaplanowana w odstępach godzinowych.

Okres interwału to minimalny czas między powtórzeniami. Dokładny czas wykonania zadania zależy od ograniczeń używanych w obiekcie WorkRequest i od optymalizacji przeprowadzonych przez system.

Elastyczne interwały biegowe

Jeśli charakter Twojej pracy sprawia, że czas jej wykonywania jest istotny, możesz skonfigurować PeriodicWorkRequest tak, aby działał w elastycznym okresie w ramach każdego przedziału czasu, jak pokazano na rysunku 1.

W przypadku zadania okresowego możesz ustawić elastyczny interwał. Określasz interwał powtarzania i interwał elastyczny, który wyznacza pewien okres na końcu interwału powtarzania. WorkManager próbuje uruchomić zadanie w dowolnym momencie w okresie elastycznym w każdym cyklu.

Rysunek 1. Diagram przedstawia powtarzające się przedziały czasu z elastycznym okresem, w którym można wykonać pracę.

Aby zdefiniować pracę okresową z okresem elastycznym, podczas tworzenia PeriodicWorkRequest przekaż flexInterval wraz z repeatInterval. Okres elastyczny rozpoczyna się o godzinie repeatInterval - flexInterval i trwa do końca przedziału.

Oto przykład pracy okresowej, która może być wykonywana w ciągu ostatnich 15 minut każdej godziny.

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 lub równy PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, a interwał elastyczny musi być większy lub równy PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS.

Wpływ ograniczeń na pracę okresową

Do pracy okresowej możesz zastosować ograniczenia. Możesz na przykład dodać do żądania pracy ograniczenie, aby praca była wykonywana tylko wtedy, gdy urządzenie użytkownika jest ładowane. W takim przypadku nawet jeśli zdefiniowany interwał powtarzania upłynie, PeriodicWorkRequest nie zostanie uruchomione, dopóki ten warunek nie zostanie spełniony. Może to spowodować opóźnienie lub nawet pominięcie konkretnego uruchomienia zadania, jeśli warunki nie zostaną spełnione w interwale uruchomienia.

Ograniczenia związane z pracą

Constraints zapewnić odroczenie pracy do momentu spełnienia optymalnych warunków. Biblioteka WorkManager ma do dyspozycji te ograniczenia:

NetworkType Określa typ sieci wymagany do uruchomienia zadania. Na przykład Wi-Fi (UNMETERED).
BatteryNotLow Jeśli ma wartość Prawda, zadanie nie zostanie uruchomione, gdy urządzenie jest w trybie niskiego poziomu baterii.
RequiresCharging Jeśli ta opcja jest ustawiona na „Prawda”, zadanie będzie wykonywane tylko wtedy, gdy urządzenie jest ładowane.
DeviceIdle Jeśli ma wartość „prawda”, wymaga to, aby urządzenie użytkownika było nieaktywne przed uruchomieniem zadania. Może to być przydatne w przypadku wykonywania operacji wsadowych, które w przeciwnym razie mogłyby negatywnie wpłynąć na wydajność innych aplikacji działających aktywnie na urządzeniu użytkownika.
StorageNotLow Jeśli ma wartość „true”, zadanie nie zostanie uruchomione, jeśli na urządzeniu użytkownika jest zbyt mało miejsca na dane.

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

Na przykład ten kod tworzy żądanie pracy, które jest wykonywane tylko wtedy, gdy urządzenie użytkownika jest ładowane i 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 kilka ograniczeń, zadanie zostanie uruchomione tylko wtedy, gdy wszystkie ograniczenia zostaną spełnione.

Jeśli podczas działania zadania warunek przestanie być spełniony, WorkManager zatrzyma proces roboczy. Gdy wszystkie ograniczenia zostaną spełnione, zadanie zostanie ponowione.

Opóźniona praca

Jeśli Twoja praca nie ma ograniczeń lub wszystkie ograniczenia są spełnione w momencie umieszczenia jej w kolejce, system może zdecydować o natychmiastowym uruchomieniu pracy. Jeśli nie chcesz, aby zadanie zostało uruchomione od razu, możesz określić minimalne opóźnienie początkowe.

Oto przykład ustawienia zadania tak, aby było uruchamiane co najmniej 10 minut po dodaniu do kolejki.

Kotlin

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

Java

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

Przykład pokazuje, jak ustawić początkowe opóźnienie w przypadku elementu OneTimeWorkRequest, ale możesz też ustawić początkowe opóźnienie w przypadku elementu PeriodicWorkRequest. W takim przypadku opóźnione będzie tylko pierwsze uruchomienie pracy okresowej.

Zasady ponawiania i czasu do ponowienia

Jeśli chcesz, aby WorkManager ponowił wykonanie zadania, możesz zwrócić wartość Result.retry() z instancji roboczej. Praca jest następnie ponownie planowana zgodnie z czasem do ponowienia i zasadami wycofywania.

  • Czas do ponowienia określa minimalny czas oczekiwania przed ponowną próbą wykonania zadania po pierwszej próbie. Ta wartość nie może być mniejsza niż 10 sekund (lub MIN_BACKOFF_MILLIS).

  • Zasady czasu do ponowienia określają, jak opóźnienie czasu do ponowienia powinno wzrastać z czasem w przypadku kolejnych prób ponowienia. WorkManager obsługuje 2 rodzaje zasad ponawiania: LINEAREXPONENTIAL.

Każde żądanie pracy ma zasady wycofywania i opóźnienie wycofywania. Domyślna zasada to EXPONENTIAL z opóźnieniem wynoszącym 30 sekund, ale możesz ją zastąpić w konfiguracji żądania pracy.

Oto przykład dostosowywania opóźnienia wycofywania i zasad.

Kotlin

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

Java

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

W tym przykładzie minimalny czas do ponowienia został ustawiony na minimalną dozwoloną wartość, czyli 10 sekund. Ponieważ zasada to LINEAR, interwał ponawiania będzie się zwiększać o około 10 sekund z każdą nową próbą. Na przykład pierwsza próba zakończenia z błędem Result.retry() zostanie ponowiona po 10 sekundach, a następnie po 20, 30, 40 sekundach itd., jeśli po kolejnych próbach zadanie nadal będzie zwracać błąd Result.retry(). Jeśli zasada czasu do ponowienia zostałaby ustawiona na EXPONENTIAL, sekwencja czasu ponawiania byłaby bliższa wartościom 20, 40 i 80.

Oznaczanie prac tagami

Każde zgłoszenie ma unikalny identyfikator, który można później wykorzystać do identyfikacji tego zgłoszenia w celu anulowania lub sprawdzenia postępów.

Jeśli masz grupę logicznie powiązanych zadań, możesz je otagować. Tagowanie umożliwia wspólne zarządzanie grupą zgłoszeń.

Na przykład WorkManager.cancelAllWorkByTag(String) anuluje wszystkie żądania pracy z określonym 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ć do pracy tag „cleanup”:

Kotlin

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

Java

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

Do jednego zgłoszenia można dodać wiele tagów. Wewnętrznie te tagi są przechowywane jako zbiór ciągów znaków. Aby uzyskać zestaw tagów powiązanych z elementem WorkRequest, możesz użyć elementu WorkInfo.getTags().

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

Przypisywanie danych wejściowych

Aby wykonać zadanie, może być konieczne podanie danych wejściowych. Na przykład zadanie, które obsługuje przesyłanie obrazu, może wymagać przesłania identyfikatora URI obrazu jako danych wejściowych.

Wartości wejściowe są przechowywane jako pary klucz-wartość w obiekcie Data i można je ustawić w żądaniu pracy. Gdy WorkManager wykona zadanie, przekaże do niego dane wejściowe Data. Klasa Worker może uzyskać dostęp do argumentów wejściowych, wywołując Worker.getInputData(). Poniższy kod pokazuje, jak utworzyć instancję Worker, która wymaga danych wejściowych, i jak wysłać je w żądaniu pracy.

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

Podobnie możesz użyć klasy Data, aby zwrócić wartość.

Następne kroki

Na stronie Stany i obserwacje dowiesz się więcej o stanach zadań i o tym, jak śledzić postępy w pracy.