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

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

Ява

Разработчики, использующие 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() обязательно укажите тип службы переднего плана .

МоеРасположениеИМикрофонРаботник

Котлин

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