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, 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
Praca jest definiowana w WorkManager za pomocą WorkRequest
. Aby zaplanować pracę z WorkManagerem, 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 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ą potrzebne do wykonania zadania.
WorkRequest
to abstrakcyjna klasa podstawowa. Istnieją 2 implementacje pochodne tej klasy, których możesz użyć do utworzenia żądania: OneTimeWorkRequest
i PeriodicWorkRequest
.
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 Twoja 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
Od wersji WorkManager 2.7 Twoja aplikacja może wywoływać funkcję setExpedited()
, aby zadeklarować, że WorkRequest
powinna działać tak szybko, jak to możliwe, korzystając z szybkiego 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 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()
i 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.
Przykładowa aplikacja
Pełny przykład użycia przyspieszonego działania w WorkManager 2.7.0 znajdziesz w pliku WorkManagerSample na GitHubie.
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 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.
Oto jak za pomocą elementu 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.
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 ta wartość jest ustawiona na „Prawda”, Twoje zadanie nie będzie wykonywane, jeśli użytkownik ma za mało miejsca na urządzeniu. |
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 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 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:
LINEAR
iEXPONENTIAL
.
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. Zgodnie z tą zasadą LINEAR
czas ponownego próby będzie wydłużał się o około 10 sekund przy każdej kolejnej próbie. 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 żądanie wykonania pracy ma wyjątkowy identyfikator, który można wykorzystać do identyfikacji tego zadania w celu anulowania lub obserwowania jego postępów.
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.