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 está orientada 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 una versión posterior y contiene una
de un trabajador de larga duración que requiera acceso a la ubicación, indica que
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.
declarar el primer plano de camera
o microphone
tipos de servicios, 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:
<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.
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); }