Cómo administrar trabajos

Una vez que hayas definido tu Worker y WorkRequest, el último paso es poner el trabajo en cola. La forma más sencilla de hacerlo es llamar al método enqueue() de WorkManager y pasar la solicitud WorkRequest que deseas ejecutar.

Kotlin


val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)

Java


WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);

Ten cuidado de no duplicar trabajos cuando los pongas en cola. Por ejemplo, una app podría intentar subir sus registros a un servicio de backend cada 24 horas. Si no tienes cuidado, podrías terminar poniendo en cola la misma tarea varias veces, aunque el trabajo solo deba ejecutarse una vez. Para evitar este problema, puedes programarlo como un trabajo único.

Trabajo único

El trabajo único es un concepto potente que garantiza que solo tengas una instancia de trabajo con un nombre en particular a la vez. A diferencia de los ID, los nombres únicos son legibles que especifica el desarrollador en lugar de que WorkManager los genere automáticamente. A diferencia de las etiquetas, los nombres únicos solo están asociados con una sola instancia de trabajo.

Los trabajos únicos se pueden aplicar a trabajos periódicos y de una sola ejecución. Puedes crear una secuencia de trabajo único llamando a uno de estos métodos, según si estás programando un trabajo periódico o de una sola ejecución.

Ambos métodos aceptan estos 3 argumentos:

  • uniqueWorkName: es una String que se usa para identificar de forma exclusiva la solicitud de trabajo.
  • existingWorkPolicy: es un elemento enum que le indica a WorkManager qué hacer si ya existe una cadena de trabajo sin terminar con ese nombre único. Consulta la política de resolución de conflictos para obtener más información.
  • work: es la solicitud WorkRequest que se quiere programar.

El uso de trabajos únicos permite solucionar el problema de programación duplicada que se mencionó anteriormente.

Kotlin


val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

Java


PeriodicWorkRequest sendLogsWorkRequest = new
      PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
              .setConstraints(new Constraints.Builder()
              .setRequiresCharging(true)
          .build()
      )
     .build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

Si se ejecuta el código mientras un trabajo sendLogs ya está en cola, se conserva el trabajo existente y no se agrega ninguno nuevo.

Las secuencias de trabajo único también pueden ser útiles si necesitas crear gradualmente una cadena larga de tareas. Por ejemplo, una app de edición de fotos puede permitir que los usuarios deshagan una cadena larga de acciones. Cada una de esas operaciones de deshacer puede llevar un tiempo, pero deben realizarse en el orden correcto. En este caso, la app podría crear una cadena de "deshacer" y agregar cada operación de deshacer a la cadena según sea necesario. Consulta Cómo encadenar trabajos para obtener más información.

Política de resolución de conflictos

Cuando programas un trabajo único, debes indicarle a WorkManager qué acción tomar cuando haya un conflicto. Para ello, debes pasar una enum cuando pongas el trabajo en la cola.

Para los trabajos de una sola ejecución, debes proporcionar una política ExistingWorkPolicy, que admite 4 opciones para controlar el conflicto.

  • REPLACE: reemplaza el trabajo existente con el nuevo. Esta opción cancela el trabajo existente.
  • KEEP: conserva el trabajo existente e ignora el nuevo.
  • APPEND: agrega el trabajo nuevo al final del existente. Esta política hará que el trabajo nuevo se encadene al trabajo existente y se ejecute después de él.

El trabajo existente se convierte en un requisito previo para ejecutar el nuevo. Si el trabajo existente se marca como CANCELLED o FAILED, el trabajo nuevo también quedará CANCELLED o FAILED. Si deseas que el trabajo nuevo se ejecute sin importar el estado del trabajo existente, usa APPEND_OR_REPLACE en su lugar.

  • APPEND_OR_REPLACE funciona de manera similar a APPEND, salvo que no depende del estado de trabajo requisito previo. Si el trabajo existente se marca como CANCELLED o FAILED, el trabajo nuevo se ejecutará de todos modos.

Para los trabajos periódicos, debes proporcionar una política ExistingPeriodicWorkPolicy, que admite 2 opciones: REPLACE y KEEP. Estas funcionan de la misma manera que sus equivalentes de ExistingWorkPolicy.

Cómo observar tu trabajo

Después de poner el trabajo en cola, puedes verificar su estado en cualquier momento mediante una consulta a WorkManager por su name, id o tag asociada.

Kotlin


// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

Java


// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>


La consulta muestra un elemento ListenableFuture del objeto WorkInfo, que incluye el id del trabajo, sus etiquetas, su State actual y los datos de salida establecidos a través de Result.success(outputData).

Una variante de LiveData de cada uno de los métodos te permite observar cambios de WorkInfo registrando un objeto de escucha. Por ejemplo, si quieres mostrar un mensaje al usuario cuando el trabajo finaliza correctamente, puedes configurarlo de la siguiente manera:

Kotlin


workManager.getWorkInfoByIdLiveData(syncWorker.id)
               .observe(viewLifecycleOwner) { workInfo ->
   if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
       Snackbar.make(requireView(), 
      R.string.work_completed, Snackbar.LENGTH_SHORT)
           .show()
   }
}


Java


workManager.getWorkInfoByIdLiveData(syncWorker.id)
        .observe(getViewLifecycleOwner(), workInfo -> {
    if (workInfo.getState() != null &&
            workInfo.getState() == WorkInfo.State.SUCCEEDED) {
        Snackbar.make(requireView(),
                    R.string.work_completed, Snackbar.LENGTH_SHORT)
                .show();
   }
});

Consultas de trabajo complejas

WorkManager 2.4.0 y las versiones posteriores admiten consultas complejas de trabajos en cola mediante objetos WorkQuery. WorkQuery admite consultas de trabajos especificando una combinación de las etiquetas, el estado y el nombre de trabajo único.

En el siguiente ejemplo, se muestra cómo puedes encontrar todos los trabajos que tengan la etiqueta "syncTag", tengan el estado FAILED o CANCELLED y sus nombres únicos sean "preProcess" o "sync".

Kotlin


val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

Java


WorkQuery workQuery = WorkQuery.Builder
       .fromTags(Arrays.asList("syncTag"))
       .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(Arrays.asList("preProcess", "sync")
     )
    .build();

ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);

Cada componente (etiqueta, estado o nombre) de una WorkQuery se une mediante el elemento AND. Cada valor de un componente se une mediante el elemento OR. Por ejemplo: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)

WorkQuery también funciona con el equivalente de LiveData: getWorkInfosLiveData().

Cómo cancelar y detener el trabajo

Si ya no necesitas ejecutar tu trabajo que se encontraba previamente en la cola, puedes solicitar que se cancele. Los trabajos se pueden cancelar por su name, id o tag asociada.

Kotlin


// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")


Java


// by id
workManager.cancelWorkById(syncWorker.id);

// by name
workManager.cancelUniqueWork("sync");

// by tag
workManager.cancelAllWorkByTag("syncTag");

En niveles más profundos, WorkManager verifica el State del trabajo. Si el trabajo ya está finalizado, no sucede nada. De lo contrario, su estado cambia a CANCELLED y el trabajo no se ejecutará en el futuro. También se marcarán como CANCELLED todos los trabajos WorkRequest que dependan de este trabajo.

Los trabajos que estén actualmente RUNNING reciben una llamada a ListenableWorker.onStopped(). Anula este método para controlar cualquier posible limpieza. Consulta cómo Cómo detener un trabajador en ejecución para obtener más información.

Cómo detener un trabajador en ejecución

Hay algunos motivos diferentes por los que WorkManager puede detener a tu Worker en ejecución:

  • Solicitaste explícitamente que se cancelara (por ejemplo, llamando a WorkManager.cancelWorkById(UUID)).
  • En el caso de un trabajo único, solicitaste explícitamente una nueva solicitud WorkRequest con una política ExistingWorkPolicy de REPLACE. La solicitud WorkRequest anterior se considera inmediatamente cancelada.
  • Las restricciones de tu trabajo ya no se cumplen.
  • El sistema le indicó a tu app que detuviera tu trabajo por algún motivo. Esto puede suceder si supera el plazo de ejecución de 10 minutos. El trabajo está programado para reintentarlo más adelante.

En estas condiciones, se detiene tu trabajador.

Debes anular de manera cooperativa todos tus trabajos en ejecución y liberar los recursos que el trabajador esté reteniendo. Por ejemplo, debes cerrar los controladores abiertos de las bases de datos y los archivos en este momento. Tienes dos mecanismos a tu disposición para saber cuándo se detiene al trabajador.

Devolución de llamada onStopped()

WorkManager invoca el objeto ListenableWorker.onStopped() en cuanto se detiene el trabajador. Anula este método para cerrar los recursos que pudieras estar reteniendo.

Propiedad isStopped()

Puedes llamar al método ListenableWorker.isStopped() para comprobar si tu trabajador ya se detuvo. Si realizas operaciones repetitivas o de larga duración en tu trabajador, deberías verificar esta propiedad con frecuencia y usarla como indicador para saber lo antes posible si se detuvo el trabajo.

Nota: WorkManager ignora el Result de un trabajador que haya recibido el indicador onStop, dado que el trabajador ya se considera detenido.