O Guia explicativo abordou como criar uma WorkRequest
simples e a enfileirar.
Neste guia, você vai aprender a definir e personalizar objetos WorkRequest
para lidar com casos de uso comuns, por exemplo:
- Programar trabalhos únicos e recorrentes
- Definir restrições de trabalho, como exigir Wi-Fi ou carregamento
- Garantir um atraso mínimo na execução de trabalhos
- Definir estratégias de novas tentativas e espera
- Transmitir dados de entrada para os trabalhos
- Agrupar trabalhos relacionados usando tags
Visão geral
O trabalho é definido no WorkManager usando uma
WorkRequest
. Para
agendar qualquer trabalho com o WorkManager, é preciso primeiro criar um
objeto WorkRequest
e depois o colocar em fila.
Kotlin
val myWorkRequest = ... WorkManager.getInstance(myContext).enqueue(myWorkRequest)
Java
WorkRequest myWorkRequest = ... WorkManager.getInstance(myContext).enqueue(myWorkRequest);
O objeto WorkRequest contém todas as informações necessárias para o WorkManager programar e executar seu trabalho. Isso inclui restrições que precisam ser atendidas para que o trabalho seja executado, por exemplo, o agendamento de informações como atrasos ou intervalos de repetição e a configuração de novas tentativas, podendo incluir também dados de entrada caso o trabalho dependa deles.
A WorkRequest
é uma classe básica abstrata. Há duas
implementações derivadas dessa classe que podem ser usadas para criar a solicitação,
OneTimeWorkRequest
e PeriodicWorkRequest
.
Como os nomes indicam, uma OneTimeWorkRequest
é útil para agendar
um trabalho não recorrente, enquanto uma PeriodicWorkRequest
é mais adequada para
agendar um trabalho que se repete.
Agendar trabalho único
Para trabalhos simples, que não exigem mais configuração, use o método
estático from
:
Kotlin
val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
Java
WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);
Para trabalhos mais complexos, use um builder:
Kotlin
val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<MyWork>() // Additional configuration .build()
Java
WorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) // Additional configuration .build();
Agendar trabalho priorizado
O WorkManager 2.7.0 introduz o conceito de trabalho priorizado. Isso permite que o WorkManager execute um trabalho importante e ofereça ao sistema um melhor controle sobre o acesso aos recursos.
O trabalho priorizado é importante para estas características:
- Importância: o trabalho priorizado se adequa a tarefas que são importantes para o usuário ou iniciadas por ele.
- Velocidade: o trabalho acelerado é mais adequado para tarefas curtas, que são iniciadas imediatamente e concluídas em poucos minutos.
- Cotas: uma cota no nível do sistema que limita o tempo de execução em primeiro plano determina se um job priorizado pode ser iniciado.
- Gerenciamento de energia: as Restrições do gerenciamento de energia, como os modos de Economia de bateria e Soneca, têm menor probabilidade de afetar o trabalho priorizado.
- Latência: o sistema vai executar imediatamente o trabalho priorizado se a carga de trabalho atual permitir. Ou seja, ele é sensível à latência e não pode ser agendado para execução posterior.
Um possível caso de uso para trabalhos priorizados seria quando o usuário quer enviar uma mensagem ou uma imagem anexada em um app de chat. Da mesma forma, um app que processa um fluxo de pagamento ou assinatura também pode exigir o uso do trabalho priorizado. Isso é porque essas tarefas são importantes para o usuário, são executadas rapidamente no em segundo plano, precisa começar imediatamente e deve continuar em execução, mesmo se O usuário fecha o app
Cotas
O sistema precisa alocar tempo de execução para um job priorizado antes de podem ser executados. O tempo de execução não é ilimitado. Em vez disso, cada app recebe uma cota de tempo de execução. Quando seu app usa o tempo de execução e atinge o cota alocada, não é mais possível executar o trabalho priorizado é atualizado. Isso permite que o Android equilibre os recursos de forma mais eficaz entre aplicativos conteinerizados.
A quantidade de tempo de execução disponível para um aplicativo é baseada no bucket em espera e importância do processo.
É possível determinar o que ocorre quando a cota de execução não permite que um job priorizado seja executado imediatamente. Veja detalhes nos snippets abaixo.
Como executar trabalhos priorizados
Com o WorkManager 2.7, o app pode chamar setExpedited()
para declarar que
uma WorkRequest
precisa ser executada o mais rápido possível usando um job priorizado. O
snippet de código abaixo mostra um exemplo de como usar o método setExpedited()
.
Kotlin
val request = OneTimeWorkRequestBuilder<SyncWorker>() .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build() WorkManager.getInstance(context) .enqueue(request)
Java
OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>() .setInputData(inputData) .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build();
Nesse exemplo, inicializamos uma instância da OneTimeWorkRequest
e chamamos o método
setExpedited()
nela. Essa solicitação se torna um trabalho priorizado. Se a cota
permitir, a execução vai começar imediatamente em segundo plano. Se a cota tiver
for usado, o parâmetro OutOfQuotaPolicy
indica que a solicitação precisa
ser executado como um trabalho normal e não priorizado.
Serviços em primeiro plano e compatibilidade com versões anteriores
Para manter a compatibilidade com versões anteriores em jobs priorizados, o WorkManager pode executar um serviço em primeiro plano em versões de plataforma anteriores ao Android 12. Os serviços em primeiro plano podem mostrar uma notificação para o usuário.
Os métodos getForegroundInfoAsync()
e getForegroundInfo()
no Worker
permitem que o WorkManager mostre uma notificação ao chamar setExpedited()
em versões anteriores ao Android 12.
Qualquer ListenableWorker
precisa implementar o método getForegroundInfo
se você
gostaria de solicitar que a tarefa seja executada como um job acelerado.
No Android 12 ou em versões mais recentes, os serviços em primeiro plano permanecem disponíveis para
o método setForeground
correspondente.
Worker
Os workers não sabem se o trabalho que estão fazendo é priorizado ou não. Mas
eles podem exibir uma notificação em algumas versões do Android quando uma
WorkRequest
tiver sido priorizada.
A fim de ativar esse comportamento, o WorkManager oferece o método getForegroundInfoAsync()
,
que precisa ser implementado para que ele possa exibir uma notificação e iniciar um
ForegroundService
por você sempre que necessário.
CoroutineWorker
Se você usar um CoroutineWorker
, é preciso implementar getForegroundInfo()
. Depois
o transmita para setForeground()
em doWork()
. Isso vai criar a
notificação em versões anteriores ao Android 12.
Veja o exemplo abaixo:
class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
CoroutineWorker(appContext, workerParams) {
override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(
NOTIFICATION_ID, createNotification()
)
}
override suspend fun doWork(): Result {
TODO()
}
private fun createNotification() : Notification {
TODO()
}
}
Políticas de cotas
Você pode controlar o que acontece com o trabalho priorizado quando o app atinge a
cota de execução. Para continuar, transmita setExpedited()
:
OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
, que faz o job ser executado como uma solicitação de trabalho comum. O snippet acima demonstra isso.OutOfQuotaPolicy.DROP_WORK_REQUEST
, que faz com que a solicitação seja cancelada se não houver cota suficiente.
App de exemplo
Para ver um exemplo completo de como o WorkManager 2.7.0 usa o trabalho priorizado, confira WorkManagerSample (em inglês) no GitHub.
Trabalho priorizado adiado
O sistema tenta executar o job acelerado o quanto antes após ele ser invocado. No entanto, como acontece com outros tipos de jobs, o sistema poderá adiar a inicialização de novos trabalhos acelerados, como nos casos a seguir:
- Carregamento: o carregamento do sistema está muito alto. Isso pode ocorrer quando muitos trabalhos já estão em execução ou quando o sistema não tem memória suficiente.
- Cota: a cota limite de jobs acelerados foi excedida. Os trabalhos acelerados usam um sistema de cotas baseado nos Buckets do App em espera e limitam o tempo máximo de execução dentro de uma janela de tempo. As cotas usadas para trabalho priorizado são mais restritivos do que as usadas para outros tipos de jobs em segundo plano.
Programar trabalho periódico
Às vezes, seu app pode exigir que determinados trabalhos sejam executados periodicamente. Por exemplo, você pode querer fazer backup dos seus dados, download de novos conteúdos no app ou upload de registros a um servidor periodicamente.
Veja como usar a PeriodicWorkRequest
para criar um
objeto WorkRequest
que é executado periodicamente:
Kotlin
val saveRequest = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS) // Additional configuration .build()
Java
PeriodicWorkRequest saveRequest = new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS) // Constraints .build();
Neste exemplo, o trabalho é programado com um intervalo de uma hora.
O período é definido como o tempo mínimo entre repetições. O momento exato em que o worker será executado depende das restrições usadas no objeto WorkRequest e das otimizações feitas pelo sistema.
Intervalos de execução flexíveis
Se a natureza do trabalho é sensível ao tempo de execução, é possível configurar
a PeriodicWorkRequest
para ser executada em um período
flexível em cada intervalo, conforme mostrado na Figura 1.
Figura 1. O diagrama mostra intervalos de repetição com o período flexível em que o trabalho pode ser executado.
Para definir o trabalho periódico com um período flexível, transmita um flexInterval
junto
ao repeatInterval
ao criar a PeriodicWorkRequest
. O período flexível
começa às repeatInterval - flexInterval
e dura até o fim do intervalo.
Veja a seguir um exemplo de trabalho periódico que pode ser executado nos últimos 15 minutos de cada período de uma hora.
Kotlin
val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>( 1, TimeUnit.HOURS, // repeatInterval (the period cycle) 15, TimeUnit.MINUTES) // flexInterval .build()
Java
WorkRequest saveRequest = new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS, 15, TimeUnit.MINUTES) .build();
O intervalo de repetição precisa ser maior ou igual a
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS
, e o intervalo
flexível precisa ser maior ou igual a
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS
.
Efeito das restrições no trabalho periódico
Você pode aplicar restrições ao trabalho periódico. Por exemplo, é possível adicionar uma
restrição à solicitação de trabalho para que ele seja executado apenas quando o dispositivo do usuário
estiver sendo carregado. Nesse caso, mesmo que o intervalo de repetição definido seja transmitido,
a PeriodicWorkRequest
não vai ser executada até que essa condição seja atendida. Isso pode
atrasar uma execução específica de trabalho ou até mesmo o ignorar caso
as condições não sejam atendidas no intervalo de execução.
Restrições de trabalho
As restrições garantem que o trabalho seja adiado até que as condições ideais sejam atendidas. As restrições abaixo estão disponíveis para o WorkManager.
NetworkType | Restringe o tipo de rede necessário para que o trabalho seja executado.
Por exemplo, Wi-Fi (UNMETERED ).
|
BatteryNotLow | Quando definida como "true", seu trabalho não será executado se o dispositivo estiver no modo de bateria fraca. |
RequiresCharging | Quando definida como "true", seu trabalho só será executado quando o dispositivo estiver sendo carregado. |
DeviceIdle | Quando definida como "true", o dispositivo do usuário precisará ficar inativo antes da execução do trabalho. Isso pode ser útil para executar operações em lote que podem ter um impacto negativo no desempenho de outros apps executados ativamente no dispositivo do usuário. |
StorageNotLow | Quando definida como "true", seu trabalho não será executado se o espaço de armazenamento do usuário no dispositivo estiver baixo demais. |
Para criar um conjunto de restrições e associá-lo a algum trabalho, crie uma
instância de Constraints
usando o Contraints.Builder()
e atribua-a ao
WorkRequest.Builder()
.
Por exemplo, o código a seguir cria uma solicitação de trabalho que só é executada quando o dispositivo do usuário está sendo carregado e está usando o Wi-Fi:
Kotlin
val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresCharging(true) .build() val myWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<MyWork>() .setConstraints(constraints) .build()
Java
Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresCharging(true) .build(); WorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setConstraints(constraints) .build();
Quando várias restrições forem especificadas, o trabalho será executado somente quando todas elas forem atendidas.
Caso uma restrição não seja atendida enquanto o trabalho estiver em execução, o WorkManager interromperá o worker. O trabalho será executado novamente quando todas as restrições forem atendidas.
Trabalho atrasado
Se o trabalho não tiver restrições ou se todas as restrições forem atendidas quando o trabalho for colocado em fila, o sistema poderá executá-lo imediatamente. Se você não quiser que o trabalho seja executado imediatamente, especifique um atraso inicial mínimo.
Veja um exemplo de como configurar seu trabalho para ser executado pelo menos 10 minutos depois de ser colocado em fila.
Kotlin
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>() .setInitialDelay(10, TimeUnit.MINUTES) .build()
Java
WorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInitialDelay(10, TimeUnit.MINUTES) .build();
O exemplo ilustra como definir um atraso inicial para uma
OneTimeWorkRequest
, mas também é possível definir um atraso inicial para uma
PeriodicWorkRequest
. Nesse caso, somente a primeira execução do seu trabalho periódico
será atrasada.
Política de nova tentativa e espera
Se você precisa que o WorkManager tente realizar o trabalho novamente, pode retornar
Result.retry()
do worker. O trabalho vai ser
agendado de novo com base em um atraso de espera e uma política de espera.
O atraso de espera especifica o tempo mínimo que é necessário aguardar antes de tentar executar o trabalho novamente após a primeira tentativa. Esse valor não pode ser inferior a 10 segundos (ou MIN_BACKOFF_MILLIS).
A política de espera define como o atraso de espera aumentará ao longo do tempo para novas tentativas subsequentes. O WorkManager é compatível com duas políticas de espera,
LINEAR
eEXPONENTIAL
.
Cada solicitação de trabalho tem uma política de espera e um atraso de espera. A política padrão
é EXPONENTIAL
com um atraso de 30 segundos, mas é possível substituir isso na
configuração da solicitação de trabalho.
Veja um exemplo de personalização do atraso e da política de espera.
Kotlin
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>() .setBackoffCriteria( BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .build()
Java
WorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setBackoffCriteria( BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .build();
Nesse exemplo, o atraso de espera mínimo é definido como o valor mínimo permitido,
10 segundos. Como a política é LINEAR
, o intervalo de repetição aumentará
aproximadamente 10 segundos a cada nova tentativa. Por exemplo, a primeira execução
terminada com Result.retry()
será tentada novamente após 10 segundos,
seguida por 20, 30, 40 e assim por diante se o trabalho continuar a retornar
Result.retry()
após tentativas subsequentes. Se a política de espera fosse definida como
EXPONENTIAL
, a sequência de duração da nova tentativa seria mais próxima de 20, 40, 80 e
assim por diante.
Incluir tag no trabalho
Cada solicitação de trabalho tem um identificador exclusivo que pode ser usado mais tarde para a identificar e cancelar o trabalho ou observar o progresso dele.
Se você tem um grupo de trabalhos logicamente relacionados, também pode ser útil marcar esses itens de trabalho. A inclusão de tags permite que você opere com um grupo de solicitações de trabalho.
Por exemplo, WorkManager.cancelAllWorkByTag(String)
cancela
todas as solicitações de trabalho com uma tag específica, e
WorkManager.getWorkInfosByTag(String)
retorna uma lista dos
objetos WorkInfo que podem ser usados para determinar o estado de trabalho atual.
O código abaixo mostra como adicionar uma tag "cleanup" (de limpeza) ao trabalho:
Kotlin
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>() .addTag("cleanup") .build()
Java
WorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .addTag("cleanup") .build();
Por fim, várias tags podem ser adicionadas a uma única solicitação de trabalho. Internamente, essas
tags são armazenadas como um conjunto de strings. Para ver o conjunto de tags associadas ao objeto
WorkRequest
, use WorkInfo.getTags().
Pela classe Worker
, é possível recuperar o conjunto de tags usando
ListenableWorker.getTags().
Atribuir dados de entrada
Seu trabalho pode exigir dados de entrada para que seja realizado. Por exemplo, trabalhos que processam o upload de uma imagem podem exigir que o URI da imagem seja enviado por upload como entrada.
Os valores de entrada são armazenados como pares de chave-valor em um objeto Data
e podem ser definidos na solicitação de trabalho. O WorkManager vai entregar a entrada Data
para
o trabalho e o executar. A classe Worker
pode acessar
os argumentos de entrada chamando Worker.getInputData()
. O
código abaixo mostra como criar uma instância de Worker
que
exige dados de entrada e como a enviar na solicitação de trabalho.
Kotlin
// Define the Worker requiring input class UploadWork(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { val imageUriInput = inputData.getString("IMAGE_URI") ?: return Result.failure() uploadFile(imageUriInput) return Result.success() } ... } // Create a WorkRequest for your Worker and sending it input val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>() .setInputData(workDataOf( "IMAGE_URI" to "http://..." )) .build()
Java
// Define the Worker requiring input public class UploadWork extends Worker { public UploadWork(Context appContext, WorkerParameters workerParams) { super(appContext, workerParams); } @NonNull @Override public Result doWork() { String imageUriInput = getInputData().getString("IMAGE_URI"); if(imageUriInput == null) { return Result.failure(); } uploadFile(imageUriInput); return Result.success(); } ... } // Create a WorkRequest for your Worker and sending it input WorkRequest myUploadWork = new OneTimeWorkRequest.Builder(UploadWork.class) .setInputData( new Data.Builder() .putString("IMAGE_URI", "http://...") .build() ) .build();
Da mesma forma, a classe Data
pode ser usada para gerar um valor de retorno. Os dados de
entrada e saída são abordados com mais detalhes na seção Parâmetros de entrada e
valores retornados.
A seguir
Na página de Estados e observação, você vai aprender mais sobre estados de trabalho e como monitorar o progresso do trabalho.