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ć WorkRequestobiektyWorkRequest, aby obsługiwać typowe przypadki użycia, np.:

  • Planowanie jednorazowych i cyklicznych zadań
  • określać ograniczenia dotyczące pracy, takie jak konieczność korzystania z Wi-Fi lub ładowania;
  • Gwarantowanie minimalnego opóźnienia w wykonywaniu pracy
  • Ustawianie strategii ponawiania i wycofywania
  • Przekazywanie danych wejściowych do zadania
  • Grupowanie powiązanych zadań za pomocą tagów

Omówienie

Zadanie jest definiowane w WorkManagerze za pomocą klasy WorkRequest. Aby zaplanować dowolne zadanie 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 to abstrakcyjna klasa bazowa. Istnieją 2 implementacje pochodne tej klasy, których możesz użyć do utworzenia żądania: OneTimeWorkRequestPeriodicWorkRequest. Jak sugerują ich nazwy, funkcja OneTimeWorkRequest przydaje się do planowania niepowtarzalnych zadań, a funkcja 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 bibliotece WorkManager w wersji 2.7.0 wprowadzono koncepcję 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ść: przyspieszone działanie jest odpowiednie w przypadku zadań, które są ważne dla użytkownika lub zostały przez niego zainicjowane.
  • Szybkość: ekspresowe wykonanie najlepiej sprawdza się w przypadku krótkich zadań, które zaczynają się natychmiast i kończą w ciągu kilku minut.
  • Limity: limit na poziomie systemu, który ogranicza czas wykonywania na pierwszym planie, określa, czy można rozpocząć przyspieszone zadanie.
  • Zarządzanie zasilaniem: ograniczenia zarządzania zasilaniem, takie jak Oszczędzanie baterii i Tryb uśpienia, rzadziej wpływają na przyspieszone zadania.
  • Czas oczekiwania: system natychmiast wykonuje przyspieszone zadania, o ile bieżące obciążenie systemu na to pozwala. Oznacza to, że są one wrażliwe na czas oczekiwania i nie można zaplanować ich wykonania na później.

Potencjalnym zastosowaniem przyspieszonego działania może być aplikacja do 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 przyspieszonej pracy. Dzieje się tak, ponieważ te zadania są ważne dla użytkownika, szybko wykonują się w tle, muszą się rozpocząć natychmiast i powinny być kontynuowane nawet wtedy, gdy użytkownik zamknie aplikację.

Limity

Aby uruchomić przyspieszone zadanie, system 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ć przyspieszonej pracy 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 przyspieszonego zadania. Szczegółowe informacje znajdziesz w poniższych fragmentach.

Wykonywanie prac priorytetowych

Od wersji WorkManager 2.7 aplikacja może wywoływać funkcję setExpedited(), aby zadeklarować, że WorkRequest ma być uruchomiony jak najszybciej za pomocą przyspieszonego zadania. 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ę OneTimeWorkRequest i wywołujemy na niej funkcję setExpedited(). W takim przypadku prośba jest traktowana jako pilna. Jeśli limit na to pozwala, zacznie się ona od razu uruchamiać 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ą WorkManagerowi wyświetlanie powiadomienia, 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 przyspieszone.

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 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ć, WorkManager udostępnia metodę getForegroundInfoAsync(), którą musisz zaimplementować, aby WorkManager mógł 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 przyspieszonymi zadaniami, 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. Pokazuje to wcześniejszy fragment kodu.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, co powoduje anulowanie żądania, jeśli nie ma wystarczającego limitu.

Odroczone prace priorytetowe

System próbuje wykonać dane przyspieszone zadanie jak najszybciej po jego wywołaniu. Jednak podobnie jak w przypadku innych rodzajów zadań system może odłożyć rozpoczęcie nowych zadań ekspresowych, 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ń przyspieszonych. Praca w trybie przyspieszonym korzysta z systemu limitów opartych na grupach gotowości aplikacji i ogranicza maksymalny czas wykonywania w ramach ruchomego okna czasowego. Limity używane w przypadku przyspieszonych zadań 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ą 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 godzinnych.

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 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 ramach elastycznego okresu w każdym okresie interwału, 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ę interwały 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 dotyczące pracy

Constraints zapewnić odroczenie pracy do czasu, gdy zostaną spełnione optymalne warunki; 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 „true”, 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ść Prawda, 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ą funkcji Constraints.Builder() i przypisz ją do elementu 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 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. Jeśli nie chcesz, aby zadanie zostało wykonane 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 dla elementu OneTimeWorkRequest, ale możesz też ustawić początkowe opóźnienie dla elementu PeriodicWorkRequest. W takim przypadku opóźnione będzie tylko pierwsze uruchomienie pracy okresowej.

Zasady ponawiania i wycofywania

Jeśli chcesz, aby WorkManager ponowił wykonanie zadania, możesz zwrócić wartość Result.retry() z procesu roboczego. Praca jest następnie ponownie planowana zgodnie z opóźnieniem wycofywania i zasadami wycofywania.

  • Opóźnienie wycofania 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 wycofywania określają, jak opóźnienie wycofywania powinno wzrastać z czasem w przypadku kolejnych prób ponowienia. WorkManager obsługuje 2 rodzaje zasad wycofywania: 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 prośby o wykonanie zadania.

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

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 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 pierwsze wykonanie kończące się wartością Result.retry() zostanie ponowione po 10 sekundach, a następnie po 20, 30, 40 sekundach itd., jeśli zadanie nadal będzie zwracać wartość Result.retry() po kolejnych próbach. Jeśli zasada wycofywania zostałaby ustawiona na EXPONENTIAL, sekwencja czasu ponawiania byłaby bliższa wartościom 20, 40 i 80.

Oznaczanie prac

Każda prośba o wykonanie zadania ma unikalny identyfikator, który można później wykorzystać do identyfikacji tego zadania 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ć funkcji 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 przesyła obraz, może wymagać identyfikatora URI przesyłanego 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, oraz 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 klasa Data może służyć do zwracania wartości. Dane wejściowe i wyjściowe są bardziej szczegółowo omówione w sekcji parametry wejściowe i wartości zwracane.

Następne kroki

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