Definisci le richieste di lavoro

La guida introduttiva ha illustrato come creare un oggetto WorkRequest e metterlo in coda.

In questa guida imparerai a definire e personalizzare gli oggetti WorkRequest per gestire i casi d'uso comuni, ad esempio come:

  • Pianificare il lavoro una tantum e ricorrente
  • Impostare vincoli di lavoro, ad esempio richiedere il Wi-Fi o la ricarica
  • Garantire un ritardo minimo nell'esecuzione del lavoro
  • Impostare strategie di nuovi tentativi e backoff
  • Trasmettere i dati di input al lavoro
  • Raggruppare i lavori correlati utilizzando i tag

Panoramica

Il lavoro è definito in WorkManager utilizzando un WorkRequest. Per pianificare qualsiasi lavoro con WorkManager, devi prima creare un oggetto WorkRequest e poi metterlo in coda.

Kotlin

val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java

WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

L'oggetto WorkRequest contiene tutte le informazioni necessarie a WorkManager per pianificare ed eseguire il lavoro. Include vincoli che devono essere soddisfatti per l'esecuzione del lavoro, informazioni di pianificazione come ritardi o intervalli di ripetizione, configurazione dei nuovi tentativi e può includere dati di input se il lavoro dipende da questi.

WorkRequest è una classe base astratta. Esistono due implementazioni derivate di questa classe che puoi utilizzare per creare la richiesta, OneTimeWorkRequest e PeriodicWorkRequest. Come suggeriscono i nomi, OneTimeWorkRequest è utile per pianificare lavori non ripetitivi, mentre PeriodicWorkRequest è più appropriato per pianificare lavori che si ripetono a un determinato intervallo.

Pianificare il lavoro una tantum

Per i lavori di base, che non richiedono configurazioni aggiuntive, utilizza il metodo statico from:

Kotlin

val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java

WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

Per lavori più complessi, puoi utilizzare un builder:

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

Pianificare l'attività rapida

WorkManager 2.7.0 ha introdotto il concetto di attività rapida. Ciò consente a WorkManager di eseguire lavori importanti, dando al sistema un maggiore controllo sull'accesso alle risorse.

L'attività rapida è caratterizzata da quanto segue:

  • Importanza: il lavoro rapido è adatto per le attività importanti per l'utente o avviate dall'utente.
  • Velocità: il lavoro accelerato è più adatto per le attività brevi che iniziano immediatamente e vengono completate in pochi minuti.
  • Quote: una quota a livello di sistema che limita il tempo di esecuzione in primo piano determina se un processo rapido può essere avviato.
  • Gestione dell'alimentazione: è meno probabile che le limitazioni di gestione dell'alimentazione, come il risparmio energetico e Sospensione, influiscano sulle attività rapide.
  • Latenza: il sistema esegue immediatamente le attività rapide, a condizione che il carico di lavoro attuale del sistema lo consenta. Ciò significa che sono sensibili alla latenza e non possono essere pianificate per l'esecuzione successiva.

Un potenziale caso d'uso per l'attività rapida potrebbe essere all'interno di un'app di chat quando l'utente vuole inviare un messaggio o un'immagine allegata. Allo stesso modo, un'app che gestisce un flusso di pagamento o abbonamento potrebbe anche voler utilizzare il lavoro rapido. Questo perché queste attività sono importanti per l'utente, vengono eseguite rapidamente in background, devono iniziare immediatamente e devono continuare a essere eseguite anche se l'utente chiude l'app.

Quote

Il sistema deve allocare il tempo di esecuzione a un processo rapido prima che possa essere eseguito. Il tempo di esecuzione non è illimitato. Ogni app riceve una quota di tempo di esecuzione. Quando l'app utilizza il tempo di esecuzione e raggiunge la quota allocata, non puoi più eseguire attività rapida finché la quota non viene aggiornata. Ciò consente ad Android di bilanciare in modo più efficace le risorse tra le applicazioni.

La quantità di tempo di esecuzione disponibile per un'app si basa sul bucket di standby e sull'importanza del processo.

Puoi determinare cosa succede quando la quota di esecuzione non consente l'esecuzione immediata di un processo rapido. Per maggiori dettagli, consulta i seguenti snippet.

Eseguire l'attività rapida

A partire da WorkManager 2.7, l'app può chiamare setExpedited() per dichiarare che un oggetto WorkRequest deve essere eseguito il più rapidamente possibile utilizzando un processo rapido. Il seguente snippet di codice fornisce un esempio di come utilizzare setExpedited():

Kotlin

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    <b>.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)</b>
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
    .setInputData(inputData)
    <b>.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)</b>
    .build();

In questo esempio, inizializziamo un'istanza di OneTimeWorkRequest e chiamiamo setExpedited() su di essa. Questa richiesta diventa quindi un'attività rapida. Se la quota lo consente, inizierà a essere eseguita immediatamente in background. Se la quota è stata utilizzata, il parametro OutOfQuotaPolicy indica che la richiesta deve essere eseguita come un normale lavoro non rapido.

Compatibilità con le versioni precedenti e servizi in primo piano

Per mantenere la compatibilità con le versioni precedenti per i job rapidi, WorkManager potrebbe eseguire un servizio in primo piano sulle versioni della piattaforma precedenti ad Android 12. I servizi in primo piano possono mostrare una notifica all'utente.

I metodi getForegroundInfoAsync() e getForegroundInfo() nel worker consentono a WorkManager di mostrare una notifica quando chiami setExpedited() prima di Android 12.

Qualsiasi ListenableWorker deve implementare il metodo getForegroundInfo se vuoi richiedere che l'attività venga eseguita come un processo rapido.

Quando il target è Android 12 o versioni successive, i servizi in primo piano rimangono disponibili tramite il metodo setForeground corrispondente.

Worker

I worker non sanno se il lavoro che stanno svolgendo è rapido o meno. Tuttavia, i worker possono mostrare una notifica su alcune versioni di Android quando un oggetto WorkRequest è stato accelerato.

Per abilitare questa funzionalità, WorkManager fornisce il metodo getForegroundInfoAsync(), che devi implementare in modo che WorkManager possa mostrare una notifica per avviare un oggetto ForegroundService per te, se necessario.

CoroutineWorker

Se utilizzi un oggetto CoroutineWorker, devi implementare getForegroundInfo(). Quindi, passalo a setForeground() all'interno di doWork(). In questo modo, la notifica verrà creata nelle versioni di Android precedenti alla 12.

Considera l'esempio seguente:

  class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
   CoroutineWorker(appContext, workerParams) {

   override suspend fun getForegroundInfo(): ForegroundInfo {
       return ForegroundInfo(
           NOTIFICATION_ID, createNotification()
       )
   }

   override suspend fun doWork(): Result {
       TODO()
   }

    private fun createNotification() : Notification {
       TODO()
    }

}

Norme sulle quote

Puoi controllare cosa succede all'attività rapida quando l'app raggiunge la quota di esecuzione. Per continuare, puoi passare setExpedited():

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, che fa sì che il job venga eseguito come una normale richiesta di lavoro. Lo snippet precedente lo dimostra.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, che fa sì che la richiesta venga annullata se la quota non è sufficiente.

Attività rapida differita

Il sistema tenta di eseguire un determinato processo rapido il prima possibile dopo che è stato richiamato. Tuttavia, come nel caso di altri tipi di job, il sistema potrebbe rinviare l'avvio di una nuova attività rapida, ad esempio nei seguenti casi:

  • Carico: il carico del sistema è troppo elevato, il che può verificarsi quando sono già in esecuzione troppi job o quando il sistema non ha memoria sufficiente.
  • Quota: è stato superato il limite della quota di processi rapidi. L'attività rapida utilizza un sistema di quote basato sui bucket di standby delle app e limita il tempo di esecuzione massimo all'interno di una finestra temporale mobile. Le quote utilizzate per le attività rapide sono più restrittive di quelle utilizzate per altri tipi di job in background.

Pianificare il lavoro periodico

A volte, la tua app potrebbe richiedere che determinati lavori vengano eseguiti periodicamente. Ad esempio, potresti voler eseguire periodicamente il backup dei dati, scaricare nuovi contenuti nell'app o caricare i log su un server.

Ecco come utilizzare PeriodicWorkRequest per creare un WorkRequest oggetto che viene eseguito periodicamente:

Kotlin

val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java

PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

In questo esempio, il lavoro viene pianificato con un intervallo di un'ora.

Il periodo di intervallo è definito come il tempo minimo tra le ripetizioni. L'ora esatta in cui verrà eseguito il worker dipende dai vincoli utilizzati nell'oggetto WorkRequest e dalle ottimizzazioni eseguite dal sistema.

Intervalli di esecuzione flessibili

Se la natura del tuo lavoro lo rende sensibile alla tempistica di esecuzione, puoi configurare il tuo PeriodicWorkRequest in modo che venga eseguito all'interno di un periodo di flessibilità all'interno di ogni periodo di intervallo, come mostrato nella Figura 1.

Puoi impostare un intervallo flessibile per un job periodico. Definisci un intervallo di ripetizione e un intervallo flessibile che specifica un determinato periodo di tempo alla fine dell&#39;intervallo di ripetizione. WorkManager tenta di eseguire il job in un momento qualsiasi dell&#39;intervallo
flessibile di ogni ciclo.

Figura 1. Il diagramma mostra gli intervalli di ripetizione con il periodo flessibile in cui è possibile eseguire il lavoro.

Per definire il lavoro periodico con un periodo di flessibilità, passa un oggetto flexInterval insieme a repeatInterval quando crei l'oggetto PeriodicWorkRequest. Il periodo di flessibilità inizia a repeatInterval - flexInterval e termina alla fine dell'intervallo.

Di seguito è riportato un esempio di lavoro periodico che può essere eseguito durante gli ultimi 15 minuti di ogni periodo di un'ora.

Kotlin

val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

Java

WorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class,
               1, TimeUnit.HOURS,
               15, TimeUnit.MINUTES)
           .build();

L'intervallo di ripetizione deve essere maggiore o uguale a PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS e l'intervallo di flessibilità deve essere maggiore o uguale a PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS`.

Effetto dei vincoli sul lavoro periodico

Puoi applicare vincoli al lavoro periodico. Ad esempio, puoi aggiungere un vincolo alla richiesta di lavoro in modo che il lavoro venga eseguito solo quando il dispositivo dell'utente è in carica. In questo caso, anche se l'intervallo di ripetizione definito è trascorso, PeriodicWorkRequest non verrà eseguito finché questa condizione non viene soddisfatta. Ciò potrebbe causare un ritardo nell'esecuzione di un determinato lavoro o persino l'omissione se le condizioni non vengono soddisfatte entro l'intervallo di esecuzione.

Vincoli di lavoro

Constraints assicurano che il lavoro venga rinviato fino a quando non vengono soddisfatte le condizioni ottimali met. I seguenti vincoli sono disponibili per WorkManager:

NetworkType Vincola il tipo di rete richiesto per l'esecuzione del lavoro. Ad esempio, Wi-Fi (UNMETERED).
BatteryNotLow Se impostato su true, il lavoro non verrà eseguito se il dispositivo è in modalità di batteria scarica.
RequiresCharging Se impostato su true, il lavoro verrà eseguito solo quando il dispositivo è in carica.
DeviceIdle Se impostato su true, richiede che il dispositivo dell'utente sia inattivo prima dell'esecuzione del lavoro. Questa opzione può essere utile per eseguire operazioni batch che altrimenti potrebbero avere un impatto negativo sulle prestazioni di altre app in esecuzione attive sul dispositivo dell'utente.
StorageNotLow Se impostato su true, il lavoro non verrà eseguito se lo spazio di archiviazione dell'utente sul dispositivo è troppo basso.

Per creare un insieme di vincoli e associarlo a un lavoro, crea un' Constraints istanza utilizzando Constraints.Builder() e assegnala a tuo WorkRequest.Builder().

Ad esempio, il seguente codice crea una richiesta di lavoro che viene eseguita solo quando il dispositivo dell'utente è in carica e connesso al Wi-Fi:

Kotlin

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

Java

Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresCharging(true)
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();

Quando vengono specificati più vincoli, il lavoro verrà eseguito solo quando tutti i vincoli vengono soddisfatti.

Se un vincolo non viene soddisfatto durante l'esecuzione del lavoro, WorkManager interromperà il worker. Il lavoro verrà ritentato quando tutti i vincoli saranno soddisfatti.

Lavoro in ritardo

Se il lavoro non ha vincoli o se tutti i vincoli vengono soddisfatti quando il lavoro viene messo in coda, il sistema potrebbe scegliere di eseguire il lavoro immediatamente. Se non vuoi che il lavoro venga eseguito immediatamente, puoi specificare che il lavoro inizi dopo un ritardo iniziale minimo.

Ecco un esempio di come impostare l'esecuzione del lavoro almeno 10 minuti dopo che è stato messo in coda.

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java

WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

Sebbene l'esempio illustri come impostare un ritardo iniziale per un oggetto OneTimeWorkRequest, puoi anche impostare un ritardo iniziale per un oggetto PeriodicWorkRequest. In questo caso, solo la prima esecuzione del lavoro periodico verrà ritardata.

Policy di nuovi tentativi e backoff

Se richiedi a WorkManager di ritentare il lavoro, puoi restituire Result.retry() dal worker. Il lavoro viene quindi ripianificato in base a un ritardo di backoff e a una policy di backoff.

  • Ritardo di backoff specifica la quantità minima di tempo di attesa prima di ritentare il lavoro dopo il primo tentativo. Questo valore non può essere inferiore a 10 secondi (o MIN_BACKOFF_MILLIS).

  • Policy di backoff definisce in che modo il ritardo di backoff deve aumentare nel tempo per i tentativi successivi. WorkManager supporta 2 policy di backoff, LINEAR e EXPONENTIAL.

Ogni richiesta di lavoro ha una policy di backoff e un ritardo di backoff. La policy predefinita è EXPONENTIAL con un ritardo di 30 secondi, ma puoi sostituirla nella configurazione della richiesta di lavoro.

Ecco un esempio di personalizzazione del ritardo e della policy di backoff.

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       WorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setBackoffCriteria(
                       BackoffPolicy.LINEAR,
                       WorkRequest.MIN_BACKOFF_MILLIS,
                       TimeUnit.MILLISECONDS)
               .build();

In questo esempio, il ritardo di backoff minimo è impostato sul valore minimo consentito, ovvero 10 secondi. Poiché la policy è LINEAR, l'intervallo di nuovi tentativi aumenterà di circa 10 secondi a ogni nuovo tentativo. Ad esempio, la prima esecuzione che termina con Result.retry() verrà ritentata dopo 10 secondi, seguita da 20, 30, 40 e così via, se il lavoro continua a restituire Result.retry() dopo i tentativi successivi. Se la policy di backoff fosse impostata su EXPONENTIAL, la sequenza di durata dei nuovi tentativi sarebbe più vicina a 20, 40 e 80.

Tag del lavoro

Ogni richiesta di lavoro ha un identificatore univoco, che può essere utilizzato per identificare il lavoro in un secondo momento per annullarlo o monitorarne l'avanzamento.

Se hai un gruppo di lavori correlati logicamente, potrebbe essere utile anche taggare questi elementi di lavoro. Il tagging ti consente di operare con un gruppo di richieste di lavoro contemporaneamente.

Ad esempio, WorkManager.cancelAllWorkByTag(String) annulla tutte le richieste di lavoro con un tag specifico e WorkManager.getWorkInfosByTag(String) restituisce un elenco di oggetti WorkInfo che possono essere utilizzati per determinare lo stato attuale del lavoro.

Il seguente codice mostra come aggiungere un tag "cleanup" al lavoro:

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

Infine, è possibile aggiungere più tag a una singola richiesta di lavoro. Internamente, questi tag vengono memorizzati come un insieme di stringhe. Per ottenere l'insieme di tag associati a WorkRequest puoi utilizzare WorkInfo.getTags().

Dalla classe Worker, puoi recuperare l'insieme di tag utilizzando ListenableWorker.getTags().

Assegnare dati di input

Il lavoro potrebbe richiedere dati di input per essere eseguito. Ad esempio, un lavoro che gestisce il caricamento di un'immagine potrebbe richiedere l'URI dell'immagine da caricare come input.

I valori di input vengono memorizzati come coppie chiave-valore in un Data oggetto e possono essere impostati nella richiesta di lavoro. WorkManager fornirà i dati di input Data al lavoro quando lo esegue. La classe Worker può accedere agli argomenti di input chiamando Worker.getInputData(). Il seguente codice mostra come creare un'istanza di Worker che richiede dati di input e come inviarli nella richiesta di lavoro.

Kotlin

// Define the Worker requiring input
class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput =
           inputData.getString("IMAGE_URI") ?: return Result.failure()

       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf(
       "IMAGE_URI" to "http://..."
   ))
   .build()

Java

// Define the Worker requiring input
public class UploadWork extends Worker {

   public UploadWork(Context appContext, WorkerParameters workerParams) {
       super(appContext, workerParams);
   }

   @NonNull
   @Override
   public Result doWork() {
       String imageUriInput = getInputData().getString("IMAGE_URI");
       if(imageUriInput == null) {
           return Result.failure();
       }

       uploadFile(imageUriInput);
       return Result.success();
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
      new OneTimeWorkRequest.Builder(UploadWork.class)
           .setInputData(
               new Data.Builder()
                   .putString("IMAGE_URI", "http://...")
                   .build()
           )
           .build();

Allo stesso modo, puoi utilizzare la classe Data per restituire un valore di ritorno.

Passaggi successivi

Nella pagina Stati e osservazione, scoprirai di più sugli stati di lavoro e su come monitorare l'avanzamento del lavoro.