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

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

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

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

Начиная с 2.3.0-alpha03 , WorkManager также позволяет создавать PendingIntent , который можно использовать для отмены Worker без необходимости регистрации нового компонента 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"
   }
}

Ява

Разработчики, использующие 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) }

Ява

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