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

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

Под капотом 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 .

Вот простой пример долго работающего рабочего процесса, который загружает файл. Этот Worker отслеживает прогресс и обновляет текущее 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);
}