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