Gestione del lavoro

Dopo aver definito Worker e WorkRequest, l'ultimo passaggio consiste nell'accodare il lavoro. Il modo più semplice per accodare il lavoro è chiamare il metodo enqueue() di WorkManager, passando il 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 accodi il lavoro per evitare duplicati. Ad esempio, un'app potrebbe provare a caricare i log in un servizio di backend ogni 24 ore. Se non presti attenzione, potresti finire per accodare la stessa attività molte volte, anche se il job deve essere eseguito una sola volta. Per raggiungere questo obiettivo, puoi pianificare il lavoro come lavoro unico.

Lavoro unico

Il lavoro unico è un concetto potente che garantisce che sia presente 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 unici sono associati a una sola istanza di lavoro.

Il lavoro unico può essere applicato sia al lavoro una tantum sia al lavoro periodico. Puoi creare una sequenza di lavoro unica chiamando uno di questi metodi, a seconda che tu stia pianificando un lavoro ripetuto o un lavoro una tantum.

Entrambi questi metodi accettano 3 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 terminata con quel nome univoco. Per saperne di più, consulta le norme per la risoluzione dei conflitti.
  • work - il WorkRequest da pianificare.

Utilizzando il lavoro unico, possiamo risolvere il problema di pianificazione duplicata notato 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 un nuovo job.

Le 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 deve essere eseguita 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 maggiori dettagli, consulta la sezione Concatenare il lavoro.

Norme per la risoluzione dei conflitti

Quando pianifichi un lavoro unico, devi indicare a WorkManager l'azione da intraprendere in caso di conflitto. A questo scopo, passa un enum quando accodi il lavoro.

Per il lavoro una tantum, fornisci un ExistingWorkPolicy, che supporta 4 opzioni per la gestione del conflitto.

  • REPLACE : sostituisce il lavoro esistente con il nuovo lavoro. Questa opzione annulla il lavoro esistente.
  • KEEP : mantiene il lavoro esistente e ignora il nuovo lavoro.
  • APPEND : aggiunge il nuovo lavoro alla fine del lavoro esistente. Questa norma farà sì che il nuovo lavoro venga concatenato al lavoro esistente, eseguito dopo il completamento del lavoro esistente.

Il lavoro esistente diventa un prerequisito per il nuovo lavoro. Se il lavoro esistente diventa CANCELLED o FAILED, anche il nuovo lavoro 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 a APPEND, ma non dipende dallo stato del lavoro prerequisito. Se il lavoro esistente è CANCELLED o FAILED, il nuovo lavoro viene comunque eseguito.

Per il lavoro periodico, fornisci un ExistingPeriodicWorkPolicy, che supporta 2 opzioni: REPLACE e KEEP. Queste opzioni funzionano come le rispettive controparti di ExistingWorkPolicy.

Osservare il lavoro

In qualsiasi momento dopo l'accodamento del lavoro, puoi verificarne lo stato eseguendo una query su WorkManager in base al name, all'id o a un tag associato.

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 WorkInfo oggetto, che include l' id del lavoro, i relativi tag, il relativo current State, e qualsiasi set di dati di output utilizzando Result.success(outputData).

Le varianti LiveData e Flow di ciascuno dei metodi consentono di osservare le modifiche apportate a WorkInfo registrando un listener. Ad esempio, se vuoi visualizzare un messaggio per l'utente quando un lavoro viene completato correttamente, puoi configurarlo nel seguente modo:

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 accodati utilizzando WorkQuery oggetti. WorkQuery supporta l'esecuzione di query per il lavoro in base a una combinazione di tag, stato e nome del lavoro univoco.

L'esempio seguente mostra come trovare tutto il lavoro con il tag, "syncTag", nello stato FAILED o CANCELLED e con un nome di lavoro univoco either "preProcess" or "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 viene combinato con gli altri tramite l'operatore AND. Ogni valore in un componente viene combinato tramite l'operatore 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 l'equivalente Flow, getWorkInfosFlow().

Annullare e interrompere il lavoro

Se non hai più bisogno che il lavoro accodato in precedenza venga eseguito, puoi chiedere che venga annullato. Il lavoro può essere annullato in base al name, all'id o a 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");

Dietro le quinte, WorkManager controlla il 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. Anche tutti i job WorkRequest che sono dipendenti da questo lavoro verranno anche 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 la sezione Interrompere un worker in esecuzione.

Interrompere un worker in esecuzione

Esistono alcuni motivi per cui il Worker in esecuzione potrebbe essere interrotto da WorkManager:

  • Hai chiesto esplicitamente che venga annullato (ad esempio chiamando WorkManager.cancelWorkById(UUID)).
  • Nel caso di lavoro unico, hai accodato esplicitamente un nuovo WorkRequest con un ExistingWorkPolicy di REPLACE. Il vecchio WorkRequest 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. Questo può accadere se superi il termine di esecuzione di 10 minuti. Il lavoro è pianificato per un nuovo tentativo in un secondo momento.

In queste condizioni, il worker viene interrotto.

Devi interrompere in modo cooperativo qualsiasi lavoro in corso e rilasciare tutte le risorse a cui il worker è collegato. Ad esempio, a questo punto devi chiudere gli handle aperti a database e file. Sono disponibili due meccanismi per capire quando il worker si sta arrestando.

Callback onStopped()

WorkManager invokes ListenableWorker.onStopped() non appena il worker viene interrotto. Esegui l'override di questo metodo per chiudere tutte le risorse a cui potresti essere collegato.

Proprietà isStopped()

Puoi chiamare il ListenableWorker.isStopped() metodo per verificare se il worker è già stato interrotto. Se esegui operazioni a lunga esecuzione o ripetitive nel worker, devi controllare spesso questa proprietà e utilizzarla come segnale per interrompere il lavoro il prima possibile.

Nota: WorkManager ignora il Result impostato da un worker che ha ricevuto il segnale onStop, perché il worker è già considerato interrotto.