Cómo definir solicitudes de trabajo

En la guía de introducción, se describió cómo crear un elemento WorkRequest sencillo y ponerlo en cola.

En esta guía, aprenderás a definir y personalizar objetos WorkRequest para controlar casos de uso comunes, como los siguientes:

  • Cómo programar trabajos periódicos y de una sola ejecución
  • Cómo establecer restricciones de trabajos, como el uso de Wi-Fi o la carga del dispositivo
  • Cómo garantizar un retraso mínimo en la ejecución de trabajos
  • Cómo establecer estrategias de retirada y reintento
  • Cómo pasar datos de entrada a trabajos
  • Cómo agrupar trabajos relacionados mediante etiquetas

Descripción general

El trabajo se define en WorkManager a través de un objeto WorkRequest. Para programar trabajos con WorkManager, primero debes crear un objeto WorkRequest y, luego, ponerlo en cola.

Kotlin


val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java


WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

El objeto WorkRequest contiene toda la información que necesita WorkManager para programar y ejecutar tu trabajo. Esto incluye las restricciones que deben cumplirse para su ejecución, los datos de programación, como retrasos o intervalos repetidos, la configuración de reintentos y, además, podría incluir datos de entrada si el trabajo se basa en ellos.

WorkRequest es una clase básica abstracta. Hay dos implementaciones derivadas de esta clase que puedes usar para crear la solicitud: OneTimeWorkRequest y PeriodicWorkRequest. Como lo indican sus nombres, OneTimeWorkRequest es útil para programar trabajos no repetitivos, mientras que PeriodicWorkRequest es más adecuado para trabajos que se repiten en algún intervalo.

Cómo programar trabajos de una sola ejecución

Para programar un trabajo simple, que no necesite configuración adicional, usa el método estático from:

Kotlin


val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java


WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

Para trabajos más complejos, puedes usar un compilador:

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

Cómo programar trabajos acelerados

WorkManager 2.7.0 presentó el concepto de trabajo acelerado. Esto permite que WorkManager ejecute un trabajo importante mientras el sistema controla mejor el acceso a los recursos.

El trabajo acelerado se destaca por las siguientes características:

  • Importancia: El trabajo acelerado se adapta a tareas que son importantes para el usuario o que este inició.
  • Velocidad: El trabajo acelerado se adapta mejor a las tareas cortas que se inician de inmediato y se completan en cuestión de minutos.
  • Cuotas: Una cuota a nivel del sistema que limita el tiempo de ejecución en primer plano determina si se puede iniciar un trabajo acelerado.
  • Administración de energía: Las restricciones para la administración de energía, como el Ahorro de batería y Descanso, tienen menos probabilidades de afectar el trabajo acelerado.
  • Latencia: El sistema ejecuta de inmediato el trabajo acelerado, siempre que la carga de trabajo actual del sistema lo permita. Esto significa que son sensibles a la latencia y no se pueden programar para una ejecución posterior.

Un posible caso de uso para el trabajo acelerado puede ser dentro de una app de chat cuando el usuario desea enviar un mensaje o una imagen adjunta. Del mismo modo, una app que controla un flujo de pago o de suscripción también podría usar trabajo acelerado. Esto se debe a que esas tareas son importantes para el usuario, se ejecutan rápidamente en segundo plano, deben comenzar de inmediato y deben continuar ejecutándose incluso si el usuario cierra la app.

Cuotas

El sistema debe asignar tiempo de ejecución a un trabajo acelerado antes de que se pueda ejecutar. El tiempo de ejecución no es ilimitado. En su lugar, cada app recibe una cuota de tiempo de ejecución. Cuando tu app usa su tiempo de ejecución y alcanza su cuota asignada, ya no puedes ejecutar el trabajo acelerado hasta que se actualice la cuota. Esto permite que Android balancee de manera más eficaz los recursos entre las aplicaciones.

La cantidad de tiempo de ejecución disponible para una app se basa en el bucket en espera y la importancia del proceso.

Puedes determinar qué sucede cuando la cuota de ejecución no permite que un trabajo acelerado se ejecute de inmediato. Consulta los siguientes fragmentos para obtener más detalles.

Cómo ejecutar trabajos acelerados

A partir de WorkManager 2.7, tu app puede llamar a setExpedited() para declarar que un WorkRequest debe ejecutarse lo más rápido posible mediante un trabajo acelerado. En el siguiente fragmento de código, se proporciona un ejemplo de cómo usar 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();

En este ejemplo, inicializamos una instancia de OneTimeWorkRequest y llamamos a setExpedited() en ella. Esta solicitud se convierte en trabajo acelerado. Si la cuota lo permite, comenzará a ejecutarse de inmediato en segundo plano. Si se usó la cuota, el parámetro OutOfQuotaPolicy indica que la solicitud debe ejecutarse como un trabajo normal y no acelerado.

Servicios en primer plano y retrocompatibilidad

A fin de mantener la retrocompatibilidad para los trabajos acelerados, WorkManager puede ejecutar un servicio en primer plano en versiones de plataforma anteriores a Android 12. Los servicios en primer plano pueden mostrar una notificación al usuario.

Los métodos getForegroundInfoAsync() y getForegroundInfo() de tu Worker permiten que WorkManager muestre una notificación cuando llamas a setExpedited() antes de Android 12.

Cualquier ListenableWorker debe implementar el método getForegroundInfo si deseas solicitar que la tarea se ejecute como un trabajo acelerado.

Cuando se orientan a Android 12 o versiones posteriores, los servicios en primer plano permanecen disponibles para ti a través del método setForeground correspondiente.

Trabajador

Los trabajadores no saben si el trabajo que realizan se acelera o no. Sin embargo, pueden mostrar una notificación en algunas versiones de Android cuando se acelera un WorkRequest.

A fin de habilitar esto, WorkManager proporciona el método getForegroundInfoAsync(), que debes implementar para que WorkManager pueda mostrar una notificación a fin de iniciar un ForegroundService cuando sea necesario.

CoroutineWorker

Si usas un CoroutineWorker, debes implementar getForegroundInfo(). Luego, debes pasarlo a setForeground() dentro de doWork(). Al hacerlo, se creará la notificación en versiones anteriores a Android 12.

Consulta el siguiente ejemplo:

  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()
    }

}

Políticas de cuotas

Puedes controlar lo que ocurre con el trabajo acelerado cuando tu app alcanza su cuota de ejecución. Para continuar, puedes pasar setExpedited():

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, que hace que el trabajo se ejecute como una solicitud de trabajo común (el fragmento anterior lo demuestra)
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, que hace que la solicitud se cancele si no hay cuota suficiente

App de ejemplo

Para ver un ejemplo completo de cómo WorkManager 2.7.0 usa trabajos acelerados, consulta WorkManagerSample en GitHub.

Trabajos acelerados diferidos

El sistema intenta ejecutar un trabajo acelerado determinado lo antes posible una vez que se invoca. Sin embargo, como sucede con otros tipos de trabajos, el sistema podría diferir el inicio de nuevos trabajos acelerados, por ejemplo, en los siguientes casos:

  • Carga: La carga del sistema es demasiado alta, lo que puede suceder cuando ya se están ejecutando demasiados trabajos o cuando el sistema no tiene suficiente memoria.
  • Cuota: Se superó el límite de cuota para trabajos acelerados, que usan un sistema de cuotas basado en los buckets de App Standby y limitan el tiempo máximo de ejecución en un período de tiempo progresivo. Las cuotas que se usan para trabajos acelerados son más restrictivas que las que se usan para otros tipos de trabajos en segundo plano.

Cómo programar trabajos periódicos

En algunas ocasiones, tu app podría requerir que determinadas tareas se ejecuten de forma periódica. Por ejemplo, es posible que quieras crear copias de seguridad de tus datos, descargar contenido actualizado en tu app o subir registros a un servidor periódicamente.

A continuación, se muestra cómo puedes usar PeriodicWorkRequest para crear un objeto WorkRequest que se ejecute de manera periódica:

Kotlin


val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java


PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

En este ejemplo, el trabajo está programado con un intervalo de una hora.

El período de intervalo corresponde al tiempo mínimo entre las repeticiones. La hora exacta a la que se ejecutará el trabajador dependerá de las restricciones que uses en tu objeto WorkRequest y de las optimizaciones que lleve a cabo el sistema.

Intervalos de ejecución flexibles

Si las características de tu trabajo lo hacen depender del tiempo de ejecución, puedes configurar PeriodicWorkRequest para que se ejecute en un período flexible dentro de cada período de intervalo, como se muestra en la figura 1.

Puedes establecer un intervalo flexible para un trabajo periódico. Debes definir un intervalo de repetición y un intervalo flexible que especifique una cantidad de tiempo determinada al final del intervalo que se repite. WorkManager intentará ejecutar tu trabajo en el transcurso del intervalo flexible de cada ciclo.

Figura 1: En el diagrama se muestran intervalos de repetición con períodos flexibles en los que se puede ejecutar el trabajo.

Para definir un trabajo periódico con un período flexible, debes crear un elemento PeriodicWorkRequest y pasar el objeto flexInterval junto con repeatInterval. El período flexible transcurre desde repeatInterval - flexInterval hasta el final del intervalo.

El siguiente ejemplo es un trabajo periódico programado para ejecutarse en los últimos 15 minutos de cada período de una hora.

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();

El intervalo de repetición debe ser mayor o igual que PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, y el intervalo flexible debe ser mayor o igual que PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS.

Efectos de las restricciones en trabajos periódicos

Puedes aplicar restricciones a trabajos periódicos. Por ejemplo, puedes agregar una restricción para que tu solicitud de trabajo se ejecute solo cuando se esté cargando el dispositivo del usuario. En ese caso, no se ejecutará PeriodicWorkRequest, a menos que se cumpla esta condición, incluso si ya pasó el tiempo del intervalo de repetición definido. Esto podría ocasionar que se retrase o incluso omita la ejecución de determinada parte del trabajo si las condiciones no se cumplen durante el intervalo de ejecución.

Restricciones de trabajos

Las restricciones garantizan que el trabajo se aplace hasta que se cumplan las condiciones óptimas. Las siguientes restricciones están disponibles para WorkManager.

Tipo de red Restringe el tipo de red que se necesita para ejecutar tu trabajo, por ejemplo, una red Wi-Fi (UNMETERED).
BateríaNotBaja Si la estableces como verdadera, no se ejecutará el trabajo si el dispositivo está en modo de batería baja.
RequiereCarga Si la estableces como verdadera, el trabajo solo se ejecutará cuando el dispositivo se esté cargando.
DeviceIdle Si se establece como verdadero, solo se ejecutará el trabajo cuando el dispositivo del usuario esté inactivo. Esto puede resultar útil para operaciones en lotes que, de otro modo, podrían afectar negativamente el rendimiento de otras apps que se estén ejecutando de manera activa en el dispositivo del usuario.
AlmacenamientoPoco bajo Si se establece como verdadera, el trabajo no se ejecutará si el espacio de almacenamiento del dispositivo del usuario es demasiado bajo.

Para crear un conjunto de restricciones y asociarlo con algún trabajo, crea una instancia de Constraints a través de Contraints.Builder() y asígnala a tu WorkRequest.Builder().

Por ejemplo, en el siguiente código, se crea una solicitud de trabajo que solo se ejecutará cuando el dispositivo del usuario esté conectado a una red Wi-Fi y cargándose.

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();

Cuando se especifican varias restricciones, tu trabajo solo se ejecuta si se cumplen todas.

En caso de que una restricción deje de cumplirse mientras se está ejecutando tu trabajo, WorkManager detendrá a su trabajador. Se intentará ejecutar de nuevo cuando se cumplan todas las restricciones.

Trabajos retrasados

En el caso de que tu trabajo no tenga restricciones o que todas se cumplan cuando el trabajo esté en cola, el sistema podrá optar por ejecutarlo de inmediato. Si no quieres que el trabajo se ejecute de inmediato, puedes especificar que comience después de un retraso inicial mínimo.

A continuación, te mostramos un ejemplo de cómo configurar tu trabajo para que se ejecute al menos 10 minutos después de que se haya puesto en cola.

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java


WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

Si bien el ejemplo muestra cómo establecer un retraso inicial para un elemento OneTimeWorkRequest, también puedes hacerlo para PeriodicWorkRequest. En ese caso, solo se retrasaría la primera ejecución de tu trabajo periódico.

Política de retirada y reintento

Si necesitas que WorkManager vuelva a realizar tu trabajo, puedes mostrar Result.retry() desde tu trabajador. Entonces, se reprograma tu trabajo según el retraso de retirada y la política de retirada.

  • El retraso de retirada especifica el tiempo mínimo que se debe esperar para volver a ejecutar el trabajo después del primer intento. Este valor no puede ser inferior a 10 segundos (o MIN_BACKOFF_MILLIS).

  • La política de retirada define cómo debería aumentar el retraso de retirada con el paso del tiempo para los siguientes reintentos. WorkManager admite 2 políticas de retirada: LINEAR y EXPONENTIAL.

Cada solicitud de trabajo tiene una política y un retraso de retirada. La política predeterminada es EXPONENTIAL con un retraso de 30 segundos, pero puedes anularla en la configuración de tu solicitud de trabajo.

A continuación, puedes ver un ejemplo de cómo personalizar el retraso y la política de retirada.

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();

En este ejemplo, el retraso inicial de retirada se establece en el valor mínimo permitido: 10 segundos. Dado que la política es LINEAR, el intervalo de reintento aumentará unos 10 segundos luego de cada intento. Por ejemplo, la primera ejecución que finaliza mostrando Result.retry(), se reintentará después de 10 segundos, luego, en 20, 30, 40, y así sucesivamente, siempre que el trabajo siga mostrando Result.retry() en cada intento. Si la política de retirada fuera EXPONENTIAL, la secuencia de duración de reintentos sería aproximadamente 20, 40, 80, etcétera.

Cómo etiquetar tu trabajo

Cada solicitud de trabajo tiene un identificador único, que más tarde servirá para poder identificar el trabajo a fin de cancelarlo u observar su progreso.

Si tienes un grupo de trabajos relacionados lógicamente, también puede resultarte útil etiquetar esos elementos. Esto te permitirá operar con un grupo de solicitudes de trabajo en conjunto.

Por ejemplo, WorkManager.cancelAllWorkByTag(String) cancela todas las solicitudes de trabajo que tienen una etiqueta en particular y WorkManager.getWorkInfosByTag(String) muestra una lista de objetos WorkInfo que sirve para determinar el estado actual del trabajo.

En el siguiente código, se muestra cómo puedes agregar una etiqueta de "limpieza" a tu trabajo:

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java


WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

Por último, se pueden agregar varias etiquetas a una misma solicitud de trabajo. Estas etiquetas se almacenan internamente como un conjunto de strings. Para obtener el conjunto de etiquetas asociadas con el objeto WorkRequest, puedes usar WorkInfo.getTags().

Desde la clase Worker, puedes recuperar el conjunto de etiquetas mediante ListenableWorker.getTags().

Cómo asignar datos de entrada

Es posible que tu trabajo requiera datos de entrada para funcionar. Por ejemplo, un trabajo que controla la subida de una imagen puede requerir que se suba el URI de la imagen como entrada.

Los valores de entrada se almacenan como pares clave-valor en un objeto Data y se pueden configurar en la solicitud de trabajo. WorkManager enviará los Data de entrada a tu trabajo cuando lo ejecute. La clase Worker puede acceder a los argumentos de entrada llamando a Worker.getInputData(). En el siguiente código, se muestra cómo crear una instancia de Worker que requiere datos de entrada y cómo enviarla a la solicitud de trabajo.

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();

Del mismo modo, se puede utilizar la clase Data para generar un valor de retorno. Los datos de entrada y salida se explican de forma más detallada en la sección Parámetros de entrada y valores mostrados.

Próximos pasos

En la página Estados y observación, encontrarás más información sobre los estados de trabajo y cómo supervisar su progreso.