O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Migrar do Firebase JobDispatcher para o WorkManager

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 seguintes dependências ao arquivo build.gradle do aplicativo:

dependencies {
  def work_version = "2.4.0"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional - GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
  }

Do JobService ao Workers

FirebaseJobDispatcher usa uma subclasse de JobService como ponto de entrada para definir o trabalho que precisa ser feito (links em inglês). Você pode usar JobService diretamente ou SimpleJobService.

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 usar 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 (link em inglês), que é usada para sinalizar a conclusão do trabalho de forma assíncrona. Aqui, você precisa escolher a própria estratégia de encadeamento.

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 saber mais, consulte a página de referência do 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 pode 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 ListenableWorker.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 WorkRequests

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 WorkRequest com os dados de entrada e restrições semelhantes aos que foram definidos acima para FirebaseJobDispatcher;
  • colocar o WorkRequest na 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 usa Job.Builder.setConstaints(...) para configurar restrições em jobs (link em inglês). 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 automaticamente mantidos 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 um 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 forneceu 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);
    

Ao usar o WorkManager, você pode alcançar o mesmo resultado usando as APIs enqueueUniqueWork() e enqueueUniquePeriodicWork() (ao usar um OneTimeWorkRequest e um PeriodicWorkRequest, respectivamente). Para saber mais, consulte as páginas de referê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

O WorkManager precisa ser inicializado uma vez por app, geralmente usando ContentProvider ou Application.onCreate().

Em geral, o WorkManager é autoinicializado usando ContentProvider. No entanto, há algumas diferenças sutis em padrões com relação ao tamanho do pool de linhas de execução e o número de workers que podem ser programados em um determinado momento. Por isso, pode ser necessário personalizar o WorkManager.

Geralmente, essa personalização é feita usando WorkManager.initialize(). Isso permite que você personalize o Executor de segundo plano usado para executar os Workers, e o WorkerFactory usado para construir Workers. WorkerFactory é útil no contexto de injeção de dependência. Leia a documentação desse método para garantir a interrupção automática da inicialização do WorkManager.

Para saber mais, consulte a documentação de initialize() e Configuration.Builder.