Gestione del lavoro

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.

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 a APPEND, tranne per il fatto che non dipende dallo stato di avanzamento del prerequisito. Se il lavoro esistente è CANCELLED o FAILED, 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 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. 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());
        }
  });