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 consiste nel chiamare il metodo enqueue() di WorkManager, passando il valore 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);

Fai attenzione quando metti in coda i lavori per evitare duplicati. Ad esempio, un'app potrebbe provare a caricare i log in un servizio di backend ogni 24 ore. Se non fai 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 programmare il lavoro come lavoro unico.

Lavoro unico

Il lavoro unico è un concetto potente che ti garantisce di avere 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 del lavoro.

Il lavoro unico può essere applicato sia a lavori una tantum che a lavori periodici. Puoi creare una sequenza di lavoro univoca chiamando uno di questi metodi, a seconda che tu stia pianificando un lavoro ricorrente o un lavoro una tantum.

Entrambi i 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 completata con quel nome univoco. Per ulteriori informazioni, consulta le norme per la risoluzione dei conflitti.
  • work: il WorkRequest per programmare.

Utilizzando un lavoro unico, possiamo risolvere il problema di programmazione duplicato indicato 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 conservato e non viene aggiunto nessun nuovo job.

Le sequenze di lavoro univoche possono essere utili anche se devi creare gradualmente una lunga catena di attività. Ad esempio, un'app di fotoritocco potrebbe consentire all'utente 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, vedi Il lavoro di Caino.

Norme per la risoluzione dei conflitti

Quando pianifichi un lavoro univoco, devi indicare a WorkManager quale azione eseguire in caso di conflitto. Per farlo, passa un'enumerazione quando metti in coda il lavoro.

Per i lavori una tantum devi fornire un elemento ExistingWorkPolicy, che supporta quattro opzioni per la gestione del conflitto.

  • REPLACE lavori esistenti con il nuovo lavoro. Questa opzione annulla il lavoro esistente.
  • KEEP lavoro esistente e ignora quello nuovo.
  • APPEND il nuovo lavoro alla fine di quello esistente. Questo criterio farà sì che il nuovo lavoro venga connesso a quello esistente, in esecuzione al termine di quello esistente.

Il lavoro esistente diventa un prerequisito della nuova opera. Se il lavoro esistente diventa CANCELLED o FAILED, anche la nuova opera sarà CANCELLED o FAILED. Se vuoi che il nuovo lavoro venga eseguito a prescindere dallo stato di quello esistente, utilizza invece APPEND_OR_REPLACE.

  • APPEND_OR_REPLACE funziona in modo simile a APPEND, con la differenza che non dipende dallo stato di lavoro dei prerequisiti. Se il lavoro esistente è CANCELLED o FAILED, il nuovo lavoro continuerà a essere eseguito.

Per i lavori del ciclo mestruale, fornisci un elemento ExistingPeriodicWorkPolicy, che supporta due opzioni: REPLACE e KEEP. Queste opzioni funzionano allo stesso modo delle controparti esistentiWorkPolicy.

Osservare il tuo lavoro

In qualsiasi momento dopo aver accodato il lavoro, puoi controllarne lo stato eseguendo una query su WorkManager tramite il name, id o 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 elemento ListenableFuture di un oggetto WorkInfo, che include id dell'opera, i relativi tag, State corrente e qualsiasi set di dati di output tramite Result.success(outputData).

Una variante LiveData di ciascuno dei metodi ti consente di osservare le modifiche al WorkInfo registrando un listener. Ad esempio, se vuoi mostrare un messaggio all'utente al termine di un lavoro, potresti impostarlo come segue:

Kotlin


workManager.getWorkInfoByIdLiveData(syncWorker.id)
               .observe(viewLifecycleOwner) { 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'esecuzione di query per il lavoro in base a una combinazione dei suoi tag, stato e nome dell'opera univoco.

Il seguente esempio mostra come trovare tutte le operazioni con il tag "syncTag", che si trova in stato FAILED o CANCELLED e ha un nome univoco dell'opera "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 elemento WorkQuery è AND-ed con gli altri. Ciascun valore in un componente è OR-ed. Ad esempio: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...).

WorkQuery funziona anche con l'equivalente LiveData, getWorkInfosLiveData().

Annullamento e interruzione del lavoro

Se non hai più bisogno di eseguire il lavoro precedentemente aggiunto in coda, puoi richiederne l'annullamento. Un lavoro può essere annullato tramite name, id o 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");

In background, WorkManager controlla State del lavoro. Se un lavoro è già completato, non succede nulla. In caso contrario, lo stato dell'opera viene modificato in CANCELLED e il lavoro non verrà eseguito in futuro. Anche tutti i job di WorkRequest dipendenti da questo lavoro saranno CANCELLED.

Al momento, il lavoro di RUNNING riceve una chiamata al numero ListenableWorker.onStopped(). Esegui l'override di questo metodo per gestire qualsiasi possibile pulizia. Vedi Arresta un worker in esecuzione per ulteriori informazioni.

Arresta 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 operazioni uniche, hai esplicitamente inserito in coda un nuovo WorkRequest con ExistingWorkPolicy REPLACE. Il vecchio WorkRequest viene immediatamente considerato annullato.
  • I vincoli del tuo lavoro non vengono più soddisfatti.
  • Il sistema ha indicato all'app di interrompere il lavoro per un motivo non determinato. Ciò può verificarsi se superi la scadenza di esecuzione di 10 minuti. Il lavoro è programmato per un secondo momento.

In queste condizioni, il worker è stato arrestato.

Devi interrompere cooperativamente il lavoro in corso e rilasciare tutte le risorse a cui il worker sta lavorando. Ad esempio, a questo punto devi chiudere gli handle aperti per i database e i file. Esistono due meccanismi a tua disposizione per capire quando il lavoratore si ferma.

Callback onSST()

WorkManager richiama ListenableWorker.onStopped() non appena il worker è stato arrestato. Esegui l'override di questo metodo per chiudere tutte le risorse che stai conservando.

proprietà isSST()

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

Nota: WorkManager ignora Result impostato da un worker che ha ricevuto l'indicatore onStop, perché il worker è già considerato arresto.