De nombreuses options sont disponibles sur Android pour un travail en arrière-plan différable. Cet atelier de programmation porte sur WorkManager, une bibliothèque rétrocompatible, flexible et simple pour exécuter en arrière-plan des travaux différables. WorkManager est le planificateur de tâches recommandé sur Android pour les tâches différables, dont l'exécution est garantie.
Qu'est-ce que WorkManager ?
Partie intégrante d'Android Jetpack, WorkManager est un composant d'architecture permettant d'exécuter en arrière-plan un travail qui nécessite une exécution à la fois opportuniste et garantie. Une exécution opportuniste signifie que WorkManager exécute le travail en arrière-plan dès que possible. Une exécution garantie signifie que WorkManager gère la logique permettant de démarrer le travail dans diverses situations, même si vous quittez votre application.
WorkManager est une bibliothèque simple, mais incroyablement flexible, qui présente de nombreux avantages. En voici quelques-uns :
- Prise en charge des tâches asynchrones ponctuelles et périodiques
- Prise en charge des contraintes telles que l'état du réseau, l'espace de stockage et l'état de charge
- Enchaînement de requêtes de travail complexes, y compris l'exécution du travail en parallèle
- Résultat d'une requête de travail utilisé comme entrée pour la requête suivante
- Rétrocompatibilité au niveau de l'API avec le niveau d'API 14 (voir la remarque)
- Fonctionnement avec ou sans les services Google Play
- Respect des bonnes pratiques relatives à l'état du système
- Compatibilité avec LiveData, pour afficher facilement l'état des requêtes de travail dans l'interface utilisateur
Quand utiliser WorkManager
La bibliothèque WorkManager est un excellent choix pour les tâches qu'il est nécessaire de terminer même si l'utilisateur quitte l'écran ou l'application en question.
Voici quelques exemples de tâches pour lesquelles WorkManager s'avère particulièrement utile :
- Envoi de journaux
- Application de filtres à des images et enregistrement de celles-ci
- Synchronisation périodique des données locales avec le réseau
WorkManager garantit l'exécution, ce que n'exigent pas toutes les tâches. De ce fait, ce n'est pas un outil générique pour exécuter n'importe quelle tâche en dehors du thread principal. Pour savoir quand utiliser WorkManager, consultez le guide sur le traitement en arrière-plan.
Objectif de cet atelier
Les smartphones actuels sont presque trop bons pour prendre des photos. Le temps où le photographe pouvait prendre une photo un peu floue d'un objet mystérieux est révolu.
Dans cet atelier de programmation, vous allez utiliser Blur-O-Matic, une application qui permet de flouter des photos et des images, et d'enregistrer le résultat dans un fichier. S'agit-il du monstre du Loch Ness ou d'un jouet de bain ? Grâce à Blur-O-Matic, personne ne le saura jamais.
|
| |
Photo d'un bar rayé hybride par Peggy Greb, service de recherche agricole du ministère américain de l'Agriculture | ||
Points abordés
- Ajout de WorkManager à votre projet
- Planification d'une tâche simple
- Paramètres d'entrée et de sortie
- Enchaînement de travaux
- Travail unique
- Affichage de l'état du travail dans l'interface utilisateur
- Annulation d'un travail
- Contraintes liées aux travaux
Prérequis
- La dernière version stable d'Android Studio
- Vous devez également connaître les classes
LiveDataetViewModel. Si vous ne les connaissez pas encore, consultez l'atelier de programmation sur les composants du cycle de vie d'Android (en particulier pour ViewModel et LiveData) ou l'atelier de programmation sur Room (présentation des composants de l'architecture).
Si vous êtes bloqué…
Si vous vous retrouvez bloqué au cours de cet atelier de programmation ou si vous souhaitez voir le code final, vous pouvez utiliser le lien suivant :
Ou, si vous préférez, vous pouvez cloner l'atelier de programmation de WorkManager à partir de GitHub :
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
Étape 1 : Téléchargez le code
Cliquez sur le lien ci-dessous pour télécharger l'ensemble du code de cet atelier de programmation :
Ou, si vous préférez, vous pouvez cloner l'atelier de programmation sur la navigation depuis GitHub :
$ git clone -b start_java https://github.com/googlecodelabs/android-workmanager
Étape 2 : Obtenez une image
Si vous utilisez un appareil sur lequel vous avez déjà téléchargé ou pris des photos, vous n'avez rien d'autre à faire.
Si vous utilisez un nouvel appareil (comme un émulateur récemment créé), vous devez soit prendre une photo, soit télécharger une image depuis le Web à l'aide de l'appareil. Choisissez quelque chose de mystérieux !
Étape 3 : Exécutez l'application
Exécutez l'application. Les écrans suivants devraient s'afficher. Veillez à autoriser l'accès aux photos depuis l'invite initiale et, si l'image est désactivée, rouvrez l'application :
|
|
Vous pouvez sélectionner une image et passer à l'écran suivant, qui contient des cases d'option pour sélectionner le niveau de flou de l'image. Appuyez sur le bouton Go (Appliquer) pour flouter et enregistrer l'image.
À ce stade, l'application n'applique aucun floutage.
Le code de départ contient les éléments suivants :
WorkerUtils**:** cette classe contient le code du floutage, ainsi que des méthodes pratiques que vous utiliserez plus tard pour afficherNotificationset ralentir l'application.BlurActivity***:** activité qui permet d'afficher l'image, et qui inclut des cases d'option pour sélectionner le niveau de flou.BlurViewModel***:** ce modèle de vue stocke toutes les données nécessaires à l'affichage deBlurActivity. C'est également dans cette classe que vous démarrez le travail en arrière-plan à l'aide de WorkManager.Constants**:** classe statique comprenant quelques constantes que vous utiliserez dans cet atelier de programmation.SelectImageActivity**:** première activité vous permettant de sélectionner une image.res/activity_blur.xmletres/activity_select.xml: fichiers de mise en page pour chaque activité.
***** Seuls fichiers dans lesquels vous écrirez le code.
WorkManager nécessite la dépendance Gradle ci-dessous. Ils ont déjà été inclus dans les fichiers de version :
app/build.gradle
dependencies {
// Other dependencies
implementation "androidx.work:work-runtime:$versions.work"
}
Vous devez télécharger la dernière version de work-runtime sur cette page et installer la version appropriée. À l'heure actuelle, la dernière version est la suivante :
build.gradle
versions.work = "2.3.3"
Si vous passez à une version plus récente, veillez à cliquer sur Sync Now (Synchroniser maintenant) pour synchroniser votre projet avec les fichiers Gradle modifiés.
Au cours de cette étape, vous allez utiliser une image du dossier res/drawable appelée test.jpg et y exécuter quelques fonctions en arrière-plan. Celles-ci vont flouter l'image et l'enregistrer dans un fichier temporaire.
Principes de base de WorkManager
Voici quelques-unes des classes de WorkManager que vous devez connaître :
Worker: c'est ici que vous devez insérer le code correspondant au travail à effectuer en arrière-plan. Vous allez développer cette classe et remplacer la méthodedoWork().WorkRequest: représente une requête d'exécution d'un travail. Vous transmettrez votreWorkerdans le cadre de la création de votreWorkRequest. Lorsque vous créez laWorkRequest, vous pouvez aussi spécifier des éléments tels que desConstraintsconcernant l'exécution deWorker.WorkManager: cette classe planifie votreWorkRequestet l'exécute. Elle planifie lesWorkRequestde manière à répartir la charge pesant sur les ressources système, tout en respectant les contraintes que vous spécifiez.
Dans le cas présent, vous devrez définir un nouveau BlurWorker, qui contiendra le code permettant de flouter une image. Lorsque vous cliquez sur le bouton Go (Appliquer), WorkRequest est créé, puis placé en file d'attente par WorkManager.
Étape 1 : Créez BlurWorker
Dans le package workers, créez une classe appelée BlurWorker.
Elle doit étendre Worker.
Étape 2 : Ajoutez un constructeur
Ajoutez un constructeur à la classe BlurWorker :
public class BlurWorker extends Worker {
public BlurWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
}
Étape 3 : Remplacez et implémentez doWork()
Votre Worker floutera l'image res/test.jpg.
Remplacez la méthode doWork(), puis implémentez les éléments suivants :
- Obtenez un
Contexten appelantgetApplicationContext(). Vous en aurez besoin pour les diverses manipulations bitmap que vous êtes sur le point d'effectuer. - Créez un
Bitmapà partir de l'image de test :
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.test);
- Obtenez une version floue du bitmap en appelant la méthode statique
blurBitmapà partir deWorkerUtils. - Écrivez ce bitmap dans un fichier temporaire en appelant la méthode statique
writeBitmapToFileà partir deWorkerUtils. Veillez à enregistrer l'URI renvoyé dans une variable locale. - Créez une notification affichant l'URI en appelant la méthode statique
makeStatusNotificationà partir deWorkerUtils. - Renvoyez
Result.success();. - Encapsulez le code des étapes 2 à 6 dans une instruction try/catch. Récupérez un
Throwablegénérique. - Dans l'instruction catch, émettez une instruction de journalisation des erreurs :
Log.e(TAG, "Error applying blur", throwable); - Dans l'instruction catch, renvoyez ensuite
Result.failure();
Le code final de cette étape est donné ci-dessous.
BlurWorker.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import com.example.background.R;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class BlurWorker extends Worker {
public BlurWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = BlurWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
try {
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.test);
// Blur the bitmap
Bitmap output = WorkerUtils.blurBitmap(picture, applicationContext);
// Write bitmap to a temp file
Uri outputUri = WorkerUtils.writeBitmapToFile(applicationContext, output);
WorkerUtils.makeStatusNotification("Output is "
+ outputUri.toString(), applicationContext);
// If there were no errors, return SUCCESS
return Result.success();
} catch (Throwable throwable) {
// Technically WorkManager will return Result.failure()
// but it's best to be explicit about it.
// Thus if there were errors, we're return FAILURE
Log.e(TAG, "Error applying blur", throwable);
return Result.failure();
}
}
}
Étape 4 : Obtenez WorkManager dans ViewModel
Créez une variable pour une instance WorkManager dans votre ViewModel et instanciez-la dans le constructeur de ViewModel :
BlurViewModel.java
private WorkManager mWorkManager;
// BlurViewModel constructor
public BlurViewModel(@NonNull Application application) {
super(application);
mWorkManager = WorkManager.getInstance(application);
//...rest of the constructor
}
Étape 5 : Placez WorkRequest en file d'attente dans WorkManager
Il est temps de créer une WorkRequest et de demander à WorkManager de l'exécuter. Il existe deux types de WorkRequest :
OneTimeWorkRequest:WorkRequestqui ne s'exécute qu'une seule fois.PeriodicWorkRequest:WorkRequestqui s'exécute de manière cyclique.
L'image ne doit être floutée qu'une seule fois, lorsque vous cliquez sur le bouton Go (Appliquer). La méthode applyBlur est appelée quand vous cliquez sur le bouton Go (Appliquer). Vous devez donc créer une OneTimeWorkRequest à partir de BlurWorker. Ensuite, utilisez votre instance WorkManager pour placer votre WorkRequest. en file d'attente.
Ajoutez la ligne de code suivante à la méthode applyBlur() de BlurViewModel :
BlurViewModel.java
void applyBlur(int blurLevel) {
mWorkManager.enqueue(OneTimeWorkRequest.from(BlurWorker.class));
}
Étape 6 : Exécutez le code
Exécutez votre code. Il devrait se compiler, et une notification devrait s'afficher lorsque vous cliquez sur le bouton Go (Appliquer).

Vous pouvez aussi ouvrir l'Explorateur de fichiers de l'appareil dans Android Studio :

Accédez ensuite à data>data>com.example.background>files>blur_filter_outputs><URI> et vérifiez que le poisson a bien été flouté :

Le floutage de cette image de test est satisfaisant, mais pour que Blur-O-Matic devienne l'application révolutionnaire de retouche d'images qu'elle doit être, vous devez permettre aux utilisateurs de flouter leurs propres images.
Pour ce faire, nous allons fournir à notre WorkRequest l'URI de l'image sélectionnée par l'utilisateur en tant qu'entrée.
Étape 1 : Créez un objet d'entrée de données
Les entrées et les sorties sont transférées via des objets Data. Les objets Data sont des conteneurs légers pour les paires clé/valeur. Ils servent à stocker une petite quantité de données qui peuvent être transmises ou non depuis des WorkRequest.
Vous allez transmettre l'URI de l'image de l'utilisateur à un groupe. Cet URI est stocké dans une variable appelée mImageUri.
Créez une méthode privée appelée createInputDataForUri. Cette méthode doit permettre d'effectuer les tâches suivantes :
- Créer un objet
Data.Builder. - Si
mImageUriest une valeurURInon nulle, ajoutez-le à l'objetDataà l'aide de la méthodeputString. Cette méthode utilise une clé et une valeur. Vous pouvez utiliser la constante de chaîneKEY_IMAGE_URIdepuis la classeConstants. - Appeler
build()sur l'objetData.Builderpour créer l'objetDataet le renvoyer.
Vous trouverez ci-dessous la méthode createInputDataForUri complète :
BlurViewModel.java
/**
* Creates the input data bundle which includes the Uri to operate on
* @return Data which contains the Image Uri as a String
*/
private Data createInputDataForUri() {
Data.Builder builder = new Data.Builder();
if (mImageUri != null) {
builder.putString(KEY_IMAGE_URI, mImageUri.toString());
}
return builder.build();
}
Étape 2 : Transmettez l'objet de données à WorkRequest
Vous allez modifier la méthode applyBlur pour qu'elle :
- crée un objet
OneTimeWorkRequest.Builder; - appelle
setInputDataet transmette le résultat depuiscreateInputDataForUri; - crée
OneTimeWorkRequest; - place la requête en file d'attente à l'aide de
WorkManager.
Vous trouverez ci-dessous la méthode applyBlur complète :
BlurViewModel.java
void applyBlur(int blurLevel) {
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
}
Étape 3 : Mettez à jour l'élément "doWork()" de BlurWorker pour obtenir l'entrée
Nous allons maintenant mettre à jour la méthode doWork() de BlurWorker pour obtenir l'URI que nous avons transmis à partir de l'objet Data :
BlurWorker.java
public Result doWork() {
Context applicationContext = getApplicationContext();
// ADD THIS LINE
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI);
//... rest of doWork()
}
Cette variable sera utilisée à l'issue des prochaines étapes.
Étape 4 : Floutez l'URI donné
L'URI vous permet de flouter l'image sélectionnée par l'utilisateur :
BlurWorker.java
public Worker.Result doWork() {
Context applicationContext = getApplicationContext();
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI);
try {
// REPLACE THIS CODE:
// Bitmap picture = BitmapFactory.decodeResource(
// applicationContext.getResources(),
// R.drawable.test);
// WITH
if (TextUtils.isEmpty(resourceUri)) {
Log.e(TAG, "Invalid input uri");
throw new IllegalArgumentException("Invalid input uri");
}
ContentResolver resolver = applicationContext.getContentResolver();
// Create a bitmap
Bitmap picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
//...rest of doWork
Étape 5 : Renvoyez l'URI temporaire
Nous en avons terminé avec ce nœud de calcul et pouvons désormais renvoyer Result.success(). Nous allons fournir OutputURI sous forme de donnée de sortie pour que cette image temporaire soit facilement accessible aux autres nœuds de calcul pour d'autres opérations. Cela servira lorsque nous créerons une chaîne de nœuds de calcul au prochain chapitre. Pour ce faire, procédez comme suit :
- Créez un fichier
Data, comme vous l'avez fait pour l'entrée, puis enregistrezoutputUrien tant queString. Utilisez la même clé,KEY_IMAGE_URI. - Transmettez-la à la méthode
Result.success()deWorker.
BlurWorker.java
Cette ligne doit suivre la ligne WorkerUtils.makeStatusNotification et remplacer Result.success() dans doWork() :
Data outputData = new Data.Builder()
.putString(KEY_IMAGE_URI, outputUri.toString())
.build();
return Result.success(outputData);
Étape 6 : Exécutez votre application
À ce stade, vous devez exécuter votre application. Elle doit se compiler et présenter le même comportement.
Vous pouvez aussi ouvrir l'Explorateur de fichiers de l'appareil dans Android Studio et accéder à data/data/com.example.background/files/blur_filter_outputs/<URI> comme vous l'avez fait à l'étape précédente.
Notez que vous devrez peut-être lancer la synchronisation pour afficher les images :

Bien joué ! Vous avez flouté une image d'entrée en utilisant WorkManager.
Pour l'instant, vous n'effectuez qu'une seule tâche : flouter l'image. C'est une première étape importante, mais il manque quelques fonctionnalités de base :
- Les fichiers temporaires ne sont pas nettoyés.
- L'image n'est pas enregistrée dans un fichier permanent.
- L'image est toujours floutée de la même manière.
Nous allons utiliser une chaîne de travail WorkManager pour ajouter cette fonctionnalité.
WorkManager vous permet de créer des WorkerRequest distinctes qui s'exécutent de façon séquentielle ou parallèle. Au cours de cette étape, vous allez créer une chaîne de travail qui ressemble à ceci :

Les WorkRequest sont représentées par des cases.
Autre avantage très intéressant de la création d'une chaîne : la sortie d'une WorkRequest est l'entrée de la WorkRequest suivante dans la chaîne. L'entrée et la sortie transmises entre chaque WorkRequest s'affichent en bleu.
Étape 1 : Configurez le nettoyage et enregistrez les nœuds de calcul
Commencez par définir toutes les classes Worker dont vous avez besoin. Vous disposez déjà d'un Worker pour flouter une image, mais vous avez également besoin d'un Worker qui nettoie les fichiers temporaires et d'un Worker qui enregistre l'image de manière définitive.
Créez deux classes dans le package worker qui étendent Worker.
La première doit être appelée CleanupWorker, et la seconde SaveImageToFileWorker.
Étape 2 : Ajoutez un constructeur
Ajoutez un constructeur à la classe CleanupWorker :
public class CleanupWorker extends Worker {
public CleanupWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
}
Étape 3 : Remplacez et implémentez doWork() pour CleanupWorker
CleanupWorker n'a pas besoin de recevoir d'entrées ni de transmettre des sorties. Il supprime toujours les fichiers temporaires existants. Comme il ne s'agit pas d'un atelier de programmation sur la manipulation de fichiers, vous pouvez copier le code de CleanupWorker ci-dessous :
CleanupWorker.java
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.example.background.Constants;
import java.io.File;
public class CleanupWorker extends Worker {
public CleanupWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = CleanupWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
// Makes a notification when the work starts and slows down the work so that it's easier to
// see each WorkRequest start, even on emulated devices
WorkerUtils.makeStatusNotification("Cleaning up old temporary files",
applicationContext);
WorkerUtils.sleep();
try {
File outputDirectory = new File(applicationContext.getFilesDir(),
Constants.OUTPUT_PATH);
if (outputDirectory.exists()) {
File[] entries = outputDirectory.listFiles();
if (entries != null && entries.length > 0) {
for (File entry : entries) {
String name = entry.getName();
if (!TextUtils.isEmpty(name) && name.endsWith(".png")) {
boolean deleted = entry.delete();
Log.i(TAG, String.format("Deleted %s - %s",
name, deleted));
}
}
}
}
return Worker.Result.success();
} catch (Exception exception) {
Log.e(TAG, "Error cleaning up", exception);
return Worker.Result.failure();
}
}
}
Étape 4 : Remplacez et implémentez doWork() pour SaveImageToFileWorker
SaveImageToFileWorker nécessite une entrée et transmet une sortie. L'entrée est une String stockée avec la clé KEY_IMAGE_URI. La sortie sera également une String stockée avec la clé KEY_IMAGE_URI.

Comme il ne s'agit toujours pas d'un atelier de programmation sur les manipulations de fichiers, le code est donné ci-dessous. Il contient deux TODO pour que vous puissiez insérer le code approprié pour l'entrée et la sortie. Ce code est très semblable à celui que vous avez écrit à l'étape précédente pour l'entrée et la sortie (il utilise les mêmes clés).
SaveImageToFileWorker.java
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.example.background.Constants;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class SaveImageToFileWorker extends Worker {
public SaveImageToFileWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = SaveImageToFileWorker.class.getSimpleName();
private static final String TITLE = "Blurred Image";
private static final SimpleDateFormat DATE_FORMATTER =
new SimpleDateFormat("yyyy.MM.dd 'at' HH:mm:ss z", Locale.getDefault());
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
// Makes a notification when the work starts and slows down the work so that it's easier to
// see each WorkRequest start, even on emulated devices
WorkerUtils.makeStatusNotification("Saving image", applicationContext);
WorkerUtils.sleep();
ContentResolver resolver = applicationContext.getContentResolver();
try {
String resourceUri = getInputData()
.getString(Constants.KEY_IMAGE_URI);
Bitmap bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
String outputUri = MediaStore.Images.Media.insertImage(
resolver, bitmap, TITLE, DATE_FORMATTER.format(new Date()));
if (TextUtils.isEmpty(outputUri)) {
Log.e(TAG, "Writing to MediaStore failed");
return Result.failure();
}
Data outputData = new Data.Builder()
.putString(Constants.KEY_IMAGE_URI, outputUri)
.build();
return Result.success(outputData);
} catch (Exception exception) {
Log.e(TAG, "Unable to save image to Gallery", exception);
return Worker.Result.failure();
}
}
}
Étape 5 : Modifiez la notification BlurWorker
Maintenant que nous disposons d'une chaîne de Worker qui se charge d'enregistrer l'image dans le dossier approprié, nous pouvons modifier la notification pour informer les utilisateurs de la progression du travail et ralentir le travail afin que l'utilisateur puisse voir plus facilement chaque WorkRequest démarrer, même sur les appareils émulés. La version finale de BlurWorker devient :
BlurWorker.java
@NonNull
@Override
public Worker.Result doWork() {
Context applicationContext = getApplicationContext();
// Makes a notification when the work starts and slows down the work so that it's easier to
// see each WorkRequest start, even on emulated devices
WorkerUtils.makeStatusNotification("Blurring image", applicationContext);
WorkerUtils.sleep();
String resourceUri = getInputData().getString(KEY_IMAGE_URI);
try {
if (TextUtils.isEmpty(resourceUri)) {
Log.e(TAG, "Invalid input uri");
throw new IllegalArgumentException("Invalid input uri");
}
ContentResolver resolver = applicationContext.getContentResolver();
// Create a bitmap
Bitmap picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
// Blur the bitmap
Bitmap output = WorkerUtils.blurBitmap(picture, applicationContext);
// Write bitmap to a temp file
Uri outputUri = WorkerUtils.writeBitmapToFile(applicationContext, output);
Data outputData = new Data.Builder()
.putString(KEY_IMAGE_URI, outputUri.toString())
.build();
// If there were no errors, return SUCCESS
return Result.success(outputData);
} catch (Throwable throwable) {
// Technically WorkManager will return Result.failure()
// but it's best to be explicit about it.
// Thus if there were errors, we're return FAILURE
Log.e(TAG, "Error applying blur", throwable);
return Result.failure();
}
}
Étape 6 : Créez une chaîne de WorkRequest
Vous devez modifier la méthode applyBlur de BlurViewModel afin qu'elle exécute une chaîne de WorkRequest au lieu d'un seul. Actuellement, le code ressemble à ceci :
BlurViewModel.java
void applyBlur(int blurLevel) {
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
}
Au lieu d'appeler WorkManager.enqueue(), appelez WorkManager.beginWith(). Cela renvoie une WorkContinuation, qui définit une chaîne de WorkRequest. Vous pouvez ajouter des éléments à cette chaîne de requêtes de travail en appelant la méthode then(). Par exemple, si vous avez trois objets WorkRequest, workA, workB et workC, vous pouvez procéder comme suit :
// Example code. Don't copy to the project
WorkContinuation continuation = mWorkManager.beginWith(workA);
continuation.then(workB) // FYI, then() returns a new WorkContinuation instance
.then(workC)
.enqueue(); // Enqueues the WorkContinuation which is a chain of work
Ce code génère et exécute la chaîne de WorkRequests suivante :

Créez une chaîne de CleanupWorker WorkRequest, une BlurImage WorkRequest et une SaveImageToFile WorkRequest dans applyBlur. Transmettez l'entrée à BlurImage WorkRequest.
Le code correspondant est donné ci-dessous :
BlurViewModel.java
void applyBlur(int blurLevel) {
// Add WorkRequest to Cleanup temporary images
WorkContinuation continuation =
mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// Add WorkRequest to blur the image
OneTimeWorkRequest blurRequest = new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
continuation = continuation.then(blurRequest);
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save =
new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.build();
continuation = continuation.then(save);
// Actually start the work
continuation.enqueue();
}
Le code devrait se compiler et s'exécuter. L'image que vous avez choisi de flouter devrait désormais être disponible dans votre dossier Images :

Étape 7 : Exécutez à nouveau BlurWorker
Il est maintenant temps d'ajouter la possibilité de flouter plus ou moins fortement l'image. Prenez le paramètre blurLevel transmis à applyBlur et ajoutez autant d'opérations WorkRequest de floutage à la chaîne. Seules les premières WorkRequest ont besoin de recevoir l'entrée URI.
Essayez, puis comparez avec le code ci-dessous :
BlurViewModel.java
void applyBlur(int blurLevel) {
// Add WorkRequest to Cleanup temporary images
WorkContinuation continuation = mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// Add WorkRequests to blur the image the number of times requested
for (int i = 0; i < blurLevel; i++) {
OneTimeWorkRequest.Builder blurBuilder =
new OneTimeWorkRequest.Builder(BlurWorker.class);
// Input the Uri if this is the first blur operation
// After the first blur operation the input will be the output of previous
// blur operations.
if ( i == 0 ) {
blurBuilder.setInputData(createInputDataForUri());
}
continuation = continuation.then(blurBuilder.build());
}
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.build();
continuation = continuation.then(save);
// Actually start the work
continuation.enqueue();
}
Beau travail ! Vous pouvez désormais modifier le niveau de floutage d'une image pour plus de mystère !

Maintenant que vous avez utilisé des chaînes, il est temps de voir une autre fonctionnalité puissante de WorkManager : les chaînes de travail unique.
Il arrive parfois que vous ne souhaitiez exécuter qu'une seule chaîne de travail à la fois. Par exemple, si vous avez une chaîne de travail qui synchronise vos données locales avec le serveur, vous souhaitez probablement que la première synchronisation des données se termine avant qu'une nouvelle démarre. Pour ce faire, vous devez utiliser beginUniqueWork au lieu de beginWith. Vous indiquez un nom String unique. Ce nom qualifie l'intégralité de la chaîne de requêtes de travail. Ainsi, vous pouvez y faire référence et les interroger ensemble.
Assurez-vous que votre chaîne de travail pour flouter votre fichier est unique en utilisant beginUniqueWork. Transmettez IMAGE_MANIPULATION_WORK_NAME comme clé. Vous devez également transmettre une ExistingWorkPolicy. Les options disponibles sont REPLACE, KEEP et APPEND.
Vous utiliserez REPLACE, car nous voulons arrêter le floutage en cours si l'utilisateur décide de flouter une nouvelle image avant la fin.
Le code permettant de démarrer votre continuation de travail unique est le suivant :
BlurViewModel.java
// REPLACE THIS CODE:
// WorkContinuation continuation =
// mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// WITH
WorkContinuation continuation = mWorkManager
.beginUniqueWork(IMAGE_MANIPULATION_WORK_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.from(CleanupWorker.class));
Blur-O-Matic ne va désormais flouter qu'une image à la fois.
Cette section fait un usage intensif de LiveData. Ainsi, pour bien comprendre ce qui se passe, vous devez connaître LiveData. LiveData est un conteneur de données observable et sensible au cycle de vie.
Si c'est la première fois que vous travaillez avec LiveData ou des objets observables, consultez la documentation ou suivez l'atelier de programmation sur les composants du cycle de vie d'Android.
La modification majeure suivante consiste à modifier ce qui s'affiche dans l'application au fur et à mesure que le travail s'exécute.
Vous pouvez obtenir l'état de n'importe quelle WorkRequest en obtenant un LiveData contenant un objet WorkInfo. WorkInfo est un objet qui contient des informations sur l'état actuel d'une WorkRequest, y compris :
- L'état du travail (
BLOCKED,CANCELLED,ENQUEUED,FAILED,RUNNINGouSUCCEEDED) - Si la
WorkRequestest terminée, toutes les données de sortie du travail
Le tableau suivant présente trois méthodes différentes permettant d'obtenir des objets LiveData<WorkInfo> ou LiveData<List<WorkInfo>>, ainsi que leur fonction.
Type | Méthode WorkManager | Description |
Obtention du travail à partir de l'ID |
| Chaque |
Obtention du travail à partir d'un nom de chaîne unique |
| Comme vous venez de le voir, les |
Obtention du travail à partir d'un tag |
| Enfin, si vous le souhaitez, vous pouvez ajouter un tag à n'importe quelle WorkRequest avec une chaîne. Vous pouvez utiliser le même tag pour plusieurs |
Vous allez ajouter le tag SaveImageToFileWorker WorkRequest pour le récupérer à l'aide de getWorkInfosByTagLiveData. Vous allez utiliser un tag pour étiqueter votre travail au lieu d'utiliser l'ID WorkManager. Ainsi, si l'utilisateur floute plusieurs images, toutes les WorkRequest d'enregistrement d'image seront associées au même tag, mais pas au même ID. Vous pouvez également choisir le tag.
Vous ne devez pas utiliser getWorkInfosForUniqueWorkLiveData, car cela renverrait WorkInfo pour toutes les WorkRequest de floutage et les WorkRequest de nettoyage. Une logique supplémentaire serait nécessaire pour trouver la WorkRequest d'enregistrement d'image.
Étape 1 : Ajoutez un tag à votre travail
Dans applyBlur, lorsque vous créez SaveImageToFileWorker, ajoutez un tag à votre travail à l'aide de la constante String TAG_OUTPUT :
BlurViewModel.java
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.addTag(TAG_OUTPUT) // This adds the tag
.build();
Étape 2 : Obtenez WorkInfo
Maintenant que vous avez ajouté un tag à votre travail, vous pouvez obtenir WorkInfo :
- Déclarez une nouvelle variable appelée
mSavedWorkInfo, qui est de typeLiveData<List<WorkInfo>>. - Dans le constructeur
BlurViewModel, obtenez la valeurWorkInfoà l'aide deWorkManager.getWorkInfosByTagLiveData. - Ajoutez un getter pour
mSavedWorkInfo.
Le code dont vous avez besoin est donné ci-dessous :
BlurViewModel.java
// New instance variable for the WorkInfo class
private LiveData<List<WorkInfo>> mSavedWorkInfo;
// Placed this code in the BlurViewModel constructor
mSavedWorkInfo = mWorkManager.getWorkInfosByTagLiveData(TAG_OUTPUT);
// Add a getter method for mSavedWorkInfo
LiveData<List<WorkInfo>> getOutputWorkInfo() { return mSavedWorkInfo; }
Étape 3 : Affichez WorkInfo
Maintenant que vous disposez de LiveData pour WorkInfo, vous pouvez les observer dans BlurActivity. Dans l'observateur :
- Vérifiez que la liste de
WorkInfon'est pas nulle et qu'elle contient des objetsWorkInfo. Si ce n'est pas le cas, cela signifie que l'utilisateur n'a pas encore cliqué sur le bouton Go (Appliquer), vous devez donc la renvoyer. - Obtenez les premières
WorkInfode la liste. Il n'y aura toujours qu'une seuleWorkInfotaguée avecTAG_OUTPUT, car la chaîne de travail est unique. - Vérifiez si l'état du travail est terminé en utilisant
workInfo.getState().isFinished();. - Si ce n'est pas le cas, appelez la méthode
showWorkInProgress(), qui masque et affiche les vues appropriées. - S'il est terminé, appelez la méthode
showWorkFinished(), qui masque et affiche les vues appropriées.
Voici le code :
BlurActivity.java
// Show work status, added in onCreate()
mViewModel.getOutputWorkInfo().observe(this, listOfWorkInfos -> {
// If there are no matching work info, do nothing
if (listOfWorkInfos == null || listOfWorkInfos.isEmpty()) {
return;
}
// We only care about the first output status.
// Every continuation has only one worker tagged TAG_OUTPUT
WorkInfo workInfo = listOfWorkInfos.get(0);
boolean finished = workInfo.getState().isFinished();
if (!finished) {
showWorkInProgress();
} else {
showWorkFinished();
}
});
Étape 4 : Exécutez votre application
Exécutez votre application. Elle devrait se compiler et s'exécuter, et afficher à présent une barre de progression ainsi que le bouton d'annulation lorsqu'elle s'exécute :

Chaque WorkInfo possède également une méthode getOutputData qui vous permet d'obtenir l'objet Data de sortie avec l'image finale enregistrée. Affichons à présent un bouton See File (Consulter le fichier) lorsqu'une image floue est prête à être affichée.
Étape 1 : Créez mOutputUri
Créez une variable dans BlurViewModel pour l'URI final et fournissez des getters et des setters pour celle-ci. Pour transformer une String en Uri, vous pouvez utiliser la méthode uriOrNull.
Vous pouvez utiliser le code ci-dessous :
BlurViewModel.java
// New instance variable for the WorkInfo
private Uri mOutputUri;
// Add a getter and setter for mOutputUri
void setOutputUri(String outputImageUri) {
mOutputUri = uriOrNull(outputImageUri);
}
Uri getOutputUri() { return mOutputUri; }
Étape 2 : Créez le bouton "See File" (Consulter le fichier)
La mise en page activity_blur.xml contient déjà un bouton masqué. Il se trouve dans BlurActivity et est accessible via son composant View Binding en tant que seeFileButton.
Configurez l'écouteur de clic pour ce bouton. Il devrait obtenir l'URI, puis ouvrir une activité pour afficher cet URI. Vous pouvez utiliser le code ci-dessous :
BlurActivity.java
// Inside onCreate()
binding.seeFileButton.setOnClickListener(view -> {
Uri currentUri = mViewModel.getOutputUri();
if (currentUri != null) {
Intent actionView = new Intent(Intent.ACTION_VIEW, currentUri);
if (actionView.resolveActivity(getPackageManager()) != null) {
startActivity(actionView);
}
}
});
Étape 3 : Définissez l'URI et affichez le bouton
Il vous reste quelques réglages finaux à appliquer à l'observateur WorkInfo pour que cela fonctionne :
- Si
WorkInfoest terminé, obtenez les données de sortie à l'aide deworkInfo.getOutputData().. - Obtenez ensuite l'URI de sortie. Gardez à l'esprit qu'il est stocké avec la clé
Constants.KEY_IMAGE_URI. - Si l'URI n'est pas vide, il est enregistré correctement. Affichez
seeFileButtonet appelezsetOutputUrisur le modèle de vue avec l'URI.
BlurActivity.java
// Replace the observer code we added in previous steps with this one.
// Show work info, goes inside onCreate()
mViewModel.getOutputWorkInfo().observe(this, listOfWorkInfo -> {
// If there are no matching work info, do nothing
if (listOfWorkInfo == null || listOfWorkInfo.isEmpty()) {
return;
}
// We only care about the first output status.
// Every continuation has only one worker tagged TAG_OUTPUT
WorkInfo workInfo = listOfWorkInfo.get(0);
boolean finished = workInfo.getState().isFinished();
if (!finished) {
showWorkInProgress();
} else {
showWorkFinished();
Data outputData = workInfo.getOutputData();
String outputImageUri = outputData.getString(Constants.KEY_IMAGE_URI);
// If there is an output file show "See File" button
if (!TextUtils.isEmpty(outputImageUri)) {
mViewModel.setOutputUri(outputImageUri);
binding.seeFileButton.setVisibility(View.VISIBLE);
}
}
});
Étape 4 : Exécutez le code
Exécutez votre code. Vous devriez voir votre nouveau bouton cliquable See File (Consulter le fichier), qui vous redirige vers le fichier généré :


Vous avez ajouté le bouton Cancel Work (Annuler le travail). Ajoutons à présent le code pour qu'il effectue une action. Avec WorkManager, vous pouvez annuler votre travail par ID, par tag et par nom de chaîne unique.
Dans le cas présent, vous devez annuler le travail par nom de chaîne unique, car vous devez annuler tous les travaux de la chaîne, pas seulement d'une étape spécifique.
Étape 1 : Annulez le travail par son nom
Dans le modèle de vue, écrivez la méthode pour annuler le travail :
BlurViewModel.java
/**
* Cancel work using the work's unique name
*/
void cancelWork() {
mWorkManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME);
}
Étape 2 : Appelez la méthode d'annulation
Ensuite, associez le bouton cancelButton à l'opération cancelWork :
BlurActivity.java
// In onCreate()
// Hookup the Cancel button
binding.cancelButton.setOnClickListener(view -> mViewModel.cancelWork());
Étape 3 : Exécutez et annulez votre travail
Exécutez votre application. Elle devrait se compiler correctement. Commencez à flouter une image, puis cliquez sur le bouton d'annulation. Toute la chaîne est annulée.

Enfin et surtout, WorkManager est compatible avec Constraints. Pour Blur-O-Matic, vous allez utiliser la contrainte suivante : l'appareil doit être en train de se charger pendant l'enregistrement.
Étape 1 : Créez et ajoutez une contrainte de charge
Pour créer un objet Constraints, utilisez un Constraints.Builder. Définissez ensuite les contraintes souhaitées et ajoutez-les à la WorkRequest, comme indiqué ci-dessous :
BlurViewModel.java
// In the applyBlur method
// Create charging constraint
Constraints constraints = new Constraints.Builder()
.setRequiresCharging(true)
.build();
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.setConstraints(constraints) // This adds the Constraints
.addTag(TAG_OUTPUT)
.build();
continuation = continuation.then(save);
Étape 2 : Effectuez un test avec l'émulateur ou l'appareil
Vous pouvez à présent exécuter Blur-O-Matic. Si vous utilisez un appareil, vous pouvez le retirer ou le connecter. Sur un émulateur, vous pouvez modifier l'état de charge dans la fenêtre de commandes avancées :

Lorsque l'appareil ne se charge pas, il devrait rester dans l'état de chargement jusqu'à ce que vous le branchiez.

Félicitations ! Vous avez terminé l'application Blur-O-Matic. Vous en savez maintenant plus sur les points suivants :
- Ajout de WorkManager à votre projet
- Planification d'une
OneOffWorkRequest - Paramètres d'entrée et de sortie
- Enchaînement de travaux et de
WorkRequest - Attribution d'un nom à des chaînes
WorkRequestuniques - Ajout de tags à
WorkRequest - Affichage de
WorkInfodans l'interface utilisateur - Annulation de
WorkRequest - Ajout de contraintes à une
WorkRequest
Beau travail ! Pour voir l'état final du code et toutes les modifications, consultez la page :
Ou, si vous préférez, vous pouvez cloner l'atelier de programmation de WorkManager à partir de GitHub :
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
WorkManager est compatible avec bien d'autres fonctionnalités que nous n'avons pu traiter dans cet atelier de programmation, comme les travaux répétitifs, la bibliothèque Support de test, les requêtes de travail parallèles et les fusions d'entrées. Pour en savoir plus, consultez la documentation de WorkManager.

