Dopo aver definito il
Worker
e
il WorkRequest
,
l'ultimo passaggio è mettere in coda il lavoro. Il modo più semplice per mettere in coda il lavoro
è chiamare il metodo enqueue()
di WorkManager, passando l'WorkRequest
che
vuoi eseguire.
Kotlin
val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)
Java
WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);
Presta attenzione quando metti in coda il lavoro per evitare duplicati. Ad esempio, un'app potrebbe tentare di caricare i propri log in un servizio di backend ogni 24 ore. Se non fai attenzione, potresti accodare la stessa attività più volte, anche se il job deve essere eseguito una sola volta. Per raggiungere questo obiettivo, puoi pianificare il lavoro come lavoro unico.
Unique Work
Il lavoro unico è un concetto potente che garantisce che tu abbia una sola istanza di lavoro con un determinato nome alla volta. A differenza degli ID, i nomi univoci sono leggibili e specificati dallo sviluppatore anziché essere generati automaticamente da WorkManager. A differenza dei tag, i nomi univoci sono associati a una sola istanza di lavoro.
Il lavoro unico può essere applicato sia al lavoro una tantum sia a quello periodico. Puoi creare una sequenza di lavoro unica chiamando uno di questi metodi, a seconda che tu stia pianificando un lavoro ripetuto o una tantum.
WorkManager.enqueueUniqueWork()
per un lavoro una tantumWorkManager.enqueueUniquePeriodicWork()
per lavori periodici
Entrambi questi metodi accettano tre argomenti:
- uniqueWorkName: un
String
utilizzato per identificare in modo univoco la richiesta di lavoro. - existingWorkPolicy: un
enum
che indica a WorkManager cosa fare se esiste già una catena di lavoro non completata con quel nome univoco. Per saperne di più, consulta le norme sulla risoluzione dei conflitti. - work: il
WorkRequest
da programmare.
Utilizzando un lavoro univoco, possiamo risolvere il problema di pianificazione duplicata menzionato in precedenza.
Kotlin
val sendLogsWorkRequest =
PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
.setConstraints(Constraints.Builder()
.setRequiresCharging(true)
.build()
)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"sendLogs",
ExistingPeriodicWorkPolicy.KEEP,
sendLogsWorkRequest
)
Java
PeriodicWorkRequest sendLogsWorkRequest = new
PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
.setConstraints(new Constraints.Builder()
.setRequiresCharging(true)
.build()
)
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"sendLogs",
ExistingPeriodicWorkPolicy.KEEP,
sendLogsWorkRequest);
Ora, se il codice viene eseguito mentre un job sendLogs è già in coda, il job esistente viene mantenuto e non viene aggiunto alcun nuovo job.
Sequenze di lavoro uniche possono essere utili anche se devi creare gradualmente una lunga catena di attività. Ad esempio, un'app di fotoritocco potrebbe consentire agli utenti di annullare una lunga catena di azioni. Ognuna di queste operazioni di annullamento potrebbe richiedere un po' di tempo, ma devono essere eseguite nell'ordine corretto. In questo caso, l'app potrebbe creare una catena di "annullamento" e aggiungere ogni operazione di annullamento alla catena in base alle esigenze. Per ulteriori dettagli, consulta la sezione Concatenamento del lavoro.
Norme per la risoluzione dei conflitti
Quando pianifichi un'attività unica, devi indicare a WorkManager quale azione intraprendere in caso di conflitto. Per farlo, devi passare un enum quando metti in coda il lavoro.
Per il lavoro una tantum, fornisci un
ExistingWorkPolicy
, che
supporta quattro opzioni per la gestione del conflitto.
REPLACE
esistenti con il nuovo lavoro. Questa opzione annulla il lavoro esistente.KEEP
esistente e ignora il nuovo lavoro.APPEND
il nuovo lavoro alla fine del lavoro esistente. Questo criterio farà in modo che il nuovo lavoro venga concatenato al lavoro esistente, eseguito al termine di quest'ultimo.
Il lavoro esistente diventa un prerequisito per il nuovo lavoro. Se l'opera esistente
diventa CANCELLED
o FAILED
, anche la nuova opera diventa CANCELLED
o FAILED
.
Se vuoi che il nuovo lavoro venga eseguito indipendentemente dallo stato del lavoro esistente,
utilizza APPEND_OR_REPLACE
.
APPEND_OR_REPLACE
funziona in modo simile aAPPEND
, tranne per il fatto che non dipende dallo stato di avanzamento del prerequisito. Se il lavoro esistente èCANCELLED
oFAILED
, il nuovo lavoro viene comunque eseguito.
Per il lavoro a periodo, fornisci un
ExistingPeriodicWorkPolicy
,
che supporta due opzioni, REPLACE
e KEEP
. Queste opzioni funzionano allo stesso modo
delle controparti ExistingWorkPolicy.
Osservare il tuo lavoro
In qualsiasi momento dopo aver messo in coda un lavoro, puoi controllarne lo stato eseguendo una query
WorkManager in base al relativo name
, id
o a un tag
.
Kotlin
// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
Java
// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>
La query restituisce un
ListenableFuture
di un oggetto WorkInfo
, che include
id
dell'opera, i relativi tag, il relativo
State
attuale e qualsiasi set di dati di output che utilizza
Result.success(outputData)
.
Le varianti LiveData
e Flow
di ciascun metodo consentono di osservare le modifiche apportate a
WorkInfo
registrando un listener. Ad esempio, se vuoi mostrare
un messaggio all'utente quando un'attività viene completata correttamente, puoi configurarlo
come segue:
Kotlin
workManager.getWorkInfoByIdFlow(syncWorker.id)
.collect{ workInfo ->
if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
Snackbar.make(requireView(),
R.string.work_completed, Snackbar.LENGTH_SHORT)
.show()
}
}
Java
workManager.getWorkInfoByIdLiveData(syncWorker.id)
.observe(getViewLifecycleOwner(), workInfo -> {
if (workInfo.getState() != null &&
workInfo.getState() == WorkInfo.State.SUCCEEDED) {
Snackbar.make(requireView(),
R.string.work_completed, Snackbar.LENGTH_SHORT)
.show();
}
});
Query di lavoro complesse
WorkManager 2.4.0 e versioni successive supportano query complesse per i job in coda utilizzando oggetti
WorkQuery
. WorkQuery supporta
l'interrogazione del lavoro in base a una combinazione di tag, stato e nome univoco del lavoro.
L'esempio seguente mostra come trovare tutto il lavoro con il tag "syncTag",
che si trova nello stato FAILED
o CANCELLED
e ha un nome di lavoro univoco
"preProcess" o "sync".
Kotlin
val workQuery = WorkQuery.Builder
.fromTags(listOf("syncTag"))
.addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
.addUniqueWorkNames(listOf("preProcess", "sync")
)
.build()
val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)
Java
WorkQuery workQuery = WorkQuery.Builder
.fromTags(Arrays.asList("syncTag"))
.addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
.addUniqueWorkNames(Arrays.asList("preProcess", "sync")
)
.build();
ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);
Ogni componente (tag, stato o nome) in un WorkQuery
è AND
-ato con gli
altri. Ogni valore di un componente è OR
. Ad esempio: (name1 OR name2
OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)
.
WorkQuery
funziona anche con l'equivalente LiveData,
getWorkInfosLiveData()
,
e con l'equivalente Flow, getWorkInfosFlow()
.
Annullamento e interruzione del lavoro
Se non hai più bisogno che il lavoro precedentemente messo in coda venga eseguito, puoi chiedere che venga annullato. Il lavoro può essere annullato dal suo name
, id
o da un tag
associato.
Kotlin
// by id
workManager.cancelWorkById(syncWorker.id)
// by name
workManager.cancelUniqueWork("sync")
// by tag
workManager.cancelAllWorkByTag("syncTag")
Java
// by id
workManager.cancelWorkById(syncWorker.id);
// by name
workManager.cancelUniqueWork("sync");
// by tag
workManager.cancelAllWorkByTag("syncTag");
WorkManager controlla l'State
del lavoro. Se il lavoro è
già terminato,
non succede nulla. In caso contrario, lo stato del lavoro viene modificato in
CANCELLED
e il lavoro
non verrà eseguito in futuro. Tutti i job
WorkRequest
che dipendono
da questo lavoro verranno
CANCELLED
.
RUNNING
lavoro
riceve una chiamata a
ListenableWorker.onStopped()
.
Esegui l'override di questo metodo per gestire qualsiasi potenziale pulizia. Per saperne di più, consulta Interrompere un
worker in esecuzione.
Arrestare un worker in esecuzione
Esistono diversi motivi per cui l'esecuzione di Worker
potrebbe essere interrotta da
WorkManager:
- Hai chiesto esplicitamente l'annullamento (ad esempio chiamando
il numero
WorkManager.cancelWorkById(UUID)
). - Nel caso di lavoro unico,
hai messo in coda in modo esplicito un nuovo
WorkRequest
con unExistingWorkPolicy
diREPLACE
. Il vecchioWorkRequest
viene immediatamente considerato annullato. - I vincoli del tuo lavoro non sono più soddisfatti.
- Il sistema ha chiesto alla tua app di interrompere il lavoro per qualche motivo. Ciò può accadere se superi il termine di esecuzione di 10 minuti. Il lavoro è pianificato per essere ritentato in un secondo momento.
In queste condizioni, il worker viene arrestato.
Devi interrompere in modo cooperativo qualsiasi lavoro in corso e rilasciare le risorse che il tuo worker sta utilizzando. Ad esempio, a questo punto devi chiudere gli handle aperti per database e file. Hai a disposizione due meccanismi per capire quando il worker si sta arrestando.
Callback onStopped()
WorkManager richiama
ListenableWorker.onStopped()
non appena il worker è stato arrestato. Esegui l'override di questo metodo per chiudere
le risorse che potresti conservare.
Proprietà isStopped()
Puoi chiamare il metodo
ListenableWorker.isStopped()
per verificare se il worker è già
stato interrotto. Se esegui operazioni ripetitive o di lunga durata nel tuo
worker, devi controllare spesso questa proprietà e utilizzarla come segnale per
interrompere il lavoro il prima possibile.
Nota:WorkManager ignora il valore
Result
impostato da un worker
che ha ricevuto il segnale onStop, perché il worker è già considerato
arrestato.
Osserva lo stato del motivo dell'interruzione
Per eseguire il debug del motivo per cui è stato interrotto un Worker
, puoi registrare il motivo dell'interruzione chiamando
WorkInfo.getStopReason()
:
Kotlin
workManager.getWorkInfoByIdFlow(syncWorker.id)
.collect { workInfo ->
if (workInfo != null) {
val stopReason = workInfo.stopReason
logStopReason(syncWorker.id, stopReason)
}
}
Java
workManager.getWorkInfoByIdLiveData(syncWorker.id)
.observe(getViewLifecycleOwner(), workInfo -> {
if (workInfo != null) {
int stopReason = workInfo.getStopReason();
logStopReason(syncWorker.id, workInfo.getStopReason());
}
});