Definiowanie próśb o pracę

Przewodnik dla początkujących zawiera informacje o tym, jak utworzyć 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, np. wymagać 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

Zadania są definiowane 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 ograniczenia, które muszą zostać spełnione, aby można było uruchomić Twoje zadanie, informacje o planowaniu, takie jak opóźnienia lub powtarzające się przedziały, konfigurację ponownych prób, a także dane wejściowe, jeśli są potrzebne do wykonania zadania.

WorkRequest to abstrakcyjna klasa podstawowa. Do tworzenia żądań możesz użyć 2 implementacji pochodnych tej klasy: OneTimeWorkRequestPeriodicWorkRequest. Jak wskazuje ich nazwa, OneTimeWorkRequest jest przydatna do planowania pracy niepowtarzalnej, podczas gdy PeriodicWorkRequest jest bardziej odpowiednia do planowania pracy powtarzalnej w określonych odstępach czasu.

Planowanie jednorazowych zadań

Do podstawowych 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ć kreatora:

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 charakteryzuje 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 równoważyć zasoby między aplikacjami.

Czas wykonywania aplikacji zależy od poziomu 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 podanych niżej 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>()
    <b>.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)</b>
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

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

W tym przykładzie inicjujemy instancję klasy OneTimeWorkRequest i wywołujemy metodę setExpedited(). W takim przypadku Twoje zgłoszenie zostanie rozpatrzone priorytetowo. 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 w normalnym trybie, a nie przyspieszonym.

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.

Jeśli kierujesz aplikację na Androida 12 lub nowszego, usługi na pierwszym planie pozostaną 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 je 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. Pokazuje to wcześniejszy fragment kodu.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, co powoduje anulowanie żądania, jeśli nie ma wystarczającej puli.

Odroczone 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 wysokie. Może się to zdarzyć, gdy zbyt wiele zadań jest już uruchomionych lub gdy system nie ma wystarczającej ilości pamięci.
  • Limit: przekroczono limit zadań przyspieszonych. Szybka praca korzysta z systemu limitów, który jest oparty na grupach aplikacji w stanie gotowości i ogranicza maksymalny czas wykonania 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.

Aby utworzyć obiekt WorkRequest, który jest uruchamiany okresowo:PeriodicWorkRequest

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 elastycznym w każdym interwale, 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 elastyczności w każdym 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 miejscu 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 jeśli ustawiony interwał powtarzania upłynie, 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 uruchomienia.

Ograniczenia dotyczące pracy

Constraints zapewnij, aby odroczyć pracę do momentu spełnienia optymalnych warunków. Dostępne są 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”, urządzenie użytkownika musi być nieaktywne, zanim zadanie zostanie wykonane. Może to być przydatne podczas wykonywania operacji zbiorczych, które mogłyby negatywnie wpłynąć na wydajność innych aplikacji aktywnie działających na urządzeniu użytkownika.
StorageNotLow Jeśli ta opcja ma wartość Prawda, zadanie nie zostanie uruchomione, 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 Constraints.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 niespełnienie ograniczenia, 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, gdy zadanie jest umieszczane w kolejce, 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 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 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 do ponowienia jest ustawiony na minimalną dozwoloną wartość, czyli 10 sekund. Ponieważ zasada to LINEAR, interwał ponownego próby będzie się zwiększał o około 10 sekund przy każdej nowej próbie. Jeśli na przykład pierwsze uruchomienie kończy się wynikiem Result.retry(), po 10 sekundach zostanie podjęta kolejna próba, a po niej kolejna po 20, 30, 40 sekundach itd., jeśli po kolejnych próbach nadal będzie zwracany wynikResult.retry(). Gdyby zasada wycofywania miała wartość EXPONENTIAL, sekwencja czasu ponownego próby byłaby bliższa wartości 20, 40 i 80.

Oznaczanie 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 pracy tagami. Dzięki temu możesz obsługiwać grupy żądań.

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 zbiór ciągów znaków. Aby uzyskać zestaw tagów powiązanych z WorkRequest, możesz użyć 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 ustawiać 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(). Poniższy kod pokazuje, jak utworzyć instancję Worker, która wymaga danych wejściowych, oraz jak je wysł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 klasa Data może służyć 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 pracy.