WorkManager to biblioteka do planowania i wykonywania odroczonej pracy w tle na Androidzie. Jest zalecanym zamiennikiem JobDispatcher w Firebase. Z tego przewodnika dowiesz się, jak przenieść implementację FirebaseJobDispatcher do WorkManagera.
Konfigurowanie Gradle
Aby zaimportować bibliotekę WorkManager do projektu Androida, dodaj zależności wymienione w artykule Pierwsze kroki z WorkManagerem.
Z JobService do instancji roboczych
Metoda FirebaseJobDispatcher
używa podklasy JobService
jako punktu wejścia do definiowania zadań do wykonania. Możliwe, że używasz JobService
bezpośrednio lub SimpleJobService
.
JobService
będzie wyglądać mniej więcej tak:
Kotlin
import com.firebase.jobdispatcher.JobParameters import com.firebase.jobdispatcher.JobService class MyJobService : JobService() { override fun onStartJob(job: JobParameters): Boolean { // Do some work here return false // Answers the question: "Is there still work going on?" } override fun onStopJob(job: JobParameters): Boolean { return false // Answers the question: "Should this job be retried?" } }
Java
import com.firebase.jobdispatcher.JobParameters; import com.firebase.jobdispatcher.JobService; public class MyJobService extends JobService { @Override public boolean onStartJob(JobParameters job) { // Do some work here return false; // Answers the question: "Is there still work going on?" } @Override public boolean onStopJob(JobParameters job) { return false; // Answers the question: "Should this job be retried?" } }
Jeśli używasz typu SimpleJobService
, zastąpisz atrybut onRunJob()
, który zwraca typ @JobResult int
.
Główna różnica polega na tym, że używasz metody JobService
bezpośrednio, onStartJob()
jest wywoływana w wątku głównym, a za przeciążenie zadania do wątku w tle odpowiada aplikacja. Z drugiej strony, jeśli używasz usługi SimpleJobService
, jest ona odpowiedzialna za wykonywanie Twojej pracy w wątku w tle.
Podobne koncepcje w usłudze WorkManager. Podstawową jednostką pracy w usłudze WorkManager jest ListenableWorker
. Istnieją też inne przydatne podtypy instancji roboczych, takie jak Worker
, RxWorker
i CoroutineWorker
(podczas korzystania z kotlinów).
JobService mapuje się na ListenableWorker
Jeśli używasz bezpośrednio środowiska JobService
, instancja robocza, na którą mapuje się dane, to ListenableWorker
. Jeśli używasz parametru SimpleJobService
, użyj zamiast niego Worker
.
Wykorzystajmy powyższy przykład (MyJobService
) i sprawdźmy, jak przekonwertować go na ListenableWorker
.
Kotlin
import android.content.Context import androidx.work.ListenableWorker import androidx.work.ListenableWorker.Result import androidx.work.WorkerParameters import com.google.common.util.concurrent.ListenableFuture class MyWorker(appContext: Context, params: WorkerParameters) : ListenableWorker(appContext, params) { override fun startWork(): ListenableFuture<ListenableWorker.Result> { // Do your work here. TODO("Return a ListenableFuture<Result>") } override fun onStopped() { // Cleanup because you are being stopped. } }
Java
import android.content.Context; import androidx.work.ListenableWorker; import androidx.work.ListenableWorker.Result; import androidx.work.WorkerParameters; import com.google.common.util.concurrent.ListenableFuture; class MyWorker extends ListenableWorker { public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) { super(appContext, params); } @Override public ListenableFuture<ListenableWorker.Result> startWork() { // Do your work here. Data input = getInputData(); // Return a ListenableFuture<> } @Override public void onStopped() { // Cleanup because you are being stopped. } }
Podstawową jednostką pracy w usłudze WorkManager jest ListenableWorker
. Tak jak JobService.onStartJob()
, funkcja startWork()
jest wywoływana w wątku głównym. W tym miejscu MyWorker
implementuje właściwość ListenableWorker
i zwraca wystąpienie ListenableFuture
, które służy do sygnalizowania ukończenia pracy asynchronicznie. Tu należy wybrać własną strategię podziału na wątki.
ListenableFuture
w tym miejscu zwraca typ ListenableWorker.Result
, który może być jednym z tych elementów: Result.success()
, Result.success(Data outputData)
, Result.retry()
, Result.failure()
lub Result.failure(Data outputData)
. Więcej informacji znajdziesz na stronie z dokumentacją ListenableWorker.Result
.
Funkcja onStopped()
jest wywoływana, aby zasygnalizować konieczność zatrzymania ListenableWorker
, ponieważ ograniczenia nie są już spełnione (np. sieć nie jest już dostępna) lub wywołano metodę WorkManager.cancel…()
. Usługa onStopped()
może też być wywołana, jeśli system operacyjny z jakiegoś powodu wyłączy Twoją pracę.
Mapy SimpleJobService na instancję roboczą
Gdy używasz SimpleJobService
, powyższa instancja robocza będzie wyglądać tak:
Kotlin
import android.content.Context; import androidx.work.Data; import androidx.work.ListenableWorker.Result; import androidx.work.Worker; import androidx.work.WorkerParameters; class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { TODO("Return a Result") } override fun onStopped() { super.onStopped() TODO("Cleanup, because you are being stopped") } }
Java
import android.content.Context; import androidx.work.Data; import androidx.work.ListenableWorker.Result; import androidx.work.Worker; import androidx.work.WorkerParameters; class MyWorker extends Worker { public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) { super(appContext, params); } @Override public Result doWork() { // Do your work here. Data input = getInputData(); // Return a ListenableWorker.Result Data outputData = new Data.Builder() .putString(“Key”, “value”) .build(); return Result.success(outputData); } @Override public void onStopped() { // Cleanup because you are being stopped. } }
W tym przypadku doWork()
zwraca wystąpienie ListenableWorker.Result
, aby zasygnalizować synchronicznie zakończenie zadania. Usługa działa podobnie do usługi SimpleJobService
, która planuje zadania w wątku w tle.
JobBuilder mapuje na WorkRequest
FirebaseJobBuilder używa elementu Job.Builder
do reprezentowania metadanych Job
. WorkManager używa do tego roli WorkRequest
.
WorkManager zawiera 2 typy właściwości WorkRequest
: OneTimeWorkRequest
i PeriodicWorkRequest
.
Jeśli obecnie używasz Job.Builder.setRecurring(true)
, utwórz nowy PeriodicWorkRequest
. W przeciwnym razie użyj właściwości OneTimeWorkRequest
.
Przyjrzyjmy się, jak może wyglądać złożone harmonogramy Job
z użyciem FirebaseJobDispatcher
:
Kotlin
val input: Bundle = Bundle().apply { putString("some_key", "some_value") } val job = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyService::class.java) // uniquely identifies the job .setTag("my-unique-tag") // one-off job .setRecurring(false) // don't persist past a device reboot .setLifetime(Lifetime.UNTIL_NEXT_BOOT) // start between 0 and 60 seconds from now .setTrigger(Trigger.executionWindow(0, 60)) // don't overwrite an existing job with the same tag .setReplaceCurrent(false) // retry with exponential backoff .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) .setConstraints( // only run on an unmetered network Constraint.ON_UNMETERED_NETWORK, // // only run when the device is charging Constraint.DEVICE_CHARGING ) .setExtras(input) .build() dispatcher.mustSchedule(job)
Java
Bundle input = new Bundle(); input.putString("some_key", "some_value"); Job myJob = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyJobService.class) // uniquely identifies the job .setTag("my-unique-tag") // one-off job .setRecurring(false) // don't persist past a device reboot .setLifetime(Lifetime.UNTIL_NEXT_BOOT) // start between 0 and 60 seconds from now .setTrigger(Trigger.executionWindow(0, 60)) // don't overwrite an existing job with the same tag .setReplaceCurrent(false) // retry with exponential backoff .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) // constraints that need to be satisfied for the job to run .setConstraints( // only run on an unmetered network Constraint.ON_UNMETERED_NETWORK, // only run when the device is charging Constraint.DEVICE_CHARGING ) .setExtras(input) .build(); dispatcher.mustSchedule(myJob);
Aby osiągnąć ten sam efekt w usłudze WorkManager, musisz:
- Tworzenie danych wejściowych, których można użyć jako danych wejściowych dla funkcji
Worker
. - Utwórz
WorkRequest
z danymi wejściowymi i ograniczeniami podobnymi do tych zdefiniowanych powyżej dla atrybutuFirebaseJobDispatcher
. - Umieść
WorkRequest
w kolejce.
Konfigurowanie danych wejściowych dla instancji roboczej
FirebaseJobDispatcher
używa Bundle
do wysyłania danych wejściowych do JobService
.
WorkManager używa zamiast niej Data
. To zmienia się w ten sposób:
Kotlin
import androidx.work.workDataOf val data = workDataOf("some_key" to "some_val")
Java
import androidx.work.Data; Data input = new Data.Builder() .putString("some_key", "some_value") .build();
Konfigurowanie ograniczeń dla instancji roboczej
FirebaseJobDispatcher
używa Job.Builder.setConstaints(...)
do konfigurowania ograniczeń zadań. WorkManager używa zamiast niej Constraints
.
Kotlin
import androidx.work.* val constraints: Constraints = Constraints.Builder().apply { setRequiredNetworkType(NetworkType.CONNECTED) setRequiresCharging(true) }.build()
Java
import androidx.work.Constraints; import androidx.work.Constraints.Builder; import androidx.work.NetworkType; Constraints constraints = new Constraints.Builder() // The Worker needs Network connectivity .setRequiredNetworkType(NetworkType.CONNECTED) // Needs the device to be charging .setRequiresCharging(true) .build();
Tworzenie żądania WorkRequest (jednorazowego lub okresowego)
Do tworzenia komponentów OneTimeWorkRequest
i PeriodicWorkRequest
należy użyć narzędzi OneTimeWorkRequest.Builder
i PeriodicWorkRequest.Builder
.
Aby utworzyć OneTimeWorkRequest
podobny do tego Job
, wykonaj te czynności:
Kotlin
import androidx.work.* import java.util.concurrent.TimeUnit val constraints: Constraints = TODO("Define constraints as above") val request: Tell which work to execute OneTimeWorkRequestBuilder<MyWorker>() // Sets the input data for the ListenableWorker .setInputData(input) // If you want to delay the start of work by 60 seconds .setInitialDelay(60, TimeUnit.SECONDS) // Set a backoff criteria to be used when retry-ing .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS) // Set additional constraints .setConstraints(constraints) .build()
Java
import androidx.work.BackoffCriteria; import androidx.work.Constraints; import androidx.work.Constraints.Builder; import androidx.work.NetworkType; import androidx.work.OneTimeWorkRequest; import androidx.work.OneTimeWorkRequest.Builder; import androidx.work.Data; // Define constraints (as above) Constraints constraints = ... OneTimeWorkRequest request = // Tell which work to execute new OneTimeWorkRequest.Builder(MyWorker.class) // Sets the input data for the ListenableWorker .setInputData(inputData) // If you want to delay the start of work by 60 seconds .setInitialDelay(60, TimeUnit.SECONDS) // Set a backoff criteria to be used when retry-ing .setBackoffCriteria(BackoffCriteria.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS) // Set additional constraints .setConstraints(constraints) .build();
Główną różnicą jest to, że zadania WorkManagera są zawsze zachowane po ponownym uruchomieniu urządzenia.
Jeśli chcesz utworzyć PeriodicWorkRequest
, możesz to zrobić na przykład:
Kotlin
val constraints: Constraints = TODO("Define constraints as above") val request: PeriodicWorkRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES) // Sets the input data for the ListenableWorker .setInputData(input) // Other setters .build()
Java
import androidx.work.BackoffCriteria; import androidx.work.Constraints; import androidx.work.Constraints.Builder; import androidx.work.NetworkType; import androidx.work.PeriodicWorkRequest; import androidx.work.PeriodicWorkRequest.Builder; import androidx.work.Data; // Define constraints (as above) Constraints constraints = ... PeriodicWorkRequest request = // Executes MyWorker every 15 minutes new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.MINUTES) // Sets the input data for the ListenableWorker .setInputData(input) . // other setters (as above) .build();
Planuję pracę
Po określeniu Worker
i WorkRequest
możesz zaplanować pracę.
Każdy element Job
zdefiniowany z zastosowaniem FirebaseJobDispatcher
miał parametr tag
, który służył do jednoznacznej identyfikacji elementu Job
. Aplikacja może też informować algorytm szeregowania, czy ta instancja Job
ma zastąpić istniejącą kopię obiektu Job
, wywołując metodę setReplaceCurrent
.
Kotlin
val job = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyService::class.java) // uniquely identifies the job .setTag("my-unique-tag") // don't overwrite an existing job with the same tag .setRecurring(false) // Other setters... .build()
Java
Job myJob = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyJobService.class) // uniquely identifies the job .setTag("my-unique-tag") // don't overwrite an existing job with the same tag .setReplaceCurrent(false) // other setters // ... dispatcher.mustSchedule(myJob);
Korzystając z WorkManagera, możesz osiągnąć ten sam efekt za pomocą interfejsów API enqueueUniqueWork()
i enqueueUniquePeriodicWork()
(odpowiednio OneTimeWorkRequest
i PeriodicWorkRequest
). Więcej informacji znajdziesz na stronach z informacjami o WorkManager.enqueueUniqueWork()
i WorkManager.enqueueUniquePeriodicWork()
.
Będzie to wyglądać mniej więcej tak:
Kotlin
import androidx.work.* val request: "A WorkRequest") WorkManager.getInstance(myContext) .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, request)
Java
import androidx.work.ExistingWorkPolicy; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; OneTimeWorkRequest workRequest = // a WorkRequest; WorkManager.getInstance(myContext) // Use ExistingWorkPolicy.REPLACE to cancel and delete any existing pending // (uncompleted) work with the same unique name. Then, insert the newly-specified // work. .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, workRequest);
Anuluję pracę
W przypadku usługi FirebaseJobDispatcher
możesz anulować zadanie za pomocą:
Kotlin
dispatcher.cancel("my-unique-tag")
Java
dispatcher.cancel("my-unique-tag");
Podczas korzystania z WorkManagera możesz używać tych funkcji:
Kotlin
import androidx.work.WorkManager WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name")
Java
import androidx.work.WorkManager; WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name");
Inicjuję usługę WorkManager
WorkManager zwykle inicjuje się przy użyciu ContentProvider
.
Jeśli potrzebujesz większej kontroli nad sposobem, w jaki WorkManager porządkuje i działa harmonogramy, możesz dostosować konfigurację i inicjowanie WorkManagera.