Como gerenciar trabalhos

Depois de definir seu Worker e sua WorkRequest, a última etapa é colocar o trabalho em fila. A maneira mais simples de fazer isso é chamar o método enqueue() do WorkManager, transmitindo a WorkRequest que você quer executar.

Kotlin


val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)

Java


WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);

Tenha cuidado para evitar duplicação ao colocar o trabalho em fila. Por exemplo, um app pode tentar fazer upload dos registros para um serviço de back-end a cada 24 horas. Se não tomar cuidado, você pode acabar colocando a mesma tarefa várias vezes na fila, mesmo que o job precise ser executado apenas uma vez. Para evitar esse problema, programe o trabalho como exclusivo.

Trabalho exclusivo

Trabalho exclusivo é um conceito eficiente que garante que você tenha apenas uma instância de trabalho com um nome específico por vez. Diferentemente dos IDs, os nomes exclusivos são legíveis por humanos e especificados pelo desenvolvedor em vez de gerados automaticamente pelo WorkManager. Também diferentemente das tags, os nomes exclusivos são associados apenas a uma instância de trabalho.

O trabalho exclusivo pode ser aplicado a trabalhos únicos e periódicos. Você pode criar uma sequência de trabalhos exclusivos chamando um destes métodos. O uso de um ou outro depende de você estar programando um trabalho recorrente ou único.

Esses dois métodos aceitam três argumentos:

  • uniqueWorkName: uma String usada para identificar exclusivamente a solicitação de trabalho.
  • existingWorkPolicy: uma enum que informa ao WorkManager o que fazer se já existir uma cadeia de trabalho incompleta com o nome exclusivo. Para entender melhor, consulte a política de resolução de conflitos.
  • work: a WorkRequest a ser programada.

Com um trabalho exclusivo, podemos corrigir nosso problema de programação duplicada observado anteriormente.

Kotlin


val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

Java


PeriodicWorkRequest sendLogsWorkRequest = new
      PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
              .setConstraints(new Constraints.Builder()
              .setRequiresCharging(true)
          .build()
      )
     .build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

Agora, se o código for executado enquanto um job sendLogs já estiver na fila, o job existente será mantido, e nenhum novo job será adicionado.

Sequências de trabalhos exclusivos também podem ser úteis caso você precise aumentar gradualmente uma longa cadeia de tarefas. Por exemplo, um app de edição de fotos pode permitir que os usuários desfaçam uma longa cadeia de ações. Cada uma dessas operações de desfazer uma ação pode demorar um pouco, mas elas precisam ser realizadas na ordem correta. Nesse caso, o app pode criar uma cadeia de "desfazer" e anexar cada operação de desfazer à cadeia, conforme necessário. Consulte Como encadear trabalhos para conferir os detalhes.

Política de resolução de conflitos

Ao agendar um trabalho exclusivo, você precisa informar ao WorkManager a ação que será realizada quando houver um conflito. Faça isso transmitindo um tipo enumerado ao processar o trabalho.

Para um trabalho único, você fornece uma ExistingWorkPolicy, que oferece suporte a quatro opções para resolver o conflito.

  • REPLACE (substituir) o trabalho existente pelo novo trabalho. Essa opção cancela o trabalho existente.
  • KEEP (manter) o trabalho existente e ignorar o novo trabalho.
  • APPEND (anexar) o novo trabalho ao final do trabalho existente. Essa política fará com que o novo trabalho seja encadeado ao trabalho existente. Ou seja, o novo será executado após o existente.

O trabalho existente se torna um pré-requisito do novo trabalho. Se o trabalho existente se tornar CANCELLED ou FAILED, o novo trabalho também será CANCELLED ou FAILED. Se você quiser que o novo trabalho seja executado independentemente do status do trabalho existente, use APPEND_OR_REPLACE.

  • APPEND_OR_REPLACE funciona de forma semelhante a APPEND, mas não depende do pré-requisito de status do trabalho. Se o trabalho existente tiver o status CANCELLED ou FAILED, o novo trabalho ainda será executado.

Para trabalhos periódicos, você fornece um ExistingPeriodicWorkPolicy, que é compatível com duas opções, REPLACE e KEEP. Essas opções funcionam da mesma forma que as contrapartes da ExistingWorkPolicy.

Como observar seu trabalho

A qualquer momento após colocar o trabalho em fila, você poderá consultar o WorkManager pelo name, id ou por uma tag associados a ele para verificar o status.

Kotlin


// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

Java


// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>


A consulta retorna um ListenableFuture de um objeto WorkInfo, que inclui o id do trabalho, as tags, o State atual e qualquer conjunto de dados de saída via Result.success(outputData).

Uma variante LiveData de cada método permite que você registre um listener para observar as mudanças das WorkInfo. Por exemplo, se você quisesse exibir uma mensagem para o usuário quando algum trabalho fosse concluído, poderia configurá-la assim:

Kotlin


workManager.getWorkInfoByIdLiveData(syncWorker.id)
               .observe(viewLifecycleOwner) { workInfo ->
   if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
       Snackbar.make(requireView(), 
      R.string.work_completed, Snackbar.LENGTH_SHORT)
           .show()
   }
}


Java


workManager.getWorkInfoByIdLiveData(syncWorker.id)
        .observe(getViewLifecycleOwner(), workInfo -> {
    if (workInfo.getState() != null &&
            workInfo.getState() == WorkInfo.State.SUCCEEDED) {
        Snackbar.make(requireView(),
                    R.string.work_completed, Snackbar.LENGTH_SHORT)
                .show();
   }
});

Consultas de trabalho complexas

O WorkManager 2.4.0 e versões mais recentes são compatíveis com consultas complexas para jobs em fila usando objetos WorkQuery. O WorkQuery é compatível com a consulta do trabalho por meio de uma combinação das tags, do estado e do nome exclusivo.

O exemplo a seguir mostra como encontrar todo o trabalho com a tag "syncTag", que está no estado FAILED ou CANCELLED e tem o nome exclusivo "preProcess" ou "sync".

Kotlin


val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

Java


WorkQuery workQuery = WorkQuery.Builder
       .fromTags(Arrays.asList("syncTag"))
       .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(Arrays.asList("preProcess", "sync")
     )
    .build();

ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);

Cada componente (tag, estado ou nome) em uma WorkQuery é adicionado por AND aos outros. Cada valor em um componente é incluído com OR. Por exemplo: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...).

A WorkQuery também funciona com o equivalente do LiveData, getWorkInfosLiveData().

Como cancelar e interromper o trabalho

Se você não precisa mais de um trabalho que estava na fila, pode solicitar o cancelamento. O trabalho pode ser cancelado por name, id ou por uma tag associados a ele.

Kotlin


// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")


Java


// by id
workManager.cancelWorkById(syncWorker.id);

// by name
workManager.cancelUniqueWork("sync");

// by tag
workManager.cancelAllWorkByTag("syncTag");

Em segundo plano, o WorkManager verifica o State do trabalho. Se o trabalho já estiver concluído, nada acontecerá. Caso contrário, o estado será mudado para CANCELLED, e o trabalho não será executado no futuro. Todos os jobs de WorkRequest que forem dependentes desse trabalho também serão CANCELLED.

Atualmente, o trabalho RUNNING recebe uma chamada para ListenableWorker.onStopped(). Modifique esse método para processar uma possível limpeza. Consulte o artigo Interromper um worker em execução para mais informações.

Parar um worker em execução

Há alguns motivos diferentes para interromper a execução do Worker no WorkManager:

  • Você pediu explicitamente que ele fosse cancelado (chamando WorkManager.cancelWorkById(UUID), por exemplo).
  • No caso de um trabalho exclusivo, você colocou uma nova WorkRequest em fila com uma ExistingWorkPolicy de REPLACE. A WorkRequest antiga será imediatamente considerada cancelada.
  • As restrições do trabalho não são mais atendidas.
  • O sistema instruiu o app a interromper o trabalho por algum motivo. Isso poderá acontecer se você exceder o prazo de execução de 10 minutos. O trabalho está programado para ser repetido mais tarde.

Nessas condições, o worker será interrompido.

Você precisa cancelar cooperativamente qualquer trabalho em andamento e liberar todos os recursos em uso pelo worker. Por exemplo, nesse momento é preciso fechar os identificadores abertos relacionados a bancos de dados e arquivos. Há dois mecanismos que você pode usar para saber quando o worker será parado.

Callback onStopped()

O WorkManager invoca ListenableWorker.onStopped() assim que seu worker é interrompido. Modifique esse método para fechar todos os recursos em atividade.

Propriedade isStopped()

Chame o método ListenableWorker.isStopped() para verificar se o worker já foi interrompido. Se você estiver realizando operações repetitivas ou de longa duração no worker, verifique essa propriedade com frequência e use-a como um sinal para interromper o trabalho o mais rápido possível.

Observação: o WorkManager ignora o Result definido por um worker que tenha recebido o sinal onStop, porque ele já foi considerado interrompido.