WorkManager es una biblioteca para programar y ejecutar trabajos en segundo plano diferibles en Android. Es el reemplazo recomendado de Firebase JobDispatcher. En la siguiente guía, descubrirás cómo realizar el proceso de migración de tu implementación de Firebase JobDispatcher a WorkManager.
Configuración de Gradle
Para importar la biblioteca de WorkManager a tu proyecto de Android, agrega las dependencias enumeradas en Cómo comenzar a usar WorkManager.
De JobService a los trabajadores
FirebaseJobDispatcher
usa una subclase de
JobService
como punto de entrada para definir el trabajo que se debe hacer. Puedes usar directamente JobService
o SimpleJobService
.
Un JobService
tendrá un aspecto similar al siguiente:
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?" } }
Si estás usando SimpleJobService
, entonces anulaste onRunJob()
, que muestra un tipo @JobResult int
.
La diferencia clave es que cuando usas directamente JobService
, se llama a onStartJob()
en el subproceso principal y es responsabilidad de la app descargar el trabajo en un subproceso en segundo plano. Por otra parte, si usas SimpleJobService
, ese servicio es responsable de ejecutar tu trabajo en un subproceso en segundo plano.
WorkManager tiene conceptos similares. La unidad de trabajo fundamental de WorkManager es ListenableWorker
. También hay otros subtipos de trabajadores útiles, como Worker
, RxWorker
y CoroutineWorker
(cuando se usan corrutinas de Kotlin).
Asignaciones de JobService a ListenableWorker
Si estás usando directamente JobService
, este mapea al trabajador ListenableWorker
. Si usas SimpleJobService
, deberás cambiar a Worker
.
Usemos el ejemplo anterior (MyJobService
) y veamos cómo podemos convertirlo en 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. } }
La unidad básica de trabajo en WorkManager es ListenableWorker
. Al igual que con JobService.onStartJob()
, se llama a startWork()
en el subproceso principal. Aquí, MyWorker
implementa ListenableWorker
y muestra una instancia de ListenableFuture
, que se usa para indicar la finalización del trabajo de forma asíncrona. Debes elegir tu propia estrategia de subprocesos aquí.
Eventualmente, ListenableFuture
muestra un tipo ListenableWorker.Result
que puede ser Result.success()
, Result.success(Data outputData)
, Result.retry()
, Result.failure()
o Result.failure(Data outputData)
. Para obtener más información, consulta la página de referencia de ListenableWorker.Result
.
Se llama a onStopped()
para indicar que ListenableWorker
debe detenerse.
ya sea porque las restricciones ya no se cumplen (por ejemplo, porque
red ya no está disponible) o porque se usó un método WorkManager.cancel…()
llamado. Es posible que también se llame a onStopped()
si el SO decide cerrar tu
funcionar por alguna razón.
Asignaciones de SimpleJobService a un trabajador
Cuando se usa SimpleJobService
, el trabajador de arriba es similar a:
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. } }
Aquí, doWork()
muestra una instancia de ListenableWorker.Result
para indicar la finalización síncrona del trabajo, de manera similar a SimpleJobService
, que programa trabajos en un subproceso en segundo plano.
Asignaciones de JobBuilder a WorkRequest
FirebaseJobBuilder usa Job.Builder
para representar metadatos de Job
. WorkManager usa WorkRequest
para cumplir esta función.
WorkManager tiene dos tipos de WorkRequest
: OneTimeWorkRequest
y PeriodicWorkRequest
.
Si actualmente usas Job.Builder.setRecurring(true)
, deberías crear una PeriodicWorkRequest
nueva. De lo contrario, deberías usar una OneTimeWorkRequest
.
Veamos cómo es programar un Job
complejo con 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);
Para obtener el mismo resultado con WorkManager deberás hacer lo siguiente:
- Compilar datos de entrada, que se pueden usar como entrada para
Worker
- Compilar una
WorkRequest
con datos de entrada y restricciones similares a los definidos arriba paraFirebaseJobDispatcher
- Colocar el
WorkRequest
en la cola
Cómo configurar entradas para Worker
FirebaseJobDispatcher
usa un Bundle
para enviar datos de entrada a JobService
.
En cambio, WorkManager usa Data
. Estos son los resultados:
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();
Cómo configurar restricciones para Worker
FirebaseJobDispatcher
usos
Job.Builder.setConstaints(...)
para configurar restricciones en los trabajos. En cambio, WorkManager usa 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();
Cómo crear la WorkRequest (única o periódica)
Para crear instancias de OneTimeWorkRequest
y PeriodicWorkRequest
, deberías usar OneTimeWorkRequest.Builder
y PeriodicWorkRequest.Builder
.
Para crear una instancia OneTimeWorkRequest
que sea similar a la instancia de Job
anterior, haz lo siguiente:
Kotlin
import androidx.work.* import java.util.concurrent.TimeUnit val constraints: Constraints = TODO("Define constraints as above") val request: OneTimeWorkRequest = // 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();
La diferencia clave aquí es que los trabajos de WorkManager siempre persisten automáticamente en los reinicios de dispositivos.
Si quieres crear una instancia de PeriodicWorkRequest
, usa el siguiente ejemplo:
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();
Cómo programar trabajos
Ahora que definiste un Worker
y una WorkRequest
, ya puedes programar un trabajo.
Cada Job
definido con FirebaseJobDispatcher
tenía una tag
que se usaba para identificar de forma exclusiva un Job
, además de permitir que la aplicación le indicara al programador si esta instancia de Job
era para reemplazar una copia existente del Job
llamando a 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);
Cuando usas WorkManager, puedes lograr el mismo resultado usando las API de enqueueUniqueWork()
y enqueueUniquePeriodicWork()
(cuando se usan OneTimeWorkRequest
y PeriodicWorkRequest
, respectivamente). Para obtener más información, consulta las páginas de referencia de WorkManager.enqueueUniqueWork()
y WorkManager.enqueueUniquePeriodicWork()
.
El código tendrá el siguiente aspecto:
Kotlin
import androidx.work.* val request: OneTimeWorkRequest = TODO("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);
Cómo cancelar trabajos
Con FirebaseJobDispatcher
, puedes cancelar trabajos de la siguiente manera:
Kotlin
dispatcher.cancel("my-unique-tag")
Java
dispatcher.cancel("my-unique-tag");
Con WorkManager, puedes usar lo siguiente:
Kotlin
import androidx.work.WorkManager WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name")
Java
import androidx.work.WorkManager; WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name");
Cómo inicializar WorkManager
WorkManager suele inicializarse mediante un objeto ContentProvider
.
Si quieres obtener más control sobre la manera en que WorkManager organiza y programa el trabajo, puedes personalizar los ajustes y la inicialización de WorkManager.