Definiowanie próśb o pracę

Przewodnik dla początkujących zawierał informacje o tym, jak utworzyć prosty WorkRequest i umieścić go w kole.

Z tego przewodnika dowiesz się, jak definiować i dostosowywać obiekty WorkRequest do obsługi typowych przypadków użycia, takich jak:

  • Planowanie jednorazowych i powtarzających się zadań
  • ustawiać ograniczenia dotyczące pracy, takie jak wymaganie Wi-Fi lub ładowania;
  • gwarantować minimalne opóźnienie w wykonywaniu zadań;
  • Ustawianie strategii ponownego próbowania i rezygnacji
  • Przekazywanie danych wejściowych do pracy
  • Grupowanie powiązanych zadań za pomocą tagów

Omówienie

Praca jest definiowana w WorkManager za pomocą WorkRequest. Aby zaplanować pracę z WorkManager, musisz najpierw utworzyć obiekt WorkRequest, a potem umieścić go w kole.

Kotlin

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

Java

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

Obiekt WorkRequest zawiera wszystkie informacje potrzebne WorkManagerowi do zaplanowania i wykonania pracy. Obejmuje on ograniczenia, które muszą zostać spełnione, aby zadanie mogło zostać wykonane, informacje o planowaniu, takie jak opóźnienia lub powtarzające się interwały, konfigurację ponownych prób, a także dane wejściowe, jeśli są one potrzebne do wykonania zadania.

WorkRequest to abstrakcyjna klasa podstawowa. Istnieją 2 implementacje pochodne tej klasy, których możesz użyć do utworzenia żądania: OneTimeWorkRequestPeriodicWorkRequest. Jak wskazuje ich nazwa, OneTimeWorkRequest jest przydatna do planowania pracy niepowtarzalnej, a PeriodicWorkRequest – do planowania pracy powtarzalnej w określonych odstępach czasu.

Planowanie jednorazowych zadań

W przypadku prostych zadań, które nie wymagają dodatkowej konfiguracji, użyj statycznej metody 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ć konstruktora:

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 WorkManager 2.7.0 wprowadzono pojęcie prac priorytetowych. Dzięki temu WorkManager może wykonywać ważne zadania, zapewniając jednocześnie systemowi lepszą kontrolę nad dostępem do zasobów.

Praca priorytetowa wyróżnia się następującymi cechami:

  • Ważność: przyspieszone działanie jest odpowiednie do zadań, które są ważne dla użytkownika lub są przez niego inicjowane.
  • Szybkość: szybka praca najlepiej nadaje się do krótkich zadań, które rozpoczynają się natychmiast i kończą w ciągu kilku minut.
  • Limity: limit na poziomie systemu, który ogranicza czas wykonywania w tle, określa, czy zadanie przyspieszone może się rozpocząć.
  • Zarządzanie zasilaniem: ograniczenia zarządzania zasilaniem, takie jak Oszczędzanie baterii i Doze, rzadziej wpływają na przyspieszone działanie.
  • Czas oczekiwania: system natychmiast wykonuje przyspieszone zadania, o ile pozwala na to jego bieżące obciążenie. Oznacza to, że są one wrażliwe na opóźnienia i nie można ich zaplanować do wykonania w późniejszym terminie.

Przykładem użycia przyspieszonego przetwarzania może być sytuacja, gdy użytkownik chce wysłać wiadomość lub załączony obraz w aplikacji do czatu. Podobnie aplikacja, która obsługuje proces płatności lub subskrypcji, może korzystać z przyspieszonego przetwarzania. Dzieje się tak, ponieważ te zadania są ważne dla użytkownika, są wykonywane szybko w tle, muszą się rozpocząć natychmiast i powinny być wykonywane nawet wtedy, gdy użytkownik zamknie aplikację.

Limity

Zanim zadanie przyspieszone może zostać uruchomione, system musi przydzielić mu czas na wykonanie. Czas wykonywania nie jest nieograniczony. Każda aplikacja ma przydzielony limit czasu wykonania. Gdy aplikacja wykorzysta czas wykonania i osiągnie przydzielony limit, nie będzie można wykonywać przyspieszonych zadań, dopóki limit nie zostanie odświeżony. Dzięki temu Android może efektywniej przydzielać zasoby aplikacjom.

Czas wykonywania aplikacji zależy od poziomu gotowości i znaczenia procesu.

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

Wykonywanie prac priorytetowych

Począwszy od WorkManager 2.7 Twoja aplikacja może wywołać funkcję setExpedited(), aby zadeklarować, że WorkRequest powinna działać tak szybko, jak to możliwe, korzystając z przyspieszonego zadania. Poniższy fragment kodu pokazuje, jak używać funkcji 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 inicjujemy instancję klasy OneTimeWorkRequest i wywołujemy metodę setExpedited(). Wtedy staje się ono prośbą o przyspieszone rozpatrzenie. Jeśli limit nie zostanie przekroczony, proces rozpocznie się natychmiast w tle. Jeśli limit został wykorzystany, parametr OutOfQuotaPolicy wskazuje, że żądanie powinno zostać wykonane jako zwykłe, a nie przyspieszone.

Zgodność wsteczna i usługi na pierwszym planie

Aby zachować zgodność wsteczną w przyspieszonych zadaniach, 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 Twoim procesie roboczym umożliwiają WorkManagerowi wyświetlanie powiadomienia, gdy wywołasz metodę setExpedited() w wersjach Androida starszych niż 12.

Jeśli chcesz, aby zadanie było wykonywane jako zadanie przyspieszone, każda usługa ListenableWorker musi implementować metodę getForegroundInfo.

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

Worker

Pracownicy nie wiedzą, czy wykonują zadania w ramach usługi przyspieszonej obsługi. Jednak w niektórych wersjach Androida pracownicy mogą wyświetlać powiadomienie, gdy WorkRequest zostanie przyspieszone.

Aby to umożliwić, WorkManager udostępnia metodę getForegroundInfoAsync(), którą musisz zaimplementować, aby WorkManager mógł wyświetlić powiadomienie o uruchomieniu ForegroundService w razie potrzeby.

CoroutineWorker

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

Rozważ 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 się stanie z przyspieszoną pracą, gdy aplikacja osiągnie limit wykonania. Aby kontynuować, możesz przekazać setExpedited():

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, co powoduje, że zadanie jest wykonywane jako zwykłe zgłoszenie pracy. Przykład takiego fragmentu kodu podano powyżej.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, co powoduje anulowanie żądania, jeśli nie ma wystarczającej puli.

Odłożone prace priorytetowe

System stara się wykonać dane przyspieszone zadanie tak szybko, jak to możliwe po jego wywołaniu. Jednak podobnie jak w przypadku innych typów zadań system może opóźnić rozpoczęcie nowych prac przyspieszonych, np. w tych sytuacjach:

  • Obciążenie: obciążenie systemu jest zbyt duże, co może wystąpić, gdy zbyt wiele zadań jest już uruchomionych lub gdy system nie ma wystarczającej ilości pamięci.
  • Limit: przekroczono limit zadań przyspieszonych. Przyspieszone przetwarzanie korzysta z systemu limitów, który opiera się na zasobach aplikacji w stanie gotowości i ogranicza maksymalny czas wykonywania w ramach ruchomego okna czasowego. Limity stosowane w przypadku przyspieszonych zadań są bardziej restrykcyjne niż te stosowane w przypadku innych typów zadań w tle.

Planowanie pracy okresowej

Czasami aplikacja może wymagać okresowego wykonywania określonych czynności. Możesz na przykład okresowo tworzyć kopie zapasowe danych, pobierać nowe treści w aplikacji lub przesyłać dzienniki na serwer.

Oto jak za pomocą funkcji PeriodicWorkRequest utworzyć obiekt WorkRequest, który jest 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 zadanie jest zaplanowane z 1-godzinnym interwałem.

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

Elastyczne interwały biegowe

Jeśli ze względu na charakter Twojej pracy czas wykonania jest istotny, możesz skonfigurować PeriodicWorkRequest tak, aby działał w okresie elastyczności w ramach każdego przedziału czasowego, jak pokazano na rysunku 1.

Możesz ustawić interwał elastyczny dla zadania okresowego. Określasz interwał powtarzania oraz interwał elastyczny, który określa pewien czas na końcu tego interwału powtarzania. WorkManager próbuje uruchomić zadanie w okresie elastycznym w ramach każdego cyklu.

Rysunek 1. Diagram pokazuje powtarzające się interwały z elastycznym okresem, w którym zadanie może być wykonywane.

Aby zdefiniować pracę okresową z okresem elastycznym, podczas tworzenia zadania PeriodicWorkRequest należy przekazać parametr flexInterval wraz z parametrem repeatInterval. Okres elastyczny rozpoczyna się w repeatInterval - flexInterval i kończy się wraz z zakończeniem przedziału.

Poniżej przedstawiamy przykład zadania okresowego, które może być wykonywane w ostatnich 15 minutach 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ą

Możesz stosować ograniczenia do pracy okresowej. Możesz na przykład dodać do żądania pracy ograniczenie, aby zadanie było wykonywane tylko wtedy, gdy urządzenie użytkownika jest ładowane. W takim przypadku nawet po upływie zdefiniowanego interwału powtarzania działanie PeriodicWorkRequest nie zostanie wykonane, dopóki nie zostanie spełniony ten warunek. Może to spowodować opóźnienie lub nawet pominięcie określonego uruchomienia, jeśli warunki nie zostaną spełnione w okresie wykonywania.

Ograniczenia dotyczące pracy

Ograniczenia powodują, że zadania są odkładane do momentu spełnienia optymalnych warunków. WorkManager obsługuje te ograniczenia.

NetworkType Ogranicza typ sieci wymagany do wykonania zadania. Na przykład Wi-Fi (UNMETERED).
BatteryNotLow Jeśli ma wartość Prawda, zadanie nie będzie wykonywane, gdy urządzenie jest w trybie niskiego poziomu naładowania baterii.
RequiresCharging Gdy ta opcja ma wartość Prawda, zadanie będzie wykonywane tylko wtedy, gdy urządzenie jest ładowane.
DeviceIdle Jeśli ma wartość „prawda”, wymaga, aby urządzenie użytkownika było nieaktywne, zanim zostanie wykonane zadanie. Może to być przydatne podczas wykonywania operacji zbiorczych, które mogłyby negatywnie wpłynąć na wydajność innych aplikacji działających na urządzeniu użytkownika.
StorageNotLow Jeśli wartość to true (prawda), praca nie zostanie wykonana, jeśli na urządzeniu użytkownika jest za mało miejsca.

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

Na przykład poniższy kod tworzy żądanie pracy, które działa tylko wtedy, gdy urządzenie użytkownika jest ładowane i podłączone do 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ń, Twoje zadanie będzie wykonywane tylko wtedy, gdy zostaną spełnione wszystkie ograniczenia.

Jeśli podczas wykonywania pracy wystąpi ograniczenie, WorkManager zatrzyma zadanie. Następnie zostanie ono powtórzone, gdy zostaną spełnione wszystkie ograniczenia.

Opóźnione działanie

Jeśli Twoje zadanie nie ma żadnych ograniczeń lub wszystkie ograniczenia są spełnione w momencie umieszczenia zadania w kole, system może zdecydować się na jego uruchomienie natychmiast. Jeśli nie chcesz, aby zadanie było wykonywane natychmiast, możesz określić, aby rozpoczęło się po minimalnym początkowym opóźnieniu.

Oto przykład ustawienia pracy tak, aby była wykonywana co najmniej 10 minut po umieszczeniu 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();

Przykład pokazuje, jak ustawić początkowe opóźnienie dla OneTimeWorkRequest, ale możesz też ustawić początkowe opóźnienie dla PeriodicWorkRequest. W takim przypadku opóźnione zostanie tylko pierwsze uruchomienie zadania okresowego.

Zasady dotyczące ponownych prób i zwrotów

Jeśli chcesz, aby WorkManager ponownie wykonał Twoje zadanie, możesz zwrócić Result.retry() z poziomu workera. Następnie Twoja praca zostanie ponownie zaplanowana zgodnie z opóźnieniem i zasadami dotyczącymi odroczenia.

  • Opóźnienie określa minimalny czas oczekiwania przed ponownym uruchomieniem pracy po pierwszej próbie. Ta wartość nie może być mniejsza niż 10 sekund (lub MIN_BACKOFF_MILLIS).

  • Zasady wycofywania określają, jak ma się zwiększać opóźnienie wycofywania w kolejnych próbach. WorkManager obsługuje 2 zasady wycofywania: LINEAREXPONENTIAL.

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

Oto przykład dostosowywania opóźnienia i zasady wycofywania się.

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 minimalny czas odroczenia jest ustawiony na minimalną dozwoloną wartość, czyli 10 sekund. Zgodnie z tą zasadą LINEAR odstęp między kolejnymi próbami będzie się zwiększał o około 10 sekund. Na przykład pierwsze uruchomienie kończące się wynikiem Result.retry() zostanie powtórzone po 10 sekundach, a następnie po 20, 30, 40 i tak dalej, jeśli po kolejnych próbach nadal zwraca wartość Result.retry(). Gdyby zasada wycofywania miała wartość EXPONENTIAL, sekwencja czasu ponownego próby byłaby bliższa 20, 40, 80 i tak dalej.

Otaguj pracę

Każde zgłoszenie pracy ma wyjątkowy identyfikator, który pozwala zidentyfikować tę pracę w przyszłości, aby anulować zgłoszenie lub obserwować jego postępy.

Jeśli masz grupę elementów pracy powiązanych logicznie, możesz też oznaczyć te elementy. Tagowanie umożliwia grupowanie zapytań o pracę.

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 projektu tag „cleanup”:

Kotlin

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

Java

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

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

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

Przypisywanie danych wejściowych

Twoje zadania mogą wymagać danych wejściowych. Na przykład zadanie, które obsługuje przesyłanie obrazu, może wymagać podania 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 zgłoszeniu pracy. WorkManager przekaże dane wejściowe Data do Twojego zadania, gdy je wykona. Klasa Worker może uzyskać dostęp do argumentów wejściowych, wywołując funkcję Worker.getInputData(). Kod poniżej pokazuje, jak utworzyć instancję Worker, która wymaga danych wejściowych, oraz jak je przesłać 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 do klasy Data można używać klasy Data do zwracania wartości. Dane wejściowe i wyjściowe są omawiane bardziej szczegółowo w sekcji parametry wejściowe i wartości zwracane.

Dalsze kroki

Na stronie Stany i obserwacje znajdziesz więcej informacji o stanach zadań oraz o tym, jak monitorować postępy w ich wykonywaniu.