Definir solicitações de trabalho

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 ocorre porque essas tarefas são importantes para o usuário, são executadas rapidamente em segundo plano, precisam começar imediatamente e precisam continuar sendo executadas mesmo que o usuário feche o app.

Cotas

O sistema precisa alocar o tempo de execução para um job priorizado antes de ele ser executado. O tempo de execução não é ilimitado. Em vez disso, cada app recebe uma cota de tempo de execução. Quando o app usa o tempo de execução e atinge a cota alocada, não é mais possível executar o trabalho priorizado até que a cota seja atualizada. Assim, o Android pode equilibrar os recursos de forma mais eficaz entre os aplicativos.

A quantidade de tempo de execução disponível para um app é baseada no bucket em espera e na 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 sido usada, o parâmetro OutOfQuotaPolicy vai indicar que a solicitação será executada 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ê quer solicitar que a tarefa seja executada como um job priorizado.

No Android 12 ou em versões mais recentes, os serviços em primeiro plano permanecem disponíveis para você pelo 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 trabalhos acelerados são mais restritivas 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.

É possível definir um intervalo flexível para um job periódico. Defina um intervalo de repetição
e um intervalo flexível que especifique um período determinado no final do
intervalo de repetição. O WorkManager tenta executar o job em algum momento
do intervalo flexível em cada ciclo.

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 e EXPONENTIAL.

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 você pode modificar essa configuração 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.