O WorkManager tem suporte integrado para workers de longa duração. Nesses casos, o WorkManager pode enviar um sinal para o SO de que o processo precisa continuar ativo, se possível, enquanto a tarefa é executada. Esses workers podem ser executados por mais de 10 minutos. Exemplos de casos de uso para esse novo recurso incluem uploads ou downloads em massa (que não podem ser divididos), a fragmentação de um modelo de ML localmente ou uma tarefa do app que é importante para o usuário.
Internamente, o WorkManager gerencia e executa um serviço em primeiro plano por você
para executar a WorkRequest
. Além disso, ele mostra uma notificação
configurável.
O ListenableWorker
agora oferece suporte à API setForegroundAsync()
, e o
CoroutineWorker
oferece suporte a uma API de suspensão setForeground()
. Essas
APIs possibilitam que os desenvolvedores especifiquem que a WorkRequest
é importante (do
ponto de vista do usuário) ou de longa duração.
A partir da versão 2.3.0-alpha03
, o WorkManager também permite criar uma
PendingIntent
que pode ser usada para cancelar workers sem precisar
registrar um novo componente Android usando a API
createCancelPendingIntent()
. Essa abordagem é útil principalmente quando usada com as
APIs setForegroundAsync()
ou setForeground()
, que podem ser usadas para adicionar uma
ação de notificação de cancelamento do Worker
.
Como criar e gerenciar workers de longa duração
A abordagem usada caso a programação esteja sendo feita em Kotlin ou Java será um pouco diferente.
Kotlin
Os desenvolvedores Kotlin precisam usar o CoroutineWorker
. Em vez de
setForegroundAsync()
, você pode usar a versão de suspensão desse método,
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
Os desenvolvedores que usam um ListenableWorker
ou Worker
podem chamar a
API setForegroundAsync()
, que retorna um ListenableFuture<Void>
. Você
também pode chamar setForegroundAsync()
para atualizar uma Notification
em andamento.
Veja um exemplo simples de um worker de longa duração que faz o download de um arquivo. Esse
worker acompanha o processo de atualização de uma Notification
em andamento que mostra
o progresso do download.
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
}
}
Adicionar um tipo de serviço em primeiro plano a um worker de longa duração
Caso o app seja destinado ao Android 14 (nível 34 da API) ou versões mais recentes, especifique um
tipo de serviço em primeiro plano para todos os workers de longa duração.
Se o app for direcionado ao Android 10 (nível 29 da API) ou versões mais recentes e tiver um
um worker de longa duração que precise de acesso à localização, indique que ele
usa um tipo de serviço em primeiro plano de location
.
Se o app for direcionado ao Android 11 (nível 30 da API) ou versões mais recentes
e contém um worker de longa duração que precisa de acesso à câmera ou ao microfone,
Declarar camera
ou microphone
em primeiro plano
tipos de serviço, respectivamente.
Para adicionar esses tipos de serviço em primeiro plano, conclua as etapas descritas nas seções a seguir.
Declarar tipos de serviço em primeiro plano no manifesto do app
Declare o tipo de serviço em primeiro plano do worker no manifesto do seu app. No exemplo a seguir, o worker precisa de acesso à localização e ao microfone:
<service android:name="androidx.work.impl.foreground.SystemForegroundService" android:foregroundServiceType="location|microphone" tools:node="merge" />
Especificar tipos de serviço em primeiro plano durante a execução
Ao chamar setForeground()
ou setForegroundAsync()
, especifique um
tipo de serviço em primeiro plano:
Kotlin
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); }