Migrazione da Firebase JobDispatcher a WorkManager

WorkManager è una libreria per la pianificazione e l'esecuzione di lavori in background differibili su Android. È la sostituzione consigliata per Firebase JobDispatcher. La questa guida ti aiuterà a eseguire la migrazione di Firebase Implementazione di JobDispatcher in WorkManager.

Configurazione di Gradle

Per importare la libreria WorkManager nel tuo progetto Android, aggiungi l'elemento elencate in Guida introduttiva all'utilizzo di WorkManager.

Da JobService ai worker

FirebaseJobDispatcher utilizza una sottoclasse JobService come punto di partenza per definire il lavoro da svolgere. Potresti essere usando direttamente JobService SimpleJobService

Un JobService avrà un aspetto simile a questo:

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 utilizzi SimpleJobService avrai eseguito l'override di onRunJob(), che restituisce un tipo @JobResult int.

La differenza principale è che usi direttamente JobService, onStartJob() viene chiamato nel thread principale ed è responsabilità dell'app scaricare a un thread in background. D'altra parte, se utilizzi SimpleJobService, quel servizio è responsabile dell'esecuzione del tuo lavoro su una in background.

WorkManager presenta concetti simili. L'unità fondamentale di lavoro in WorkManager è Un ListenableWorker. Esistono ma anche altri utili sottotipi di worker Worker, RxWorker e CoroutineWorker (quando utilizzando le coroutine Kotlin).

JobService viene mappato a un ListenableWorker

Se utilizzi direttamente JobService, il worker a cui viene mappato è un ListenableWorker. Se usi SimpleJobService, dovresti usare Worker in alternativa.

Usiamo l'esempio precedente (MyJobService) e vediamo come convertirlo a 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.
  }
}

L'unità di lavoro di base in WorkManager è ListenableWorker. Proprio come JobService.onStartJob(), startWork() viene chiamato nel thread principale. Qui MyWorker implementa ListenableWorker e restituisce un'istanza di ListenableFuture, che viene utilizzato per segnalare il completamento del lavoro in modo asincrono. Dovresti scegliere il una strategia di thread.

ListenableFuture in questo caso restituisce un tipo ListenableWorker.Result che può essere uno tra Result.success(), Result.success(Data outputData), Result.retry(), Result.failure() o Result.failure(Data outputData). Per ulteriori informazioni, consulta la pagina di riferimento per ListenableWorker.Result

onStopped() viene chiamato per segnalare che ListenableWorker deve essere interrotto, perché i vincoli non vengono più soddisfatti (ad esempio, perché non è più disponibile) oppure perché è stato eseguito un metodo WorkManager.cancel…() chiamato. Potrebbe essere chiamato anche onStopped() se il sistema operativo decide di arrestare per qualche motivo.

SimpleJobService viene mappato a un worker

Quando utilizzi SimpleJobService, il worker riportato sopra avrà il seguente aspetto:

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.
  }
}

In questo caso doWork() restituisce un'istanza di ListenableWorker.Result per segnalare il funzionamento il completamento in modo sincrono. È simile a SimpleJobService, che prevede job su un thread in background.

JobBuilder mappa a WorkRequest

FirebaseJobBuilder utilizza Job.Builder per rappresentare i metadati Job. Responsabile del lavoro utilizza WorkRequest per ricoprire questo ruolo.

WorkManager ha due tipi di WorkRequest: OneTimeWorkRequest e PeriodicWorkRequest.

Se al momento utilizzi Job.Builder.setRecurring(true), dovresti crea un nuovo PeriodicWorkRequest. In caso contrario, devi utilizzare OneTimeWorkRequest.

Vediamo cosa potrebbe fare la pianificazione di un Job complesso con FirebaseJobDispatcher simile al seguente:

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);

Per ottenere lo stesso risultato con WorkManager, dovrai:

  • Crea dati di input che possono essere utilizzati come input per Worker.
  • Crea un WorkRequest con i dati di input e i vincoli simili a quelli definita sopra per FirebaseJobDispatcher.
  • Metti in coda WorkRequest.

Configurazione degli input per il worker

FirebaseJobDispatcher utilizza un Bundle per inviare i dati di input a JobService. WorkManager utilizza invece Data. Quindi... che diventa:

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();

Configurazione dei vincoli per il worker

FirebaseJobDispatcher usi Job.Builder.setConstaints(...) per configurare i vincoli sui job. WorkManager utilizza 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();

Creazione della WorkRequest (una tantum o periodica)

Per creare OneTimeWorkRequest e PeriodicWorkRequest, devi usare OneTimeWorkRequest.Builder: e PeriodicWorkRequest.Builder.

Per creare un OneTimeWorkRequest che è simile a quanto sopra Job devi segui questi passaggi:

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();

La differenza principale è che i job di WorkManager sono sempre resi persistenti il dispositivo si riavvia automaticamente.

Se vuoi creare un PeriodicWorkRequest, ad esempio:

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();

Programmazione del lavoro

Ora che hai definito un Worker e un WorkRequest, è tutto pronto per pianificare il lavoro.

Ogni Job definito con FirebaseJobDispatcher aveva un tag utilizzato per identificare in modo univoco un Job. Ha anche fornito all'applicazione un modo per capire scheduler se questa istanza di un Job dovesse sostituire una copia esistente del Job chiamando il numero 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);

Quando utilizzi WorkManager, puoi ottenere lo stesso risultato utilizzando enqueueUniqueWork() e enqueueUniquePeriodicWork() API (quando si utilizza un OneTimeWorkRequest e PeriodicWorkRequest, rispettivamente). Per ulteriori informazioni informazioni, consulta le pagine di riferimento per WorkManager.enqueueUniqueWork() e WorkManager.enqueueUniquePeriodicWork().

L'URL avrà il seguente aspetto:

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);

Annullamento del lavoro

Con FirebaseJobDispatcher puoi annullare il lavoro utilizzando:

Kotlin

dispatcher.cancel("my-unique-tag")

Java

dispatcher.cancel("my-unique-tag");

Quando utilizzi WorkManager puoi utilizzare:

Kotlin

import androidx.work.WorkManager
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name")

Java

import androidx.work.WorkManager;
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name");

Inizializzazione di WorkManager

WorkManager si inizializza in genere utilizzando un ContentProvider. Se hai bisogno di un maggiore controllo sul funzionamento dell'organizzazione e delle pianificazioni di WorkManager, puoi personalizzare la configurazione e l'inizializzazione di WorkManager.