Поддержка работников с длительным стажем работы

WorkManager имеет встроенную поддержку длительно работающих процессов. В таких случаях WorkManager может сигнализировать операционной системе о том, что процесс следует поддерживать активным, если это возможно, пока выполняется данная задача. Эти процессы могут работать более 10 минут. Примерами использования этой новой функции являются массовая загрузка или скачивание данных (которые нельзя разбить на части), локальная обработка данных с помощью модели машинного обучения или задача, важная для пользователя приложения.

Внутри WorkManager управляет и запускает фоновую службу от вашего имени для выполнения WorkRequest , а также отображает настраиваемое уведомление.

Теперь ListenableWorker поддерживает API setForegroundAsync() , а CoroutineWorker — API приостанавливаемого setForeground() . Эти API позволяют разработчикам указывать, что данный WorkRequest важен (с точки зрения пользователя) или является длительным .

Начиная с 2.3.0-alpha03 , WorkManager также позволяет создавать PendingIntent , который можно использовать для отмены выполнения рабочих процессов без необходимости регистрации нового компонента Android с помощью API createCancelPendingIntent() . Этот подход особенно полезен при использовании с API setForegroundAsync() или setForeground() , которые можно использовать для добавления действия уведомления для отмены выполнения Worker .

Создание и управление долгосрочными сотрудниками

В зависимости от того, программируете ли вы на Kotlin или Java, подход будет немного отличаться.

Котлин

Разработчикам на Kotlin следует использовать CoroutineWorker . Вместо setForegroundAsync() можно использовать приостанавливающую версию этого метода — 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

Разработчики, использующие ListenableWorker или Worker могут вызывать API setForegroundAsync() , который возвращает ListenableFuture<Void> . Вы также можете вызвать setForegroundAsync() для обновления текущего Notification .

Вот простой пример длительно работающего процесса загрузки файла. Этот процесс отслеживает ход выполнения, чтобы обновлять постоянно обновляемое Notification , показывающее прогресс загрузки.

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

Добавить тип службы переднего плана к длительно работающему рабочему процессу.

Если ваше приложение ориентировано на Android 14 (уровень API 34) или выше, необходимо указать тип службы переднего плана для всех длительно работающих процессов. Если ваше приложение ориентировано на Android 10 (уровень API 29) или выше и содержит длительно работающий процесс, которому требуется доступ к местоположению, укажите, что процесс использует тип службы переднего плана « location .

Если ваше приложение ориентировано на Android 11 (уровень API 30) или выше и содержит длительно работающий обработчик, которому требуется доступ к камере или микрофону, объявите соответствующие типы служб переднего плана для camera или microphone .

Чтобы добавить эти типы служб переднего плана, выполните действия, описанные в следующих разделах.

Объявите типы служб переднего плана в манифесте приложения.

Укажите тип службы переднего плана для воркера в манифесте вашего приложения. В следующем примере воркеру требуется доступ к местоположению и микрофону:

AndroidManifest.xml

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

Указывайте типы служб переднего плана во время выполнения.

При вызове setForeground() или setForegroundAsync() обязательно укажите тип службы переднего плана .

MyLocationAndMicrophoneWorker

Котлин

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