Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Asistencia para trabajadores de larga duración

WorkManager 2.3.0-alpha02 agrega compatibilidad de primera clase 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.

Crea y administra trabajadores de larga duración

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

Java

Los desarrolladores que usan un elemento ListenableWorker o Worker pueden llamar a la API de setForegroundAsync(), que muestra un ListenableFuture<Void>. También puedes 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(notification);
    }

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

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

Agrega un tipo de servicio en primer plano a un trabajador de larga duración

Si tu app se orienta a Android 10 (nivel 29 de API) o versiones posteriores y tiene un trabajador de larga duración que requiere acceso a la ubicación, indica que el trabajador usa un tipo de servicio en primer planode location. Además, si tu app se orienta a Android 11 (nivel 30 de API) o una versión posterior y tiene 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 de 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(), especifica un tipo de servicio en primer plano de FOREGROUND_SERVICE_TYPE_LOCATION, FOREGROUND_SERVICE_TYPE_CAMERA o FOREGROUND_SERVICE_TYPE_MICROPHONE, como se muestra en el siguiente fragmento de código.

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