WorkManager é uma biblioteca usada para a programação e a execução de tarefas adiáveis em segundo plano no Android. Ele é a substituição recomendada para o Firebase JobDispatcher. O guia a seguir traz orientações sobre o processo de migração da implementação do Firebase JobDispatcher para o WorkManager.
Configuração do Gradle
Para importar a biblioteca do WorkManager para seu projeto Android, adicione as dependências listadas em Primeiros passos com o WorkManager.
Do JobService aos workers
FirebaseJobDispatcher
usa uma subclasse de
JobService
como ponto de entrada para definir o trabalho que precisa ser feito. Você pode usar
JobService diretamente ou
SimpleJobService (links em inglês).
Um JobService será parecido com o seguinte:
Kotlin
import com.firebase.jobdispatcher.JobParameters import com.firebase.jobdispatcher.JobService class MyJobService : JobService() { override fun onStartJob(job: JobParameters): Boolean { // Do some work here return false // Answers the question: "Is there still work going on?" } override fun onStopJob(job: JobParameters): Boolean { return false // Answers the question: "Should this job be retried?" } }
Java
import com.firebase.jobdispatcher.JobParameters; import com.firebase.jobdispatcher.JobService; public class MyJobService extends JobService { @Override public boolean onStartJob(JobParameters job) { // Do some work here return false; // Answers the question: "Is there still work going on?" } @Override public boolean onStopJob(JobParameters job) { return false; // Answers the question: "Should this job be retried?" } }
Se estiver usando SimpleJobService, você terá substituído onRunJob(),
que retorna um tipo @JobResult int.
A principal diferença é que, quando você usar JobService diretamente, onStartJob()
será chamado na linha de execução principal. Além disso, é responsabilidade do app descarregar o
trabalho em uma linha de execução em segundo plano. Por outro lado, se você estiver usando
SimpleJobService, esse serviço será responsável por executar seu trabalho em uma linha de execução em
segundo plano.
O WorkManager tem conceitos semelhantes. A unidade de trabalho fundamental no WorkManager é
um ListenableWorker. Também existem
outros subtipos úteis de workers, como
Worker,
RxWorker e CoroutineWorker (ao
usar corrotinas Kotlin).
Mapeamentos do JobService para um ListenableWorker
Caso você esteja usando JobService diretamente, o worker para o qual ele será mapeado será um
ListenableWorker. Caso esteja usando SimpleJobService, use Worker.
Vamos ver o exemplo acima (MyJobService) e analisar como podemos convertê-lo
em um ListenableWorker.
Kotlin
import android.content.Context import androidx.work.ListenableWorker import androidx.work.ListenableWorker.Result import androidx.work.WorkerParameters import com.google.common.util.concurrent.ListenableFuture class MyWorker(appContext: Context, params: WorkerParameters) : ListenableWorker(appContext, params) { override fun startWork(): ListenableFuture<ListenableWorker.Result> { // Do your work here. TODO("Return a ListenableFuture<Result>") } override fun onStopped() { // Cleanup because you are being stopped. } }
Java
import android.content.Context; import androidx.work.ListenableWorker; import androidx.work.ListenableWorker.Result; import androidx.work.WorkerParameters; import com.google.common.util.concurrent.ListenableFuture; class MyWorker extends ListenableWorker { public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) { super(appContext, params); } @Override public ListenableFuture<ListenableWorker.Result> startWork() { // Do your work here. Data input = getInputData(); // Return a ListenableFuture<> } @Override public void onStopped() { // Cleanup because you are being stopped. } }
A unidade de trabalho básica no WorkManager é um ListenableWorker. Assim como
JobService.onStartJob(), startWork() é chamado na linha de execução principal. Aqui,
MyWorker implementa ListenableWorker e retorna uma instância de
ListenableFuture,
que é usada para sinalizar a conclusão do trabalho de forma assíncrona. Aqui, você precisa escolher
sua estratégia de linha de execução.
O ListenableFuture retornará um tipo ListenableWorker.Result,
que pode ser Result.success(), Result.success(Data outputData),
Result.retry(), Result.failure() ou Result.failure(Data outputData). Para
mais informações, consulte a página de referência de
ListenableWorker.Result.
onStopped() é chamado para sinalizar que o ListenableWorker precisa ser interrompido
porque as restrições deixaram de ser cumpridas (por exemplo, porque a
rede não está mais disponível) ou porque um método WorkManager.cancel…() foi
chamado. onStopped() também poderá ser chamado se o SO encerrar seu
trabalho por algum motivo.
Mapeamentos do SimpleJobService para um Worker
Ao usar SimpleJobService, o worker acima terá uma aparência semelhante à seguinte:
Kotlin
import android.content.Context; import androidx.work.Data; import androidx.work.ListenableWorker.Result; import androidx.work.Worker; import androidx.work.WorkerParameters; class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { TODO("Return a Result") } override fun onStopped() { super.onStopped() TODO("Cleanup, because you are being stopped") } }
Java
import android.content.Context; import androidx.work.Data; import androidx.work.ListenableWorker.Result; import androidx.work.Worker; import androidx.work.WorkerParameters; class MyWorker extends Worker { public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) { super(appContext, params); } @Override public Result doWork() { // Do your work here. Data input = getInputData(); // Return a ListenableWorker.Result Data outputData = new Data.Builder() .putString(“Key”, “value”) .build(); return Result.success(outputData); } @Override public void onStopped() { // Cleanup because you are being stopped. } }
Aqui, doWork() retorna uma instância de ListenableWorker.Result para sinalizar a
conclusão de trabalho de forma síncrona. Isso é semelhante ao SimpleJobService, que programa jobs em uma linha de execução em segundo plano.
Mapeamentos do JobBuilder para WorkRequest
O FirebaseJobBuilder usa Job.Builder para representar metadados Job. O WorkManager
usa WorkRequest para essa função.
O WorkManager tem dois tipos de WorkRequests:
OneTimeWorkRequest e
PeriodicWorkRequest.
Se você estiver usando Job.Builder.setRecurring(true),
crie um novo PeriodicWorkRequest. Caso contrário, use um
OneTimeWorkRequest.
Vamos ver como seria a programação de um Job complexo
com FirebaseJobDispatcher:
Kotlin
val input: Bundle = Bundle().apply { putString("some_key", "some_value") } val job = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyService::class.java) // uniquely identifies the job .setTag("my-unique-tag") // one-off job .setRecurring(false) // don't persist past a device reboot .setLifetime(Lifetime.UNTIL_NEXT_BOOT) // start between 0 and 60 seconds from now .setTrigger(Trigger.executionWindow(0, 60)) // don't overwrite an existing job with the same tag .setReplaceCurrent(false) // retry with exponential backoff .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) .setConstraints( // only run on an unmetered network Constraint.ON_UNMETERED_NETWORK, // // only run when the device is charging Constraint.DEVICE_CHARGING ) .setExtras(input) .build() dispatcher.mustSchedule(job)
Java
Bundle input = new Bundle(); input.putString("some_key", "some_value"); Job myJob = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyJobService.class) // uniquely identifies the job .setTag("my-unique-tag") // one-off job .setRecurring(false) // don't persist past a device reboot .setLifetime(Lifetime.UNTIL_NEXT_BOOT) // start between 0 and 60 seconds from now .setTrigger(Trigger.executionWindow(0, 60)) // don't overwrite an existing job with the same tag .setReplaceCurrent(false) // retry with exponential backoff .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) // constraints that need to be satisfied for the job to run .setConstraints( // only run on an unmetered network Constraint.ON_UNMETERED_NETWORK, // only run when the device is charging Constraint.DEVICE_CHARGING ) .setExtras(input) .build(); dispatcher.mustSchedule(myJob);
Para conseguir o mesmo resultado com o WorkManager, é preciso:
- criar dados de entrada que possam ser usados como entrada para o
Worker; - criar um
WorkRequestcom os dados de entrada e restrições semelhantes aos que foram definidos acima paraFirebaseJobDispatcher; - colocar o
WorkRequestna fila.
Configurar entradas para o Worker
FirebaseJobDispatcher usa um Bundle para enviar dados de entrada para o JobService.
O WorkManager usa Data. O
resultado é:
Kotlin
import androidx.work.workDataOf val data = workDataOf("some_key" to "some_val")
Java
import androidx.work.Data; Data input = new Data.Builder() .putString("some_key", "some_value") .build();
Configurar restrições para o Worker
FirebaseJobDispatcher usos
Job.Builder.setConstaints(...)
para configurar restrições em jobs. O WorkManager usa
Constraints.
Kotlin
import androidx.work.* val constraints: Constraints = Constraints.Builder().apply { setRequiredNetworkType(NetworkType.CONNECTED) setRequiresCharging(true) }.build()
Java
import androidx.work.Constraints; import androidx.work.Constraints.Builder; import androidx.work.NetworkType; Constraints constraints = new Constraints.Builder() // The Worker needs Network connectivity .setRequiredNetworkType(NetworkType.CONNECTED) // Needs the device to be charging .setRequiresCharging(true) .build();
Criar o WorkRequest (único ou periódico)
Para criar OneTimeWorkRequests e PeriodicWorkRequests, é necessário usar
OneTimeWorkRequest.Builder
e PeriodicWorkRequest.Builder.
Para criar um OneTimeWorkRequest semelhante ao Job acima,
faça o seguinte:
Kotlin
import androidx.work.* import java.util.concurrent.TimeUnit val constraints: Constraints = TODO("Define constraints as above") val request: OneTimeWorkRequest = // Tell which work to execute OneTimeWorkRequestBuilder<MyWorker>() // Sets the input data for the ListenableWorker .setInputData(input) // If you want to delay the start of work by 60 seconds .setInitialDelay(60, TimeUnit.SECONDS) // Set a backoff criteria to be used when retry-ing .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS) // Set additional constraints .setConstraints(constraints) .build()
Java
import androidx.work.BackoffCriteria; import androidx.work.Constraints; import androidx.work.Constraints.Builder; import androidx.work.NetworkType; import androidx.work.OneTimeWorkRequest; import androidx.work.OneTimeWorkRequest.Builder; import androidx.work.Data; // Define constraints (as above) Constraints constraints = ... OneTimeWorkRequest request = // Tell which work to execute new OneTimeWorkRequest.Builder(MyWorker.class) // Sets the input data for the ListenableWorker .setInputData(inputData) // If you want to delay the start of work by 60 seconds .setInitialDelay(60, TimeUnit.SECONDS) // Set a backoff criteria to be used when retry-ing .setBackoffCriteria(BackoffCriteria.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS) // Set additional constraints .setConstraints(constraints) .build();
A principal diferença nesse caso é que os jobs do WorkManager são sempre mantidos automaticamente na reinicialização do dispositivo.
Se você quiser criar um PeriodicWorkRequest, faça algo como o seguinte:
Kotlin
val constraints: Constraints = TODO("Define constraints as above") val request: PeriodicWorkRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES) // Sets the input data for the ListenableWorker .setInputData(input) // Other setters .build()
Java
import androidx.work.BackoffCriteria; import androidx.work.Constraints; import androidx.work.Constraints.Builder; import androidx.work.NetworkType; import androidx.work.PeriodicWorkRequest; import androidx.work.PeriodicWorkRequest.Builder; import androidx.work.Data; // Define constraints (as above) Constraints constraints = ... PeriodicWorkRequest request = // Executes MyWorker every 15 minutes new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.MINUTES) // Sets the input data for the ListenableWorker .setInputData(input) . // other setters (as above) .build();
Programar trabalhos
Agora que você definiu um Worker e uma WorkRequest, já está pronto para
programar o trabalho.
Cada Job definido com FirebaseJobDispatcher tinha uma tag usada para identificar o
Job de forma exclusiva. Isso também fornecia uma forma para o aplicativo informar
o programador caso a instância de um Job precisasse substituir uma cópia existente do
Job chamando setReplaceCurrent.
Kotlin
val job = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyService::class.java) // uniquely identifies the job .setTag("my-unique-tag") // don't overwrite an existing job with the same tag .setRecurring(false) // Other setters... .build()
Java
Job myJob = dispatcher.newJobBuilder() // the JobService that will be called .setService(MyJobService.class) // uniquely identifies the job .setTag("my-unique-tag") // don't overwrite an existing job with the same tag .setReplaceCurrent(false) // other setters // ... dispatcher.mustSchedule(myJob);
Com o WorkManager, você pode alcançar o mesmo resultado usando
as APIs enqueueUniqueWork() e enqueueUniquePeriodicWork() (ao usar uma
OneTimeWorkRequest e uma PeriodicWorkRequest, respectivamente). Para saber mais,
consulte as páginas de referênciareferência de
WorkManager.enqueueUniqueWork()
e WorkManager.enqueueUniquePeriodicWork().
O código ficará mais ou menos assim:
Kotlin
import androidx.work.* val request: OneTimeWorkRequest = TODO("A WorkRequest") WorkManager.getInstance(myContext) .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, request)
Java
import androidx.work.ExistingWorkPolicy; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; OneTimeWorkRequest workRequest = // a WorkRequest; WorkManager.getInstance(myContext) // Use ExistingWorkPolicy.REPLACE to cancel and delete any existing pending // (uncompleted) work with the same unique name. Then, insert the newly-specified // work. .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, workRequest);
Cancelar trabalhos
Você pode cancelar trabalhos com o FirebaseJobDispatcher usando:
Kotlin
dispatcher.cancel("my-unique-tag")
Java
dispatcher.cancel("my-unique-tag");
Com o WorkManager, você pode usar:
Kotlin
import androidx.work.WorkManager WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name")
Java
import androidx.work.WorkManager; WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name");
Inicializar o WorkManager
Em geral, o WorkManager é autoinicializado usando um ContentProvider.
Se você precisa de mais controle sobre como o WorkManager organiza e programa o trabalho,
pode personalizar a configuração e a inicialização dele.