La guida introduttiva spiegava come creare una WorkRequest
semplice e come metterla in coda.
In questa guida imparerai a definire e personalizzare gli oggetti WorkRequest
per gestire casi d'uso comuni, ad esempio:
- Pianifica il lavoro una tantum e ricorrente
- Imposta limiti di lavoro, ad esempio la necessità di utilizzare una rete Wi-Fi o di ricaricare la batteria
- Garantire un ritardo minimo nell'esecuzione del lavoro
- Impostazione di strategie di ripetizione e backoff
- Trasmetti 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 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. Include i vincoli che devono essere soddisfatti per l'esecuzione del tuo lavoro, informazioni di pianificazione come ritardi o intervalli ripetuti, nuovi tentativi di configurazione e può includere dati di input se questi ultimi si basano sul tuo lavoro.
WorkRequest
stesso è una classe base astratta. Esistono due
implementazioni derivate di questa classe che puoi utilizzare per creare la richiesta,
OneTimeWorkRequest
e PeriodicWorkRequest
.
Come implicano il loro nome, OneTimeWorkRequest
è utile per pianificare
lavori non ripetitivi, mentre PeriodicWorkRequest
è più appropriato per
pianificare lavori che si ripetono a intervalli regolari.
Pianificare il 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();
Pianifica tempi di lavoro più rapidi
WorkManager 2.7.0 ha introdotto il concetto di lavoro accelerato. Ciò consente a WorkManager di eseguire lavori importanti e di offrire al sistema un migliore controllo sull'accesso alle risorse.
Il lavoro accelerato si distingue per le seguenti caratteristiche:
- Importanza: il lavoro accelerato è adatto ad attività importanti per l'utente o avviate dall'utente.
- Velocità: il lavoro accelerato è ideale per attività brevi che vengono avviate immediatamente e completate in pochi minuti.
- Quote: una quota a livello di sistema che limita il tempo di esecuzione in primo piano determina se può essere avviato un job accelerato.
- Gestione alimentazione: le limitazioni relative alla gestione dell'alimentazione, ad esempio Risparmio energetico e Sospensione, hanno meno probabilità di influire sulle attività accelerate.
- Latenza: il sistema esegue immediatamente il lavoro accelerato, a condizione che il carico di lavoro attuale del sistema lo consenta. Ciò significa che sono sensibili alla latenza e non possono essere programmati per un'esecuzione successiva.
Un potenziale caso d'uso per velocizzare il lavoro 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 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 fino all'aggiornamento della quota. 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 job accelerato. Per informazioni dettagliate, consulta gli snippet di seguito.
Esecuzione accelerata 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 la chiamiamo
setExpedited()
. Questa richiesta diventa quindi un lavoro più rapido. Se la quota lo consente, inizierà immediatamente a essere eseguita 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 più rapidi, 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 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 svolgono è accelerato o meno. Tuttavia, i lavoratori possono visualizzare una notifica su alcune versioni di Android quando un WorkRequest
è 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.
Coroutine
Se utilizzi un CoroutineWorker
, devi implementare getForegroundInfo()
. Poi, lo passerai a setForeground()
entro doWork()
. In questo modo la notifica viene creata 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 superare setExpedited()
:
OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
, che determina l'esecuzione del job come una normale richiesta di lavoro. Lo snippet riportato sopra dimostra questo.OutOfQuotaPolicy.DROP_WORK_REQUEST
, che determina l'annullamento della richiesta se non è disponibile una quota sufficiente.
App di esempio
Per un esempio completo di come WorkManager 2.7.0 utilizza il lavoro accelerato, consulta WorkManagerSample su GitHub.
Lavoro accelerato differito
Il sistema tenta di eseguire un determinato job accelerato il prima possibile dopo che il job è stato richiamato. 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, che può verificarsi quando sono già in esecuzione troppi job o quando il sistema non ha memoria sufficiente.
- Quota: il limite di quota accelerata dei job è 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.
Programma il lavoro periodico
A volte, la tua app potrebbe richiedere l'esecuzione periodica di determinate operazioni. 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 puoi 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 esecuzione flessibili
Se la natura del tuo lavoro rende sensibile ai tempi di esecuzione, puoi configurare PeriodicWorkRequest
in modo che venga eseguito entro un periodo flessibile all'interno di ciascun periodo di intervallo, come mostrato nella Figura 1.
Figura 1. Il diagramma mostra intervalli ripetuti con il periodo flessibile in cui può essere eseguito il lavoro.
Per definire il lavoro periodico con un periodo flessibile, passi un flexInterval
insieme a
repeatInterval
durante la creazione di PeriodicWorkRequest
. Il periodo flessibile
inizia da repeatInterval - flexInterval
e arriva 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 vincoli al lavoro periodico. 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 viene superato l'intervallo di ripetizione definito, PeriodicWorkRequest
non verrà eseguito fino a quando questa condizione non verrà soddisfatta. Ciò potrebbe
causare un ritardo nell'esecuzione di una determinata esecuzione del tuo lavoro, o persino un suo salto, se
le condizioni non vengono soddisfatte entro l'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.
Tipo di rete | Vincola il tipo di rete richiesto per l'esecuzione del tuo lavoro.
ad esempio Wi-Fi (UNMETERED ).
|
BatteriaNonBassa | Se impostato su true, il lavoro non verrà eseguito se il dispositivo è in modalità batteria scarica. |
Richiede la ricarica | Se impostato su true, il lavoro verrà eseguito soltanto quando il dispositivo è in carica. |
Inattività | Se impostato su true, richiede che il dispositivo dell'utente rimanga inattivo prima dell'esecuzione del lavoro. Questo può essere utile per eseguire operazioni in batch che 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 certo 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 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 lavoro verrà eseguito solo se tutti i vincoli sono soddisfatti.
Nel caso in cui un vincolo non venga soddisfatto mentre il tuo lavoro è in esecuzione, WorkManager interromperà il worker. Verrà eseguito un nuovo tentativo del lavoro una volta soddisfatti tutti i vincoli.
Lavoro in ritardo
Nel caso in cui il lavoro non abbia vincoli o che tutti i vincoli vengano soddisfatti quando il lavoro è accodato, il sistema può scegliere di eseguire il lavoro immediatamente. Se non vuoi che il lavoro venga eseguito immediatamente, puoi specificare che l'avvio del lavoro venga avviato dopo un ritardo iniziale minimo.
Ecco un esempio di come impostare il lavoro in modo che venga eseguito 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();
Anche se l'esempio illustra come impostare un ritardo iniziale per un OneTimeWorkRequest
, puoi anche impostare un ritardo iniziale per un PeriodicWorkRequest
. In questo caso, verrà ritardata solo la prima esecuzione del lavoro periodico.
Criterio di ripetizione e backoff
Se richiedi a WorkManager di riprovare a eseguire il lavoro, puoi restituire Result.retry()
al worker. Il tuo lavoro viene quindi
riprogrammato in base a un ritardo di backoff e a un criterio di backoff.
Ritardo backoff specifica il tempo minimo di attesa prima di riprovare a lavorare dopo il primo tentativo. Questo valore non può essere inferiore a 10 secondi (o MIN_BACKOFF_MILLIS).
Il criterio di backoff definisce il modo in cui il ritardo di backoff deve aumentare nel tempo per i tentativi successivi. WorkManager supporta due criteri di backoff,
LINEAR
eEXPONENTIAL
.
Ogni richiesta di lavoro ha un criterio di backoff e un ritardo nel 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 tra i nuovi tentativi 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,
seguita da 20, 30, 40 e così via, se il lavoro continua a restituire
Result.retry()
dopo tentativi successivi. Se il criterio di backoff è stato impostato su EXPONENTIAL
, la sequenza della durata dei nuovi tentativi sarà più vicina 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 al fine di annullare il lavoro o osservarne lo stato di avanzamento.
Se hai un gruppo di lavori logicamente correlati, può essere utile anche taggare gli elementi di lavoro. Il tagging consente di lavorare con un gruppo di richieste di lavoro insieme.
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. Internamente, questi tag vengono archiviati come un insieme di stringhe. Per ottenere l'insieme di tag associati all'WorkRequest
puoi utilizzare WorkInfo.getTag().
Dalla tua classe Worker
, puoi recuperare il relativo set di tag tramite
ListenableWorker.getTag().
Assegna dati di input
Per svolgere il proprio lavoro potrebbero essere necessari dati di input. Ad esempio, il lavoro che gestisce il caricamento di un'immagine potrebbe richiedere che l'URI dell'immagine venga caricato come input.
I valori di input vengono memorizzati come coppie chiave-valore in un oggetto Data
e possono essere impostati sulla richiesta di lavoro. WorkManager fornirà l'input Data
al tuo lavoro quando lo esegue. 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();
Analogamente, la classe Data
può essere utilizzata per generare un valore restituito. I dati di input e
output sono trattati più dettagliatamente nella sezione Parametri di input e valori restituiti.
Passaggi successivi
Nella pagina Stati e osservazione, scoprirai di più sugli stati del lavoro e su come monitorarne l'avanzamento.