Asistencia para trabajadores de larga ejecución

WorkManager tiene compatibilidad integrada con trabajadores de larga duración. En estos casos, WorkManager puede proporcionar una señal al SO indicando que el proceso debe mantenerse activo, si es posible, mientras se ejecuta este trabajo. Estos trabajadores se pueden ejecutar durante más de 10 minutos. Los casos de uso de ejemplo para esta nueva función incluyen cargas o descargas masivas (que no se pueden fragmentar), la compresión local de un modelo de AA o una tarea que es importante para el usuario de la app.

En un nivel profundo, WorkManager administra y ejecuta un servicio en primer plano por ti a fin de ejecutar WorkRequest y, al mismo tiempo, muestra una notificación configurable.

ListenableWorker ahora admite la API de setForegroundAsync(), mientras que CoroutineWorker admite una API de suspensión de setForeground(). Estas API permiten a los desarrolladores especificar que esta WorkRequest es importante (desde la perspectiva del usuario) o de larga duración.

A partir de 2.3.0-alpha03, WorkManager también te permite crear un PendingIntent, que se puede usar para cancelar trabajadores sin tener que registrar un nuevo componente de Android con la API de createCancelPendingIntent(). Este enfoque es especialmente útil cuando se usa con las API de setForegroundAsync() o setForeground(), que se pueden utilizar para agregar una acción de notificación a fin de cancelar el elemento Worker.

Cómo crear y administrar trabajadores de larga duración

Deberás usar un enfoque ligeramente diferente en función de si codificas en Kotlin o en Java.

Kotlin

Los desarrolladores de Kotlin deberían usar CoroutineWorker. En lugar de usar setForegroundAsync(), puedes usar la versión de suspensión de ese método: setForeground().

class DownloadWorker(context: Context, parameters: WorkerParameters) :
   CoroutineWorker(context, parameters) {

   private val notificationManager =
       context.getSystemService(Context.NOTIFICATION_SERVICE) as
               NotificationManager

   override suspend fun doWork(): Result {
       val inputUrl = inputData.getString(KEY_INPUT_URL)
                      ?: return Result.failure()
       val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
                      ?: return Result.failure()
       // Mark the Worker as important
       val progress = "Starting Download"
       setForeground(createForegroundInfo(progress))
       download(inputUrl, outputFile)
       return Result.success()
   }

   private fun download(inputUrl: String, outputFile: String) {
       // Downloads a file and updates bytes read
       // Calls setForeground() periodically when it needs to update
       // the ongoing Notification
   }
   // Creates an instance of ForegroundInfo which can be used to update the
   // ongoing notification.
   private fun createForegroundInfo(progress: String): ForegroundInfo {
       val id = applicationContext.getString(R.string.notification_channel_id)
       val title = applicationContext.getString(R.string.notification_title)
       val cancel = applicationContext.getString(R.string.cancel_download)
       // This PendingIntent can be used to cancel the worker
       val intent = WorkManager.getInstance(applicationContext)
               .createCancelPendingIntent(getId())

       // Create a Notification channel if necessary
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           createChannel()
       }

       val notification = NotificationCompat.Builder(applicationContext, id)
           .setContentTitle(title)
           .setTicker(title)
           .setContentText(progress)
           .setSmallIcon(R.drawable.ic_work_notification)
           .setOngoing(true)
           // Add the cancel action to the notification which can
           // be used to cancel the worker
           .addAction(android.R.drawable.ic_delete, cancel, intent)
           .build()

       return ForegroundInfo(notificationId, notification)
   }

   @RequiresApi(Build.VERSION_CODES.O)
   private fun createChannel() {
       // Create a Notification channel
   }

   companion object {
       const val KEY_INPUT_URL = "KEY_INPUT_URL"
       const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
   }
}

Java

Los desarrolladores que usan un elemento ListenableWorker o Worker pueden llamar a la API de setForegroundAsync(), que muestra un ListenableFuture<Void>. También se puede llamar a setForegroundAsync() para actualizar una Notification en curso.

Este es un ejemplo simple de un trabajador que ejecuta un trabajo de larga duración de descarga de un archivo. Este trabajador realiza un seguimiento del progreso para actualizar una Notification en curso que muestra el progreso de la descarga.

public class DownloadWorker extends Worker {
   private static final String KEY_INPUT_URL = "KEY_INPUT_URL";
   private static final String KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME";

   private NotificationManager notificationManager;

   public DownloadWorker(
       @NonNull Context context,
       @NonNull WorkerParameters parameters) {
           super(context, parameters);
           notificationManager = (NotificationManager)
               context.getSystemService(NOTIFICATION_SERVICE);
   }

   @NonNull
   @Override
   public Result doWork() {
       Data inputData = getInputData();
       String inputUrl = inputData.getString(KEY_INPUT_URL);
       String outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME);
       // Mark the Worker as important
       String progress = "Starting Download";
       setForegroundAsync(createForegroundInfo(progress));
       download(inputUrl, outputFile);
       return Result.success();
   }

   private void download(String inputUrl, String outputFile) {
       // Downloads a file and updates bytes read
       // Calls setForegroundAsync(createForegroundInfo(myProgress))
       // periodically when it needs to update the ongoing Notification.
   }

   @NonNull
   private ForegroundInfo createForegroundInfo(@NonNull String progress) {
       // Build a notification using bytesRead and contentLength

       Context context = getApplicationContext();
       String id = context.getString(R.string.notification_channel_id);
       String title = context.getString(R.string.notification_title);
       String cancel = context.getString(R.string.cancel_download);
       // This PendingIntent can be used to cancel the worker
       PendingIntent intent = WorkManager.getInstance(context)
               .createCancelPendingIntent(getId());

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           createChannel();
       }

       Notification notification = new NotificationCompat.Builder(context, id)
               .setContentTitle(title)
               .setTicker(title)
               .setSmallIcon(R.drawable.ic_work_notification)
               .setOngoing(true)
               // Add the cancel action to the notification which can
               // be used to cancel the worker
               .addAction(android.R.drawable.ic_delete, cancel, intent)
               .build();

       return new ForegroundInfo(notificationId, notification);
   }

   @RequiresApi(Build.VERSION_CODES.O)
   private void createChannel() {
       // Create a Notification channel
   }
}

Cómo agregar un tipo de servicio en primer plano a un trabajador de larga duración

Si tu app se orienta a Android 14 (nivel de API 34) o versiones posteriores, debes especificar un tipo de servicio en primer plano para todos los trabajadores de larga duración. Si tu app se orienta a Android 10 (nivel de API 29) o versiones posteriores, y contiene un trabajador de larga duración que requiere acceso a la ubicación, indica que el trabajador usa un tipo de servicio en primer plano de location.

Si tu app se orienta a Android 11 (nivel de API 30) o versiones posteriores, y contiene un trabajador de larga duración que requiere acceso a la cámara o al micrófono, declara los tipos de servicio en primer plano camera o microphone, respectivamente.

A fin de agregar estos tipos de servicios en primer plano, sigue los pasos que se describen en las siguientes secciones.

Declara tipos de servicio en primer plano en el manifiesto de la app

Declara el tipo de servicio en primer plano que usa el trabajador en el manifiesto de tu app. En el siguiente ejemplo, el trabajador requiere acceso a la ubicación y al micrófono:

AndroidManifest.xml

<service
   android:name="androidx.work.impl.foreground.SystemForegroundService"
   android:foregroundServiceType="location|microphone"
   tools:node="merge" />

Especifica tipos de servicio en primer plano durante el tiempo de ejecución

Cuando llames a setForeground() o setForegroundAsync(), asegúrate de especificar un tipo de servicio en primer plano.

MyLocationAndMicrophoneWorker

Kotlin

private fun createForegroundInfo(progress: String): ForegroundInfo {
   // ...
   return ForegroundInfo(NOTIFICATION_ID, notification,
           FOREGROUND_SERVICE_TYPE_LOCATION or
FOREGROUND_SERVICE_TYPE_MICROPHONE) }

Java

@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String progress) {
   // Build a notification...
   Notification notification = ...;
   return new ForegroundInfo(NOTIFICATION_ID, notification,
           FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MICROPHONE);
}