Definisci le richieste di lavoro

La guida introduttiva spiega come creare un semplice WorkRequest e inserirlo in coda.

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

  • Pianificare attività una tantum e ricorrenti
  • Impostare vincoli di lavoro, ad esempio la necessità di Wi-Fi o ricarica
  • Garantire un ritardo minimo nell'esecuzione del lavoro
  • Impostare le strategie di ripetizione e di ritiro
  • Passare i dati di input al lavoro
  • Raggruppare il lavoro correlato utilizzando i tag

Panoramica

Il lavoro viene definito in WorkManager tramite un WorkRequest. Per pianificare qualsiasi lavoro con WorkManager, devi prima creare un oggetto WorkRequest e poi accodarlo.

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. Sono inclusi i vincoli che devono essere soddisfatti per l'esecuzione del lavoro, le informazioni sulla pianificazione come ritardi o intervalli di ripetizione, la configurazione del nuovo tentativo e possono essere inclusi i dati di input se il lavoro si basa su questi.

WorkRequest è una classe base astratta. Esistono due implementazioni derivate di questo tipo che puoi utilizzare per creare la richiesta: OneTimeWorkRequest e PeriodicWorkRequest. Come suggeriscono i nomi, OneTimeWorkRequest è utile per pianificare il lavoro non ripetitivo, mentre PeriodicWorkRequest è più appropriato per pianificare il lavoro che si ripete a un determinato intervallo.

Pianificare un lavoro una tantum

Per operazioni semplici, 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 operazioni più complesse, puoi utilizzare uno strumento di creazione:

Kotlin

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

Java

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

Pianificare il lavoro rapido

WorkManager 2.7.0 ha introdotto il concetto di lavoro rapido. Ciò consente a WorkManager di eseguire lavori importanti e di offrire al sistema un migliore controllo sull'accesso alle risorse.

Il lavoro rapido è caratterizzato dalle seguenti caratteristiche:

  • Importanza: il lavoro rapido è adatto alle attività importanti per l'utente o avviate dall'utente.
  • Velocità: il lavoro rapido è ideale per attività brevi che iniziano immediatamente e si completano in pochi minuti.
  • Quote: una quota a livello di sistema che limita il tempo di esecuzione in primo piano determina se è possibile avviare un job rapido.
  • Gestione dell'alimentazione: le limitazioni della gestione dell'alimentazione, come il Risparmio energetico e la modalità Sospensione, hanno meno probabilità di influire sul lavoro accelerato.
  • Latenza: il sistema esegue immediatamente i lavori accelerati, a condizione che il carico di lavoro corrente del sistema lo consenta. Ciò significa che sono sensibili alla latenza e non possono essere pianificati per l'esecuzione in un secondo momento.

Un potenziale caso d'uso per il lavoro accelerato potrebbe essere all'interno di un'app di chat quando l'utente vuole inviare un messaggio o un'immagine allegata. Analogamente, un'app che gestisce un flusso di pagamento o abbonamento potrebbe voler utilizzare la modalità di lavoro accelerato. Questo accade 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 job accelerato prima che possa essere eseguito. Il tempo di esecuzione non è illimitato. Ogni app riceve invece una quota di tempo di esecuzione. Quando l'app utilizza il tempo di esecuzione e raggiunge la quota allocata, non puoi più eseguire il lavoro accelerato finché la quota non viene aggiornata. In questo modo, Android può bilanciare più efficacemente le risorse tra le applicazioni.

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

Puoi determinare cosa accade quando la quota di esecuzione non consente l'esecuzione immediata di un job accelerato. Per maggiori dettagli, consulta gli snippet riportati di seguito.

Esecuzione accelerata del lavoro

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

Kotlin

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

WorkManager.getInstance(context)
    .enqueue(request)

Java

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

In questo esempio, inizializziamo un'istanza di OneTimeWorkRequest e chiamiamo setExpedited(). Questa richiesta diventa quindi un lavoro più rapido. 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 normalmente e non accelerata.

Compatibilità con le versioni precedenti e servizi in primo piano

Per mantenere la compatibilità con le versioni precedenti per i job accelerati, 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 tuo 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 job accelerato.

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

Lavoratore

I lavoratori non sanno se il lavoro che stanno svolgendo è accelerato o meno. Tuttavia, su alcune versioni di Android gli operatori possono mostrare una notifica quando unWorkRequest è stato accelerato.

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

CoroutineWorker

Se utilizzi un CoroutineWorker, devi implementare getForegroundInfo(). Poi lo giri a setForeground() entro doWork(). In questo modo verrà creata la notifica nelle versioni di Android precedenti alla 12.

Considera il seguente esempio:

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

}

Criteri per le quote

Puoi controllare cosa succede al lavoro accelerato quando la tua 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 riportato sopra lo dimostra.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, che determina l'annullamento della richiesta se la quota non è sufficiente.

App di esempio

Per vedere un esempio completo di come WorkManager 2.7.0 utilizza il lavoro rapido, consulta WorkManagerSample su GitHub.

Lavoro rapido differito

Il sistema tenta di eseguire un determinato job accelerato il prima possibile dopo che il job è stato richiamato. Tuttavia, come per altri tipi di job, il sistema potrebbe posticipare l'inizio di nuovi lavori accelerati, ad esempio nei seguenti casi:

  • Caricamento: il carico del sistema è troppo elevato, che può verificarsi quando sono già in esecuzione troppi job o quando il sistema non ha memoria sufficiente.
  • Quota: il limite di quota per i job con priorità è stato superato. Il lavoro accelerato utilizza un sistema di quote basato sui bucket in standby dell'app e limita il tempo massimo di esecuzione all'interno di un intervallo di tempo di rotazione. Le quote utilizzate per il lavoro accelerato sono più restrittive di quelle utilizzate per altri tipi di job in background.

Pianificare i lavori periodici

A volte la tua app potrebbe richiedere l'esecuzione periodica di determinati lavori. Ad esempio, potresti voler eseguire periodicamente il backup dei tuoi dati, scaricare nuovi contenuti nella tua app o caricare i log su un server.

Ecco come utilizzare PeriodicWorkRequest per creare un oggetto WorkRequest 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 è pianificato con un intervallo di un'ora.

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

Intervalli di corsa flessibili

Se la natura del tuo lavoro lo rende sensibile ai tempi di esecuzione, puoi configurare il tuo PeriodicWorkRequest in modo che venga eseguito in un periodo di tempo flessibile 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 determinato momento durante l&#39;intervallo di flessibilità di ogni ciclo.

Figura 1. Il diagramma mostra gli intervalli ripetuti con il periodo flessibile in cui può essere eseguito il lavoro.

Per definire un lavoro periodico con un periodo flessibile, devi passare un flexInterval insieme al repeatInterval durante la creazione del PeriodicWorkRequest. Il periodo flessibile inizia alle ore repeatInterval - flexInterval e termina alla fine dell'intervallo.

Di seguito è riportato un esempio di lavoro periodico che può essere eseguito negli 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 flessibile deve essere maggiore o uguale a PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS.

Effetto dei vincoli sul lavoro periodico

Puoi applicare limitazioni 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 viene superato l'intervallo di ripetizione definito, PeriodicWorkRequest non verrà eseguito finché questa condizione non sarà soddisfatta. Ciò potrebbe causare il ritardo o addirittura il salto di una determinata esecuzione del tuo lavoro se le condizioni non vengono soddisfatte nell'intervallo di esecuzione.

Vincoli di lavoro

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

NetworkType Limita il tipo di rete necessario per l'esecuzione del tuo lavoro. Ad esempio, Wi-Fi (UNMETERED).
BatteryNotLow Se impostato su true, il lavoro non verrà eseguito se il dispositivo è in modalità batteria scarica.
RequiresCharging Se impostato su true, il lavoro verrà eseguito solo quando il dispositivo è in carica.
Inattività Se impostato su true, richiede che il dispositivo dell'utente rimanga inattivo prima dell'esecuzione del lavoro. Questa operazione può essere utile per eseguire operazioni collettive che altrimenti potrebbero avere un impatto negativo sulle prestazioni di altre app in esecuzione attiva sul dispositivo dell'utente.
ArchiviazioneNonBassa Se il criterio viene 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'istanza Constraints utilizzando Contraints.Builder() e assegnala al 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 alla rete 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 tuo lavoro verrà eseguito solo se vengono soddisfatti tutti i vincoli.

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

Lavoro in ritardo

Se il tuo lavoro non ha vincoli o se tutti i vincoli sono soddisfatti quando il lavoro viene inserito in coda, il sistema potrebbe scegliere di eseguirlo immediatamente. Se non vuoi che il lavoro venga eseguito immediatamente, puoi specificare che deve iniziare dopo un ritardo iniziale minimo.

Ecco un esempio di come impostare l'esecuzione del lavoro almeno 10 minuti dopo che è stato inserito 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 OneTimeWorkRequest, puoi anche impostare un ritardo iniziale per un PeriodicWorkRequest. In questo caso, solo la prima esecuzione del lavoro periodico subirebbe un ritardo.

Criterio di ripetizione e backoff

Se richiedi a WorkManager di riprovare a eseguire il lavoro, puoi restituire Result.retry() al worker. Il lavoro viene quindi riprogrammato in base a un ritardo di backoff e a norme di backoff.

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

  • Il criterio di backoff definisce in che modo il ritardo del backoff deve aumentare nel tempo per i tentativi di ripetizione successivi. WorkManager supporta due criteri di backoff, LINEAR e EXPONENTIAL.

Ogni richiesta di lavoro ha un criterio di backoff e un ritardo di backoff. Il criterio predefinito è EXPONENTIAL con un ritardo di 30 secondi, ma puoi sostituirlo nella configurazione della richiesta di lavoro.

Ecco un esempio di personalizzazione del ritardo e del criterio di backoff.

Kotlin

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

Java

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

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

Tagga lavoro

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

Se hai un gruppo di lavori logicamente correlati, può essere utile anche taggare gli elementi di lavoro. Il tagging ti consente di gestire insieme un gruppo di richieste di lavoro.

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

Il seguente codice mostra come aggiungere un tag "pulizia" al tuo 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. Questi tag vengono memorizzati internamente come insieme di stringhe. Per ottenere l'insieme di tag associati al WorkRequest, puoi utilizzare WorkInfo.getTags().

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

Assegna i dati di input

Il tuo lavoro potrebbe richiedere l'inserimento di dati per funzionare. Ad esempio, il 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 oggetto Data e possono essere impostati nella richiesta di lavoro. WorkManager invierà l'input Data al tuo lavoro quando lo eseguirà. La classe Worker può accedere ai parametri di input chiamando Worker.getInputData(). Il codice riportato di seguito mostra come creare un'istanza Worker che richiede dati di input e come inviarla 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();

Analogamente, la classe Data può essere utilizzata per produrre un valore restituito. I dati di input e di output sono trattati in modo più dettagliato nella sezione Parametri di input e valori restituiti.

Passaggi successivi

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