Tópicos avançados do WorkManager

O WorkManager facilita a configuração e a programação de solicitações de tarefas elaboradas. Você pode usar as APIs para casos como estes:

Tarefas encadeadas

É possível que seu app tenha que executar diversas tarefas em uma ordem específica. O WorkManager permite que você crie e coloque em fila uma sequência de trabalhos que especifica diversas tarefas e a ordem em que elas serão executadas.

Por exemplo, suponha que seu app tenha três objetos OneTimeWorkRequest: workA, workB e workC. As tarefas precisam ser executadas nessa ordem. Para colocá-las em fila, crie uma sequência com o método WorkManager.beginWith(OneTimeWorkRequest), transmitindo o primeiro objeto OneTimeWorkRequest. Esse método retorna um objeto WorkContinuation, que definirá a sequência de tarefas. Em seguida, adicione os objetos OneTimeWorkRequest restantes na ordem correta com WorkContinuation.then(OneTimeWorkRequest) e, por fim, coloque a sequência toda em fila com WorkContinuation.enqueue():

Kotlin

WorkManager.getInstance(myContext)
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
    .enqueue()

Java

WorkManager.getInstance(myContext)
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
    .enqueue();

O WorkManager executa as tarefas na ordem solicitada, de acordo com as restrições especificadas de cada tarefa. Se alguma tarefa retornar Result.failure(), a sequência inteira será encerrada.

Você também pode transmitir vários objetos OneTimeWorkRequest para qualquer chamada beginWith(List<OneTimeWorkRequest>) e then(List<OneTimeWorkRequest>). Se você transmitir vários objetos OneTimeWorkRequest para uma única chamada de método, o WorkManager executará todas essas tarefas (em paralelo) antes de executar o resto da sequência. Exemplo:

Kotlin

WorkManager.getInstance(myContext)
    // First, run all the A tasks (in parallel):
    .beginWith(Arrays.asList(workA1, workA2, workA3))
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in parallel):
    .then(Arrays.asList(workC1, workC2))
    .enqueue()

Java

WorkManager.getInstance(myContext)
    // First, run all the A tasks (in parallel):
    .beginWith(Arrays.asList(workA1, workA2, workA3))
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in parallel):
    .then(Arrays.asList(workC1, workC2))
    .enqueue();

Você pode criar sequências mais complexas mesclando várias cadeias com os métodos WorkContinuation.combine(List<OneTimeWorkRequest>). Por exemplo, suponha que você queira executar uma sequência como esta:

Figura 1. Você pode usar WorkContinuation para configurar tarefas encadeadas complexas.

Para configurar essa sequência, crie duas cadeias separadas e mescle-as em uma terceira:

Kotlin

val chain1 = WorkManager.getInstance(myContext)
    .beginWith(workA)
    .then(workB)
val chain2 = WorkManager.getInstance(myContext)
    .beginWith(workC)
    .then(workD)
val chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE)
chain3.enqueue()

Java

WorkContinuation chain1 = WorkManager.getInstance(myContext)
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance(myContext)
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE);
chain3.enqueue();

Nesse caso, o WorkManager executa workA antes de workB. Ele também executa workC antes de workD. Quando workB e workD estiverem concluídos, o WorkManager executará workE.

Há diversas variantes dos métodos WorkContinuation que oferecem abreviações para situações específicas. Para ver mais detalhes, consulte a referência de WorkContinuation.

Sequências de trabalho únicas

Você pode criar uma sequência de trabalhos exclusivos iniciando a sequência com uma chamada para beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) em vez de beginWith(OneTimeWorkRequest). Cada sequência de trabalhos exclusivos tem um nome. O WorkManager só permite uma sequência de trabalhos com o mesmo nome por vez. Ao criar uma nova sequência de trabalhos exclusivos, especifique o que o WorkManager fará se existir uma sequência não concluída com o mesmo nome:

  • Cancelar a sequência existente e usar REPLACE para substituí-la por outra nova.
  • Usar KEEP para manter a sequência existente e ignorar a nova solicitação.
  • Usar APPEND para anexar a nova sequência à existente e executar a primeira tarefa da nova sequência depois que a última tarefa da sequência existente terminar.

Sequências de trabalhos exclusivos podem ser úteis caso você tenha uma tarefa que não possa ser colocada em fila várias vezes. Por exemplo, se o app precisar sincronizar dados com a rede, você poderá colocar uma sequência com o nome "sync" na fila e especificar que a nova tarefa será ignorada se existir uma sequência com esse nome. 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.

Parâmetros de entrada e valores retornados

Para maior flexibilidade, você pode transmitir argumentos às suas tarefas e fazer com que elas retornem resultados. Os valores passados e retornados são pares de chave-valor. Para transmitir um argumento para uma tarefa, chame o método WorkRequest.Builder.setInputData(Data) antes de criar o objeto WorkRequest. Esse método usa um objeto Data, que é criado com Data.Builder. A classe Worker pode acessar esses argumentos chamando Worker.getInputData(). Para gerar um valor de retorno, a tarefa precisa incluí-lo no Result (por exemplo, retornando Result.success(Data)). Você pode conseguir o resultado observando a WorkInfo da tarefa.

Por exemplo, suponha que você tenha uma classe Worker que realiza um cálculo muito demorado. O código a seguir mostra como seria a classe Worker:

Kotlin

// Define the parameter keys:
const val KEY_X_ARG = "X"
const val KEY_Y_ARG = "Y"
const val KEY_Z_ARG = "Z"

// ...and the result key:
const val KEY_RESULT = "result"

// Define the Worker class:
class MathWorker(context : Context, params : WorkerParameters)
    : Worker(context, params)  {

    override fun doWork(): Result {
        val x = inputData.getInt(KEY_X_ARG, 0)
        val y = inputData.getInt(KEY_Y_ARG, 0)
        val z = inputData.getInt(KEY_Z_ARG, 0)

        // ...do the math...
        val result = myLongCalculation(x, y, z);

        //...set the output, and we're done!
        val output: Data = workDataOf(KEY_RESULT to result)

        return Result.success(output)
    }
}

Java

// Define the Worker class:
public class MathWorker extends Worker {

    // Define the parameter keys:
    public static final String KEY_X_ARG = "X";
    public static final String KEY_Y_ARG = "Y";
    public static final String KEY_Z_ARG = "Z";
    // ...and the result key:
    public static final String KEY_RESULT = "result";

    public MathWorker(
        @NonNull Context context,
        @NonNull WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Result doWork() {
        // Fetch the arguments (and specify default values):
        int x = getInputData().getInt(KEY_X_ARG, 0);
        int y = getInputData().getInt(KEY_Y_ARG, 0);
        int z = getInputData().getInt(KEY_Z_ARG, 0);

        // ...do the math...
        int result = myLongCalculation(x, y, z);

        //...set the output, and we're done!
        Data output = new Data.Builder()
            .putInt(KEY_RESULT, result)
            .build();
        return Result.success(output);
    }
}

Para criar o trabalho e passar os argumentos, você usaria um código assim:

Kotlin

val myData: Data = workDataOf("KEY_X_ARG" to 42,
                       "KEY_Y_ARG" to 421,
                       "KEY_Z_ARG" to 8675309)

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(myData)
        .build()
WorkManager.getInstance(myContext).enqueue(mathWork)

Java

// Create the Data object:
Data myData = new Data.Builder()
    // We need to pass three integers: X, Y, and Z
    .putInt(KEY_X_ARG, 42)
    .putInt(KEY_Y_ARG, 421)
    .putInt(KEY_Z_ARG, 8675309)
    // ... and build the actual Data object:
    .build();

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class)
        .setInputData(myData)
        .build();
WorkManager.getInstance(myContext).enqueue(mathWork);

O valor retornado seria disponibilizado na WorkInfo da tarefa:

Kotlin

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.id)
        .observe(this, Observer { info ->
            if (info != null && info.state.isFinished) {
                val myResult = info.outputData.getInt(KEY_RESULT,
                      myDefaultValue)
                // ... do something with the result ...
            }
        })

Java

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.getId())
    .observe(lifecycleOwner, info -> {
         if (info != null && info.getState().isFinished()) {
           int myResult = info.getOutputData().getInt(KEY_RESULT,
                  myDefaultValue));
           // ... do something with the result ...
         }
    });

Se você encadear tarefas, os resultados de uma tarefa serão disponibilizados como entradas para a tarefa seguinte na cadeia. No caso de uma cadeia simples, com apenas um OneTimeWorkRequest seguida por outro OneTimeWorkRequest, a primeira tarefa retorna o resultado chamando Result.success(Data) e a tarefa seguinte busca esse resultado chamando getInputData(). Se a cadeia for mais complexa (por exemplo, porque várias tarefas enviam resultados para uma única tarefa seguinte), defina um InputMerger no OneTimeWorkRequest.Builder para especificar o que acontecerá se diferentes tarefas retornarem um resultado com a mesma chave.

Outros recursos

Para saber mais sobre o WorkManager, consulte os recursos listados a seguir.

Amostras