Définir des requêtes de tâche

Le guide de démarrage explique comment créer une WorkRequest simple et la mettre en file d'attente.

Le présent guide vous explique comment définir et personnaliser des objets WorkRequest pour gérer des cas d'utilisation courants. Vous découvrirez par exemple comment :

  • planifier une tâche ponctuelle et récurrente ;
  • définir des contraintes de tâche, comme la connexion Wi-Fi ou la recharge ;
  • garantir un délai minimal dans l'exécution de la tâche ;
  • définir des stratégies de nouvelle tentative et d'attente ;
  • transmettre les données d'entrée à la tâche ;
  • regrouper des tâches connexes à l'aide de tags.

Présentation

La tâche est définie dans WorkManager via une WorkRequest. Pour planifier une tâche avec WorkManager, vous devez d'abord créer un objet WorkRequest, puis le mettre en file d'attente.

Kotlin

val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java

WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

L'objet WorkRequest contient toutes les informations nécessaires à WorkManager pour planifier et exécuter votre tâche. Il inclut les contraintes à respecter pour que votre tâche s'exécute, ainsi que des informations de planification telles que les délais ou les intervalles récurrents, ainsi que les nouvelles tentatives. Il peut inclure en outre des données d'entrée si votre tâche en dépend.

WorkRequest est une classe de base abstraite. Il existe deux implémentations dérivées de cette classe que vous pouvez utiliser pour créer la requête : OneTimeWorkRequest et PeriodicWorkRequest. Comme leur nom l'indique, OneTimeWorkRequest est utile pour planifier des tâches non récurrentes, tandis que PeriodicWorkRequest est plus approprié pour planifier des tâches récurrentes à un certain intervalle.

Planifier une tâche ponctuelle

Pour une tâche simple, qui ne nécessite aucune configuration supplémentaire, utilisez la méthode statique from :

Kotlin

val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java

WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

Pour les tâches plus complexes, vous pouvez utiliser un compilateur :

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

Planifier une tâche prioritaire

WorkManager 2.7.0 a introduit le concept de tâche prioritaire. Cela permet à WorkManager d'exécuter une tâche importante tout en donnant au système un meilleur contrôle de l'accès aux ressources.

Une tâche prioritaire présente les caractéristiques suivantes :

  • Importance : la tâche prioritaire convient aux tâches importantes pour l'utilisateur ou lancées par celui-ci.
  • Vitesse : la tâche prioritaire convient mieux aux tâches courtes qui commencent immédiatement et se terminent en quelques minutes.
  • Quotas : un quota au niveau du système qui limite le temps d'exécution au premier plan détermine si une tâche prioritaire peut démarrer.
  • Gestion de l'alimentation : les restrictions de gestion de l'alimentation, telles que l'économiseur de batterie et le mode Sommeil, sont moins susceptibles d'affecter la tâche prioritaire.
  • Latence : le système exécute immédiatement la tâche prioritaire, à condition que la charge de travail actuelle du système le permette. Cela signifie que ces tâches sont sensibles à la latence et ne peuvent pas être programmées pour une exécution ultérieure.

Par exemple, une tâche prioritaire peut être utile dans une application de chat lorsque l'utilisateur souhaite envoyer un message ou une image jointe. De même, une application qui gère un flux de paiement ou d'abonnement peut également utiliser des tâches prioritaires. En effet, ces tâches sont importantes pour l'utilisateur, s'exécutent rapidement en arrière-plan, doivent commencer immédiatement et doivent continuer à s'exécuter même si l'utilisateur ferme l'application.

Quotas

Le système doit allouer le temps d'exécution à une tâche prioritaire avant de pouvoir s'exécuter. Le temps d'exécution n'est pas illimité. Chaque application reçoit plutôt un quota de temps d'exécution. Lorsque votre application utilise le temps d'exécution et atteint le quota qui lui est alloué, vous ne pouvez plus exécuter de tâches prioritaires tant que le quota n'a pas été actualisé. Android peut ainsi équilibrer plus efficacement les ressources entre les applications.

La durée d'exécution disponible pour une application dépend du bucket de secours et de l'importance du processus.

Vous pouvez déterminer ce qui se passe lorsque le quota d'exécution ne permet pas à une tâche prioritaire de s'exécuter immédiatement. Pour en savoir plus, consultez les extraits ci-dessous.

Exécuter une tâche prioritaire

À partir de WorkManager 2.7, votre application peut appeler setExpedited() pour déclarer qu'une WorkRequest doit s'exécuter le plus rapidement possible à l'aide d'une tâche prioritaire. L'extrait de code suivant fournit un exemple d'utilisation de 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();

Dans cet exemple, nous allons initialiser une instance de OneTimeWorkRequest et appeler setExpedited(). Cette demande devient alors une tâche prioritaire. Si le quota le permet, elle commence à s'exécuter immédiatement en arrière-plan. Si le quota a été utilisé, le paramètre OutOfQuotaPolicy indique que la requête doit être exécutée comme une tâche normale et non prioritaire.

Rétrocompatibilité et services de premier plan

Pour maintenir la rétrocompatibilité avec les tâches prioritaires, WorkManager peut exécuter un service de premier plan sur les versions de plate-forme antérieures à Android 12. Les services de premier plan peuvent afficher une notification à l'attention de l'utilisateur.

Les méthodes getForegroundInfoAsync() et getForegroundInfo() de votre nœud de calcul permettent à WorkManager d'afficher une notification lorsque vous appelez setExpedited() avant Android 12.

Toute ListenableWorker doit implémenter la méthode getForegroundInfo si vous souhaitez demander l'exécution de la tâche en tant que tâche accélérée.

Lorsque vous ciblez Android 12 ou une version ultérieure, les services de premier plan restent disponibles via la méthode setForeground correspondante.

Nœud de calcul

Les nœuds de calcul ne savent pas si leur tâche est prioritaire ou non. Ils peuvent toutefois afficher une notification sur certaines versions d'Android lorsqu'une WorkRequest a été priorisée.

Pour ce faire, WorkManager fournit la méthode getForegroundInfoAsync(), que vous devez implémenter pour que WorkManager puisse afficher une notification vous invitant à lancer une opération ForegroundService si nécessaire.

CoroutineWorker

Si vous utilisez un CoroutineWorker, vous devez implémenter getForegroundInfo(), puis le transmettre à setForeground() dans doWork(). La notification sera créée dans les versions d'Android antérieures à la version 12.

Prenons l'exemple suivant :

  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()
    }

}

Règles relatives aux quotas

Vous pouvez contrôler le fonctionnement de la tâche prioritaire lorsque votre application atteint son quota d'exécution. Pour continuer, vous pouvez transmettre setExpedited() :

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, qui entraîne l'exécution de la tâche en tant que requête de tâche ordinaire, comme le montre l'extrait ci-dessus.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, qui entraîne l'annulation de la requête si le quota est insuffisant.

Application exemple

Pour voir un exemple complet d'utilisation d'une tâche prioritaire dans WorkManager 2.7.0, consultez WorkManagerSample sur GitHub.

Tâche prioritaire différée

Le système tente d'exécuter une tâche prioritaire dès que possible, une fois la tâche appelée. Toutefois, comme pour d'autres types de tâches, le système peut différer le début d'une nouvelle tâche prioritaire, comme dans les cas suivants :

  • Charge : la charge du système est trop élevée, ce qui peut se produire lorsque trop de tâches sont déjà en cours d'exécution ou lorsque le système ne dispose pas de suffisamment de mémoire.
  • Quota : la limite de tâches prioritaires a été dépassée. La tâche prioritaire utilise un système de quotas basé sur les buckets de mise en veille des applications et limite la durée d'exécution maximale au cours d'une période glissante. Les quotas utilisés pour les tâches prioritaires sont plus restrictifs que ceux utilisés pour les autres types de tâches en arrière-plan.

Planifier une tâche périodique

Votre application peut parfois exiger que certaines tâches s'exécutent régulièrement. Par exemple, vous pouvez sauvegarder régulièrement vos données, télécharger du contenu récent dans votre application ou importer des journaux sur un serveur.

Voici comment utiliser PeriodicWorkRequest pour créer un objet WorkRequest qui s'exécute régulièrement :

Kotlin

val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java

PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

Dans cet exemple, la tâche est planifiée à un intervalle d'une heure.

La période d'intervalle correspond à la durée minimale entre les répétitions. Le délai exact d'exécution du nœud de calcul dépend des contraintes que vous utilisez dans votre objet WorkRequest et des optimisations effectuées par le système.

Intervalles d'exécution flexibles

Si la nature de votre tâche rend la durée d'exécution sensible, vous pouvez configurer votre PeriodicWorkRequest pour qu'elle s'exécute pendant une période flexible dans chaque intervalle, comme illustré dans la Figure 1.

Vous pouvez définir un intervalle flexible pour une tâche périodique. Vous définissez un intervalle de répétition et un intervalle flexible qui spécifie une certaine durée à la fin de l&#39;intervalle de répétition. WorkManager tente d&#39;exécuter votre tâche à un moment donné de l&#39;intervalle flexible dans chaque cycle.

Figure 1 : Ce schéma illustre des intervalles répétés avec la période flexible pendant laquelle la tâche peut être exécutée.

Pour définir une tâche périodique à l'aide d'une période flexible, vous transmettez un élément flexInterval avec l'élément repeatInterval lors de la création de l'élément PeriodicWorkRequest. La période flexible commence à repeatInterval - flexInterval et se termine à la fin de l'intervalle.

Voici un exemple de tâche périodique pouvant être exécutée au cours des 15 dernières minutes de chaque période d'une heure.

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'intervalle de répétition doit être supérieur ou égal à PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, et l'intervalle flexible doit être supérieur ou égal à PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS.

Effet des contraintes sur les tâches périodiques

Vous pouvez appliquer des contraintes à la tâche périodique. Par exemple, vous pouvez ajouter une contrainte à votre requête de tâche pour qu'elle ne s'exécute que lorsque l'appareil de l'utilisateur est en charge. Dans ce cas, même si l'intervalle de répétition défini est atteint, PeriodicWorkRequest ne s'exécute que lorsque cette condition est remplie. Cela peut retarder l'exécution d'une tâche spécifique ou même l'ignorer si les conditions ne sont pas remplies dans l'intervalle d'exécution.

Contraintes liées aux tâches

Les contraintes garantissent que la tâche est différée jusqu'à ce que les conditions optimales soient remplies. Les contraintes suivantes sont disponibles pour WorkManager.

NetworkType Limite le type de réseau requis pour l'exécution de votre tâche. Par exemple, Wi-Fi (UNMETERED).
BatteryNotLow Lorsque ce paramètre est défini sur « true », votre tâche n'est pas exécutée si l'appareil est en mode batterie faible.
RequiresCharging Lorsque ce paramètre est défini sur « true », votre tâche n'est exécutée que lorsque l'appareil est en charge.
DeviceIdle Lorsque ce paramètre est défini sur « true », l'appareil de l'utilisateur doit être inactif avant l'exécution de la tâche. Cela peut s'avérer utile pour exécuter des opérations par lot qui, autrement, pourraient avoir un impact négatif sur les performances des autres applications exécutées activement sur l'appareil de l'utilisateur.
StorageNotLow Lorsque ce paramètre est défini sur « true », votre tâche n'est pas exécutée si l'espace de stockage de l'utilisateur est trop faible sur l'appareil.

Pour créer un ensemble de contraintes et l'associer à des tâches, créez une instance Constraints à l'aide de Contraints.Builder() et attribuez-la à votre WorkRequest.Builder().

Par exemple, le code suivant génère une requête de tâche qui ne s'exécute que lorsque l'appareil de l'utilisateur est en charge et connecté au 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();

Lorsque plusieurs contraintes sont spécifiées, votre tâche ne s'exécute que lorsque toutes les contraintes sont remplies.

Si une contrainte devient non remplie pendant l'exécution de votre tâche, WorkManager arrête votre nœud de calcul. Une nouvelle tentative est effectuée lorsque toutes les contraintes sont remplies.

Tâche retardée

Si votre tâche n'a pas de contraintes ou que toutes les contraintes sont remplies lors de la mise en file d'attente, le système peut choisir de l'exécuter immédiatement. Si vous ne souhaitez pas que la tâche soit exécutée immédiatement, vous pouvez le spécifier en incluant un délai initial minimal.

Voici un exemple de configuration de votre tâche pour qu'elle s'exécute au moins 10 minutes après sa mise en file d'attente.

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java

WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

Bien que l'exemple montre comment définir un délai initial pour une OneTimeWorkRequest, vous pouvez également définir un délai initial pour une PeriodicWorkRequest. Dans ce cas, seule la première exécution de votre tâche périodique serait retardée.

Règle relative aux nouvelles tentatives et aux intervalles entre les tentatives

Si vous vous avez besoin que WorkManager relance votre tâche, vous pouvez renvoyer Result.retry() à votre nœud de calcul. Votre tâche est ensuite reprogrammée en fonction d'un délai d'intervalle entre les tentatives et d'une règle d'intervalle entre les tentatives.

  • Le délai d'intervalle entre les tentatives spécifie la durée minimale d'attente avant de relancer votre tâche après la première tentative. Cette valeur ne peut pas être inférieure à 10 secondes (ou MIN_BACKOFF_MILLIS).

  • La règle d'intervalle entre les tentatives définit comment l'intervalle augmente au fil du temps lors des nouvelles tentatives. WorkManager accepte deux règles d'intervalle entre les tentatives : LINEAR et EXPONENTIAL.

Chaque requête de tâche dispose d'un délai et d'une règle d'intervalle entre les tentatives. La règle par défaut est EXPONENTIAL avec un délai de 30 secondes, mais vous pouvez modifier ce paramètre dans la configuration de votre requête de tâche.

Voici un exemple de personnalisation du délai et de la règle d'intervalle entre les tentatives.

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();

Dans cet exemple, l'intervalle minimal entre les tentatives est défini sur la valeur minimale autorisée, à savoir 10 secondes. La règle étant LINEAR, l'intervalle augmente d'environ 10 secondes à chaque nouvelle tentative. Par exemple, la première exécution se terminant parResult.retry() est relancée au bout de 10 secondes, suivie de 20, 30, 40, etc., si la tâche continue de renvoyer Result.retry() après les tentatives suivantes. Si l'intervalle entre les tentatives était défini sur EXPONENTIAL, la séquence de durée des nouvelles tentatives serait plus proche de 20, 40, 80, etc.

Ajouter un tag à une tâche

Chaque requête de tâche possède unidentifiant unique, qui permet d'identifier cette tâche afin de l'annuler ou d'observer sa progression ultérieurement.

Si vous disposez d'un groupe de tâches logiquement liées, il peut être utile de les taguer. L'ajout de tags vous permet de travailler avec un groupe de requêtes de tâche.

Par exemple, WorkManager.cancelAllWorkByTag(String) annule toutes les requêtes de tâche avec un tag particulier et WorkManager.getWorkInfosByTag(String) renvoie une liste des objets WorkInfo qui peuvent être utilisés pour déterminer l'état actuel de la tâche.

Le code suivant montre comment ajouter un tag « cleanup » (nettoyage) à votre tâche :

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

Enfin, vous pouvez ajouter plusieurs tags à une même requête de tâche. En interne, ces tags sont stockés sous la forme d'un ensemble de chaînes. Pour obtenir l'ensemble des tags associés à WorkRequest, vous pouvez utiliser WorkInfo.getTags().

À partir de votre classe Worker, vous pouvez récupérer son ensemble de tags via ListenableWorker.getTags().

Attribuer les données d'entrée

Votre tâche peut nécessiter des données d'entrée. Par exemple, une tâche qui gère l'importation d'une image peut nécessiter que l'URI de l'image soit importé en entrée.

Les valeurs d'entrée sont stockées sous forme de paires clé/valeur dans un objet Data et peuvent être définies sur la requête de tâche. WorkManager fournit l'entrée Data à votre tâche lorsqu'il l'exécute. La classe Worker peut accéder aux arguments d'entrée en appelant Worker.getInputData(). Le code ci-dessous montre comment créer une instance Worker nécessitant des données d'entrée et comment les envoyer dans votre requête de tâche.

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();

De même, la classe Data peut être utilisée pour générer une valeur renvoyée. Les données d'entrée et de sortie sont traitées plus en détail dans la section Paramètres d'entrée et valeurs renvoyées.

Étapes suivantes

Sur la page États et observations, vous en apprendrez plus sur les états de tâche et sur la manière de surveiller la progression de votre tâche.