Definisci le richieste di lavoro

La Guida introduttiva spiega come creare una WorkRequest semplice e metterla in coda.

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

  • Pianificare lavori una tantum e ricorrenti
  • Stabilisci vincoli di lavoro come la necessità di Wi-Fi o ricarica
  • Garantire un ritardo minimo nell'esecuzione dei lavori
  • Imposta strategie per nuovi tentativi e backoff
  • Passa i dati di input al lavoro
  • Raggruppa le attività correlate 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 quindi 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. Include i vincoli che devono essere soddisfatti per l'esecuzione del tuo lavoro, informazioni di programmazione come ritardi o intervalli ripetuti, configurazione dei nuovi tentativi e può includere dati di input se il tuo lavoro si basa su questi dati.

WorkRequest stesso è una classe base astratta. Esistono due implementazioni derivate di questa classe che puoi utilizzare per creare la richiesta: OneTimeWorkRequest e PeriodicWorkRequest. Come suggerisce il nome, OneTimeWorkRequest è utile per pianificare lavori non ripetitivi, mentre PeriodicWorkRequest è più appropriato per pianificare lavori che si ripetono a intervalli specifici.

Pianifica un lavoro una tantum

Per un lavoro semplice, che non richiede 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 il lavoro più rapido

WorkManager 2.7.0 ha introdotto il concetto di lavoro accelerato, Ciò consente a WorkManager di eseguire lavori importanti e al contempo al sistema di controllare meglio l'accesso alle risorse.

Il lavoro accelerato si distingue per le seguenti caratteristiche:

  • Importanza: il lavoro accelerato si adatta alle attività importanti per l'utente o avviate dall'utente.
  • Velocità: il lavoro accelerato è consigliato per attività brevi, che vengono avviate 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 job accelerato può essere avviato.
  • Gestione dell'alimentazione: le limitazioni relative alla gestione dell'alimentazione, ad esempio Risparmio energetico e Sospensione, hanno meno probabilità di influire sui tempi di lavoro.
  • Latenza: il sistema esegue immediatamente un lavoro accelerato, a condizione che il carico di lavoro attuale del sistema lo consenta. Ciò significa che sono sensibili alla latenza e non è possibile pianificarne l'esecuzione in un secondo momento.

Un caso d'uso potenziale per attività più rapide potrebbe essere l'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 di abbonamento potrebbe voler utilizzare la modalità di lavoro accelerata. 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 job accelerato prima che possa essere eseguito. Il tempo di esecuzione non è illimitato. Ogni app riceve invece una quota di tempo di esecuzione. Quando la tua app utilizza il tempo di esecuzione e raggiunge la quota allocata, non puoi più eseguire operazioni rapide fino all'aggiornamento della quota. Ciò consente ad Android di bilanciare in modo più efficace le risorse tra le applicazioni.

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

Puoi determinare cosa succede quando la quota di esecuzione non consente l'esecuzione immediata di un job accelerato. Per i dettagli, vedi gli snippet di seguito.

Esecuzione più rapida del lavoro

A partire da WorkManager 2.7, la tua app può chiamare setExpedited() per dichiarare che un 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 accelerato. Se la quota lo consente, l'esecuzione inizierà immediatamente in background. Se la quota è stata utilizzata, il parametro OutOfQuotaPolicy indica che la richiesta deve essere eseguita normalmente, non accelerata.

Compatibilità con le versioni precedenti e servizi in primo piano

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

I metodi getForegroundInfoAsync() e getForegroundInfo() in Worker consentono a WorkManager di visualizzare una notifica quando chiami setExpedited() prima di Android 12.

Qualsiasi elemento ListenableWorker deve implementare il metodo getForegroundInfo se vuoi richiedere l'esecuzione dell'attività come job accelerato.

Quando 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 facendo è accelerato o meno. Tuttavia, i lavoratori possono mostrare una notifica su alcune versioni di Android quando un codice WorkRequest è stato accelerato.

Per attivare questa funzionalità, WorkManager fornisce il metodo getForegroundInfoAsync(), che devi implementare affinché WorkManager possa visualizzare una notifica per avviare una ForegroundService per te, se necessario.

Lavoratore Coroutine

Se utilizzi un CoroutineWorker, devi implementare getForegroundInfo(). Successivamente, lo passi a setForeground() entro doWork(). In questo modo verrà creata la notifica 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()
    }

}

Criteri per le quote

Puoi controllare cosa succede al lavoro accelerato quando la tua app raggiunge la quota di esecuzione. Per continuare, puoi superare setExpedited():

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, per cui il job viene eseguito come una normale richiesta di lavoro. Lo snippet riportato sopra lo dimostra.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, quindi la richiesta viene annullata se non è disponibile una quota sufficiente.

App di esempio

Per un esempio completo di come WorkManager 2.7.0 utilizza il lavoro accelerato, esamina WorkManagerSample su GitHub.

Lavoro accelerato differito

Il sistema tenta di eseguire un determinato job accelerato il prima possibile dopo la chiamata del job. Tuttavia, come nel caso di altri tipi di job, il sistema potrebbe posticipare l'inizio di un nuovo lavoro accelerato, come nei seguenti casi:

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

Pianificare lavori periodici

A volte la tua app potrebbe richiedere l'esecuzione periodica di determinate attività. Ad esempio, potresti voler eseguire periodicamente il backup dei 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 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 il worker verrà eseguito dipende dai vincoli utilizzati nell'oggetto WorkRequest e dalle ottimizzazioni eseguite dal sistema.

Intervalli di esecuzione flessibili

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

Puoi impostare un intervallo flessibile per un job periodico. Devi definire 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 flessibile in ogni ciclo.

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

Per definire il lavoro periodico con un periodo flessibile, passi un flexInterval insieme a repeatInterval durante la creazione di PeriodicWorkRequest. Il periodo flessibile inizia alle ore repeatInterval - flexInterval e va 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 flessibile deve essere maggiore o uguale a PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS.

Effetto dei vincoli sul lavoro periodico

Puoi applicare i vincoli ai lavori periodici. Ad esempio, potresti 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 viene superato, PeriodicWorkRequest non verrà eseguito finché questa condizione non viene soddisfatta. Ciò potrebbe causare un ritardo nell'esecuzione di una determinata esecuzione del tuo lavoro, o addirittura un'eccezione 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. Per WorkManager sono disponibili i seguenti vincoli.

Tipo di rete Vincola il tipo di rete necessario per l'esecuzione del tuo lavoro. Ad esempio, Wi-Fi (UNMETERED).
Batteria non bassa Se viene impostato su true, il lavoro non verrà eseguito se il dispositivo è in modalità di batteria in esaurimento.
Richiede la ricarica Se impostato su true, il lavoro verrà eseguito solo quando il dispositivo è in carica.
Dispositivo inattivo Se impostato su true, il dispositivo dell'utente deve essere inattivo per l'esecuzione dell'operazione. Questo può essere utile per eseguire operazioni in batch che altrimenti potrebbero avere un impatto negativo sulle prestazioni di altre app in esecuzione attiva sul dispositivo dell'utente.
Spazio di archiviazione non basso Se viene impostato su true, il lavoro non viene 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 a 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();

Se vengono specificati più vincoli, l'operazione verrà eseguita solo quando tutti i vincoli vengono soddisfatti.

Nel caso in cui un vincolo non venga soddisfatto mentre il lavoro è in esecuzione, WorkManager arresterà il worker. Il lavoro verrà quindi riprovato una volta soddisfatti tutti i vincoli.

In ritardo

Nel caso in cui il tuo lavoro non abbia vincoli o che tutti i vincoli vengano soddisfatti quando il tuo lavoro è accodato, il sistema può scegliere di eseguirlo immediatamente. Se non vuoi che il lavoro venga eseguito immediatamente, puoi specificare che inizi dopo un ritardo iniziale minimo.

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

Kotlin


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

Java


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

L'esempio illustra come impostare un ritardo iniziale per un elemento OneTimeWorkRequest, ma puoi anche impostare un ritardo iniziale per un elemento PeriodicWorkRequest. In questo caso, verrà ritardata solo la prima esecuzione del lavoro periodico.

Criterio di nuovo tentativo e backoff

Se richiedi a WorkManager di riprovare, puoi restituire Result.retry() dal tuo worker. Il tuo lavoro viene quindi riprogrammato in base a un ritardo di backoff e a un criterio di backoff.

  • Ritardo di backoff specifica il tempo minimo di attesa prima di riprovare a eseguire 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 di backoff deve aumentare nel tempo per i tentativi di nuovo tentativo 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 della norma 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 minimo di backoff è impostato sul valore minimo consentito, ovvero 10 secondi. Poiché il criterio è LINEAR, l'intervallo tra i tentativi aumenterà di circa 10 secondi a ogni nuovo tentativo. Ad esempio, verrà eseguito un nuovo tentativo per terminare la prima esecuzione con Result.retry() dopo 10 secondi, seguiti da 20, 30, 40 e così via, se il lavoro continua a essere restituito Result.retry() dopo i tentativi successivi. Se il criterio di backoff viene impostato su EXPONENTIAL, la sequenza di durata dei tentativi sarà più vicina a 20, 40, 80 e così via.

Tagga il lavoro

Ogni richiesta di lavoro ha un identificatore univoco, che può essere utilizzato per identificare che funzionano in seguito al fine di annullare il lavoro o osservarne l'avanzamento.

Se hai un gruppo di lavori correlati logicamente, potrebbe essere utile anche taggare questi elementi. 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 di "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 sono archiviati internamente come un insieme di stringhe. Per ottenere l'insieme di tag associati a WorkRequest puoi utilizzare WorkInfo.getTag().

Dalla classe Worker, puoi recuperare il relativo insieme di tag tramite ListenableWorker.getTag().

Assegna dati di input

Il tuo lavoro potrebbe richiedere dati di input per funzionare. Ad esempio, le attività che gestiscono il caricamento di un'immagine potrebbero richiedere il caricamento dell'URI dell'immagine come input.

I valori di input sono 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 agli argomenti di input chiamando Worker.getInputData(). Il codice riportato di seguito mostra come creare un'istanza 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, la classe Data può essere utilizzata per restituire un valore restituito. I dati di input e di output sono trattati in maggiore dettaglio nella sezione Parametri di input e valori restituiti.

Passaggi successivi

Nella pagina Stati e osservazione, troverai ulteriori informazioni sugli stati di lavoro e su come monitorare l'avanzamento del lavoro.