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 presenta 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 maneja un flujo de pago o 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 y deben comenzar de inmediato.

Cuotas

El sistema debe asignar el tiempo de ejecución de la app a un trabajo acelerado antes de que se pueda ejecutar. El tiempo de ejecución no es ilimitado. Más bien, se limita por una cuota. 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 nivele de manera más eficaz los recursos entre las aplicaciones.

Cada app tiene su propia cuota de tiempo de ejecución en primer plano. La cantidad de tiempo de ejecución disponible se basa en la importancia del proceso y el bucket en espera.

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 trabajador debe usar un trabajo acelerado. En el siguiente fragmento de código, se muestra un ejemplo de cómo usar el método setExpedited():

Kotlin

OneTimeWorkRequestBuilder<T>().apply {
    setInputData(inputData)
    setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
}.build()

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. Luego, esta solicitud se convierte en un trabajo acelerado. Si la cuota lo permite, comenzará a ejecutarse de inmediato en segundo plano.

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 normal. El fragmento anterior lo demuestra. O bien OutOfQuotaPolicy.DROP_WORK_REQUEST, que hace que la solicitud se cancele si no hay cuota suficiente.

Servicios en primer plano y retrocompatibilidad

WorkManager 2.7 tiene retrocompatibilidad y se ejecuta en Android 12 y versiones anteriores. Ten en cuenta que, en esos casos, usa servicios en primer plano en lugar de trabajos acelerados.

Cuando se orientan a Android 12 o versiones posteriores, los servicios en primer plano permanecen disponibles para ti a través de setForegroundAsync(). Sin embargo, te recomendamos que uses setExpedited().

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 la implementación 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 especifica 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 omita o retrase 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.

NetworkType Restringe el tipo de red que se necesita para ejecutar tu trabajo, por ejemplo, una red Wi-Fi (UNMETERED).
BatteryNotLow Si se establece como verdadero, no se ejecutará el trabajo cuando el dispositivo esté en modo de batería baja.
RequiresCharging Si se establece como verdadero, solo se ejecutará el trabajo 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.
StorageNotLow Si se establece como verdadero, no se ejecutará el trabajo 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 10 segundos, pero se puede anular 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, que puedes obtener mediante WorkRequest.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.