1. Avant de commencer
Cet atelier de programmation porte sur WorkManager, une bibliothèque rétrocompatible, flexible et simple pour exécuter en arrière-plan des tâches différables. WorkManager
est le planificateur de tâches recommandé sur Android pour les tâches différables, dont l'exécution est garantie.
Conditions préalables
- Vous devez connaître StateFlow et ViewModel. Dans le cas contraire, consultez l'atelier de programmation intitulé "ViewModel et l'état dans Compose" (spécifiquement pour ViewModel et l'état) ou Lire et mettre à jour des données avec Room (spécifiquement pour le flux et StateFlow).
- Vous devez connaître les dépôts et l'injection de dépendances. Pour vous rafraîchir la mémoire, découvrez comment ajouter un dépôt et une injection manuelle de dépendances.
- Vous devez être en mesure d'implémenter des coroutines dans votre application.
Points abordés
- Comment ajouter WorkManager à votre projet
- Comment planifier une tâche simple
- Comment configurer les paramètres d'entrée et de sortie des workers
- Comment associer les workers les uns aux autres
Objectifs de l'atelier
- Modifier une application de démarrage pour utiliser WorkManager
- Implémenter une requête de travail pour flouter une image
- Implémenter un groupe de tâches en série en enchaînant des tâches
- Transmettre les données dans les tâches programmées et à partir de celles-ci
Ce dont vous avez besoin
- La dernière version stable d'Android Studio
- Une connexion Internet
2. Présentation de l'application
Les smartphones actuels sont presque trop bons pour prendre des photos. Le temps où un photographe pouvait prendre une photo intentionnellement floue d'un sujet 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 d'enregistrer les résultats 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.
L'écran contient des cases d'option pour sélectionner le niveau de flou de l'image. Cliquez sur le bouton Start (Démarrer) pour flouter et enregistrer l'image.
Pour le moment, l'application n'effectue aucun floutage et n'enregistre pas l'image finale.
Cet atelier de programmation se concentre sur l'ajout de WorkManager dans l'application, la création de workers pour nettoyer les fichiers temporaires générés lors du floutage d'une image, le floutage d'une photo et l'enregistrement d'une copie finale de la photo que vous pouvez afficher lorsque vous cliquez sur le bouton See File (Voir le fichier). Vous apprendrez également à surveiller l'état du travail en arrière-plan et à mettre à jour l'UI de l'application en conséquence.
3. Explorer l'application de démarrage Blur-O-Matic
Télécharger le code de démarrage
Pour commencer, téléchargez le code de démarrage :
Vous pouvez également cloner le dépôt GitHub du code :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout starter
Vous pouvez parcourir le code de l'application Blur-o-matic dans ce dépôt GitHub.
Exécuter le code de démarrage
Pour vous familiariser avec le code de démarrage, procédez comme suit :
- Dans Android Studio, ouvrez le projet contenant le code de démarrage.
- Exécutez l'application sur un appareil Android ou sur un émulateur.
L'écran contient des cases d'option permettant de sélectionner le niveau de flou de l'image. Lorsque vous cliquez sur le bouton Start (Démarrer), l'application floute et enregistre l'image.
Pour le moment, l'application n'applique aucun niveau de flou lorsque vous cliquez sur le bouton Start (Démarrer).
Tutoriel du code de démarrage
Dans cette tâche, vous allez vous familiariser avec la structure du projet. Les listes suivantes présentent les fichiers et dossiers importants du projet.
WorkerUtils
: méthodes pratiques que vous utiliserez par la suite pour afficher lesNotifications
et code permettant d'enregistrer un bitmap dans un fichier.BlurViewModel
: ce modèle de vue stocke l'état de l'application et interagit avec le dépôt.WorkManagerBluromaticRepository
: classe dans laquelle vous démarrez le travail en arrière-plan avec WorkManager.Constants
: classe statique comprenant quelques constantes que vous utiliserez au cours de l'atelier de programmation.BluromaticScreen
: contient des fonctions modulables pour l'UI et interagit avec leBlurViewModel
. Les fonctions modulables affichent l'image et incluent des cases d'option permettant de sélectionner le niveau de flou souhaité.
4. 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 l'application.
WorkManager est une bibliothèque 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, tel que l'exécution de tâches 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 concernant l'état du système
- Possibilité d'afficher facilement l'état des requêtes de travail dans l'UI de l'application
5. 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'application. Une fois qu'elles sont mises en file d'attente, leur exécution se poursuit, et ce que l'application continue à être exécutée ou non. Les tâches s'exécutent même si l'application est fermée ou si l'utilisateur revient à l'écran d'accueil.
Voici quelques exemples de tâches pour lesquelles WorkManager s'avère particulièrement utile :
- Requêtes d'actualités périodiques
- Application de filtres à une image et enregistrement de l'image
- Synchronisation périodique des données locales avec le réseau
WorkManager est une option parmi d'autres permettant d'exécuter une tâche à partir du thread principal, mais elle ne permet pas pour autant d'exécuter tous les types de tâches imaginables. Les coroutines sont une autre option dont nous avons parlé précédemment lors des ateliers de programmation.
Pour savoir quand utiliser WorkManager, consultez le guide sur les tâches en arrière-plan.
6. Ajouter WorkManager à votre application
WorkManager
nécessite la dépendance Gradle suivante. Elle est déjà incluse dans le fichier de compilation :
app/build.gradle.kts
dependencies {
// WorkManager dependency
implementation("androidx.work:work-runtime-ktx:2.8.1")
}
Vous devez utiliser la version stable la plus récente de work-runtime-ktx
dans votre application.
Si vous changez de version, veillez à cliquer sur Sync Now (Synchroniser) pour synchroniser votre projet avec les fichiers Gradle mis à jour.
7. Principes de base de WorkManager
Voici quelques-unes des classes de WorkManager que vous devez connaître :
Worker
/CoroutineWorker
: classe Worker qui exécute la tâche de manière synchrone sur un thread d'arrière-plan. Comme il est question d'une tâche asynchrone, nous pouvons utiliser CoroutineWorker, qui est compatible avec les coroutines Kotlin. Dans cette application, vous allez étendre la classe CoroutineWorker et remplacer la méthodedoWork()
. Cette méthode vous permet d'insérer le code correspondant au travail à effectuer en arrière-plan.WorkRequest
: cette classe représente une requête d'exécution d'une tâche. Un objetWorkRequest
vous permet de définir si le worker doit être exécuté une ou plusieurs fois. Des contraintes peuvent également être placées au niveau de l'objetWorkRequest
. Dans ce cas, certaines conditions doivent être remplies avant l'exécution de la tâche (par exemple, l'appareil doit être en charge avant de pouvoir commencer la tâche demandée). Vous transmettezCoroutineWorker
dans le cadre de la création de l'objetWorkRequest
.WorkManager
: cette classe planifie votreWorkRequest
et l'exécute. Elle planifie un objetWorkRequest
de manière à répartir la charge pesant sur les ressources système, tout en respectant les contraintes que vous spécifiez.
Dans votre cas, vous définissez une nouvelle classe BlurWorker
, qui contient le code permettant de flouter une image. Lorsque vous cliquez sur le bouton Start (Démarrer), WorkManager crée un objet WorkRequest
, puis le place en file d'attente.
8. Créer la classe BlurWorker
Au cours de cette étape, vous allez utiliser une image du dossier res/drawable
appelée android_cupcake.png
et y exécuter quelques fonctions en arrière-plan. Ces fonctions permettent de flouter l'image.
- Effectuez un clic droit sur le package
com.example.bluromatic.workers
dans le volet de votre projet Android, puis sélectionnez New -> Kotlin Class/File (Nouveau -> Classe/Fichier Kotlin). - Nommez la nouvelle classe Kotlin
BlurWorker
. Étendez-la à partir deCoroutineWorker
avec les paramètres de constructeur requis.
workers/BlurWorker.kt.
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
}
La classe BlurWorker
étend la classe CoroutineWorker
au lieu de la classe Worker
plus générale. L'implémentation doWork()
de la classe CoroutineWorker
est une fonction de suspension, qui lui permet d'exécuter du code asynchrone qu'un Worker
ne parvient pas à gérer. Comme indiqué dans le guide Exécuter des threads dans WorkManager, "CoroutineWorker est l'implémentation recommandée pour les utilisateurs Kotlin".
À ce stade, Android Studio trace une ligne rouge ondulée sous class BlurWorker
pour indiquer une erreur.
Si vous placez le curseur au-dessus du texte class BlurWorker
, l'IDE affiche un pop-up contenant des informations supplémentaires sur l'erreur.
Le message d'erreur indique que vous n'avez pas remplacé la méthode doWork()
de manière appropriée.
Dans la méthode doWork()
, écrivez le code permettant de flouter l'image de cupcake.
Pour corriger l'erreur et implémenter la méthode doWork()
, procédez comme suit :
- Placez le curseur dans le code de la classe en cliquant sur le texte "BlurWorker".
- Dans le menu Android Studio, sélectionnez Code > Override Methods (Code > Ignorer les méthodes).
- Dans le pop-up Override Members (Remplacer les membres), sélectionnez
doWork()
. - Cliquez sur OK.
- Juste avant la déclaration de classe, créez une variable nommée
TAG
et attribuez-lui la valeurBlurWorker
. Notez que cette variable n'est pas spécifiquement liée à la méthodedoWork()
, mais vous l'utiliserez ultérieurement dans les appels àLog()
.
workers/BlurWorker.kt
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
...
- Pour mieux voir à quel moment les tâches s'exécutent, vous devez utiliser la fonction
makeStatusNotification()
deWorkerUtil
. Cette fonction vous permet d'afficher facilement une bannière de notification en haut de l'écran.
Dans la méthode doWork()
, utilisez la fonction makeStatusNotification()
pour afficher une notification d'état et avertir l'utilisateur que le worker de floutage a démarré et qu'il est en train de flouter l'image.
workers/BlurWorker.kt
import com.example.bluromatic.R
...
override suspend fun doWork(): Result {
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
...
- Ajoutez un bloc de code
return try...catch
, qui correspond à l'opération de floutage des images.
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
} catch (throwable: Throwable) {
}
...
- Dans le bloc
try
, ajoutez un appel àResult.success()
. - Dans le bloc
catch
, ajoutez un appel àResult.failure()
.
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
Result.success()
} catch (throwable: Throwable) {
Result.failure()
}
...
- Dans le bloc
try
, créez une variable nomméepicture
et renseignez-la avec le bitmap renvoyé par l'appel de la méthodeBitmapFactory.decodeResource
()
, ainsi que par la transmission du package de ressources de l'application et l'ID de ressource de l'image de cupcake.
workers/BlurWorker.kt
...
return try {
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
Result.success()
...
- Floutez le bitmap en appelant la fonction
blurBitmap()
et en transmettant la variablepicture
et la valeur1
(un) pour le paramètreblurLevel
. - Enregistrez le résultat dans une nouvelle variable nommée
output
.
workers/BlurWorker.kt
...
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
val output = blurBitmap(picture, 1)
Result.success()
...
- Créez une variable
outputUri
et renseignez-la avec un appel à la fonctionwriteBitmapToFile()
. - Dans l'appel à
writeBitmapToFile()
, transmettez le contexte de l'application et la variableoutput
en tant qu'arguments.
workers/BlurWorker.kt.
...
val output = blurBitmap(picture, 1)
// Write bitmap to a temp file
val outputUri = writeBitmapToFile(applicationContext, output)
Result.success()
...
- Ajoutez le code permettant d'afficher un message de notification contenant la variable
outputUri
.
workers/BlurWorker.kt.
...
val outputUri = writeBitmapToFile(applicationContext, output)
makeStatusNotification(
"Output is $outputUri",
applicationContext
)
Result.success()
...
- Dans le bloc
catch
, journalisez un message d'erreur pour indiquer qu'une erreur s'est produite lors du floutage de l'image. L'appel deLog.e()
transmet la variableTAG
définie précédemment, un message approprié et l'exception générée.
workers/BlurWorker.kt
...
} catch (throwable: Throwable) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_applying_blur),
throwable
)
Result.failure()
}
...
Par défaut, un CoroutineWorker,
est exécuté en tant que Dispatchers.Default
, mais vous pouvez le modifier en appelant withContext()
et en transmettant le dispatcher souhaité.
- Créez un bloc
withContext()
. - Dans l'appel de
withContext()
, transmettezDispatchers.IO
afin que la fonction lambda s'exécute dans un pool de threads spécial pour pouvoir bloquer les opérations d'E/S. - Déplacez le code
return try...catch
précédemment écrit vers ce bloc.
...
return withContext(Dispatchers.IO) {
return try {
// ...
} catch (throwable: Throwable) {
// ...
}
}
...
Android Studio affiche l'erreur suivante, car vous ne pouvez pas appeler return
à partir d'une fonction lambda.
Pour corriger cette erreur, ajoutez un libellé comme dans le pop-up.
...
//return try {
return@withContext try {
...
Comme ce worker s'exécute très rapidement, il est recommandé d'ajouter un délai dans le code pour émuler une tâche plus lente.
- Dans le lambda
withContext()
, ajoutez un appel à la fonction utilitairedelay()
et transmettez la constanteDELAY_TIME_MILLIS
. Cet appel est exclusivement destiné à l'atelier de programmation afin d'assurer un délai entre les messages de notification.
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay
...
return withContext(Dispatchers.IO) {
// This is an utility function added to emulate slower work.
delay(DELAY_TIME_MILLIS)
val picture = BitmapFactory.decodeResource(
...
9. Mettre à jour WorkManagerBluromaticRepository
Le dépôt gère toutes les interactions avec WorkManager. Cette structure respecte le principe de séparation des tâches, qui est un schéma d'architecture Android recommandé.
- Dans le fichier
data/WorkManagerBluromaticRepository.kt
, dans la classeWorkManagerBluromaticRepository
, créez une variable privée nomméeworkManager
et stockez-y une instanceWorkManager
en appelantWorkManager.getInstance(context)
.
data/WorkManagerBluromaticRepository.kt
import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
// New code
private val workManager = WorkManager.getInstance(context)
...
Créer la requête de travail et la placer en file d'attente dans WorkManager
L'heure est venue de créer une requête de travail, ou WorkRequest
, et de demander à WorkManager de l'exécuter. Il existe deux types de WorkRequest
:
OneTimeWorkRequest
:WorkRequest
qui ne s'exécute qu'une seule fois.PeriodicWorkRequest
:WorkRequest
qui s'exécute de manière répétée dans un cycle.
L'image ne doit être floutée qu'une seule fois lorsque vous cliquez sur le bouton Start (Démarrer).
Cette tâche s'effectue dans la méthode applyBlur()
, que vous appelez lorsque vous cliquez sur le bouton Start (Démarrer).
Les étapes suivantes ont lieu dans la méthode applyBlur()
.
- Renseignez une nouvelle variable nommée
blurBuilder
en créant uneOneTimeWorkRequest
pour le worker de floutage et en appelant la fonction d'extensionOneTimeWorkRequestBuilder
de WorkManager KTX.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
- Démarrez la tâche en appelant la méthode
enqueue()
au niveau de l'objetworkManager
.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// Start the work
workManager.enqueue(blurBuilder.build())
}
- Exécutez l'application et consultez la notification qui s'affiche lorsque vous cliquez sur le bouton Start (Démarrer).
Pour le moment, l'image est floutée de la même manière, quelle que soit l'option sélectionnée. Lors des étapes suivantes, le niveau de flou change en fonction de l'option sélectionnée.
Pour vérifier que l'image a bien été floutée, vous pouvez ouvrir Device Explorer (Explorateur de l'appareil) dans Android Studio :
Accédez ensuite à data > data > com.example.bluromatic > files > blur_filter_outputs > <URI> et vérifiez que l'image de cupcake est bien floutée :
10. Données d'entrée et de sortie
Le floutage de l'élément image dans le répertoire de ressources est satisfaisant, mais pour que Blur-O-Matic soit vraiment l'application révolutionnaire de retouche d'images qu'elle prétend devenir, vous devez laisser l'utilisateur flouter l'image qu'il voit à l'écran, puis afficher le résultat flouté.
Pour ce faire, nous fournissons l'URI de l'image de cupcake affichée en entrée à notre WorkRequest
, puis nous utilisons la sortie de cette WorkRequest
pour afficher l'image floutée finale.
Les entrées et les sorties sont transmises vers et depuis un worker via un objet 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 à un worker en entrée ou en sortie à partir de la WorkRequest
.
À l'étape suivante, vous transmettrez l'URI à BlurWorker
en créant un objet de données d'entrée.
Créer un objet de données d'entrée
- Dans le fichier
data/WorkManagerBluromaticRepository.kt
, au sein de la classeWorkManagerBluromaticRepository
, créez une variable privée nomméeimageUri
. - Renseignez la variable avec l'URI de l'image en appelant la méthode de contexte
getImageUri()
.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
private var imageUri: Uri = context.getImageUri() // <- Add this
private val workManager = WorkManager.getInstance(context)
...
Le code de l'application contient la fonction d'assistance createInputDataForWorkRequest()
qui permet de créer des objets de données d'entrée.
data/WorkManagerBluromaticRepository.kt
// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
val builder = Data.Builder()
builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
return builder.build()
}
La fonction d'assistance crée d'abord un objet Data.Builder
. Elle y insère ensuite l'imageUri
et le blurLevel
sous la forme de paires clé/valeur. Puis, un objet de données est créé et renvoyé lorsque return builder.build()
est appelé.
- Pour définir l'objet de données d'entrée pour une requête de travail, appelez la méthode
blurBuilder.setInputData()
. Pour créer et transmettre l'objet de données en une seule étape, vous pouvez appeler la fonction d'assistancecreateInputDataForWorkRequest()
en tant qu'argument. Pour l'appel de la fonctioncreateInputDataForWorkRequest()
, transmettez les variablesblurLevel
etimageUri
.
data/WorkManagerBluromaticRepository.kt
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// New code for input data object
blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))
workManager.enqueue(blurBuilder.build())
}
Accéder à l'objet de données d'entrée
Nous allons maintenant mettre à jour la méthode doWork()
de la classe BlurWorker
pour obtenir l'URI et le niveau de flou transmis par l'objet de données d'entrée. Si aucune valeur n'a été spécifiée pour blurLevel
, la valeur par défaut est 1
.
Dans la méthode doWork()
:
- Créez une variable nommée
resourceUri
et renseignez-la en appelantinputData.getString()
et en transmettant la constanteKEY_IMAGE_URI
qui a été utilisée comme clé lors de la création de l'objet de données d'entrée.
val resourceUri = inputData.getString(KEY_IMAGE_URI)
- Créez une variable nommée
blurLevel
. Renseignez la variable en appelantinputData.getInt()
et en transmettant la constanteBLUR_LEVEL
qui a été utilisée comme clé lors de la création de l'objet de données d'entrée. Si cette paire clé/valeur n'a pas été créée, fournissez la valeur par défaut1
(un).
workers/BlurWorker.kt.
import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {
// ADD THESE LINES
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)
// ... rest of doWork()
}
Avec l'URI, floutons maintenant l'image de cupcake à l'écran.
- Vérifiez que la variable
resourceUri
est renseignée. Si ce n'est pas le cas, le code devrait générer une exception. Le code ci-dessous utilise l'instructionrequire()
qui génère une exceptionIllegalArgumentException
si le premier argument est "false".
workers/BlurWorker.kt
return@withContext try {
// NEW code
require(!resourceUri.isNullOrBlank()) {
val errorMessage =
applicationContext.resources.getString(R.string.invalid_input_uri)
Log.e(TAG, errorMessage)
errorMessage
}
Étant donné que la source de l'image est transmise en tant qu'URI, nous avons besoin d'un objet ContentResolver pour lire le contenu vers lequel renvoie l'URI.
- Ajoutez un objet
contentResolver
à la valeurapplicationContext
.
workers/BlurWorker.kt.
...
require(!resourceUri.isNullOrBlank()) {
// ...
}
val resolver = applicationContext.contentResolver
...
- Étant donné que la source de l'image est maintenant transmise dans l'URI, utilisez
BitmapFactory.decodeStream()
au lieu deBitmapFactory.decodeResource()
pour créer l'objet Bitmap.
workers/BlurWorker.kt.
import android.net.Uri
...
// val picture = BitmapFactory.decodeResource(
// applicationContext.resources,
// R.drawable.android_cupcake
// )
val resolver = applicationContext.contentResolver
val picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
- Transmettez la variable
blurLevel
dans l'appel de la fonctionblurBitmap()
.
workers/BlurWorker.kt
//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)
Créer un objet de données de sortie
Vous en avez terminé avec ce worker et pouvez renvoyer l'URI de sortie en tant qu'objet de données de sortie dans Result.success()
. Fournir l'URI de sortie en tant qu'objet de données de sortie permet aux autres workers d'y accéder facilement pour d'autres opérations. Cette approche sera utile dans la section suivante, lorsque vous créerez une chaîne de workers.
Voici l'approche à suivre :
- Avant le code
Result.success()
, créez une variable nomméeoutputData
. - Renseignez cette variable en appelant la fonction
workDataOf()
et utilisez la constanteKEY_IMAGE_URI
pour la clé et la variableoutputUri
comme valeur. La fonctionworkDataOf()
crée un objet de données à partir de la paire clé/valeur transmise.
workers/BlurWorker.kt.
import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
- Mettez à jour le code
Result.success()
pour utiliser ce nouvel objet de données comme argument.
workers/BlurWorker.kt
//Result.success()
Result.success(outputData)
- Supprimez le code qui affiche la notification. Il n'est plus nécessaire, car l'objet de données de sortie utilise désormais l'URI.
workers/BlurWorker.kt
// REMOVE the following notification code
//makeStatusNotification(
// "Output is $outputUri",
// applicationContext
//)
Exécuter votre application
À ce stade, lorsque vous exécutez votre application, la compilation devrait avoir lieu. Vous pouvez voir l'image floutée dans Device Explorer (Explorateur de l'appareil), mais pas encore à l'écran.
Notez que vous devrez peut-être lancer la synchronisation pour afficher les images :
Beau travail ! Vous avez flouté une image d'entrée en utilisant WorkManager
.
11. Créer votre chaîne
Pour l'instant, vous n'effectuez qu'une seule tâche : flouter l'image. Bien que cette première réalisation soit essentielle, certaines fonctionnalités de base doivent encore être ajoutées dans l'application :
- L'application ne nettoie pas les fichiers temporaires.
- L'application n'enregistre pas l'image dans un fichier permanent.
- L'application floute toujours l'image de la même manière.
Vous pouvez 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.
Dans cette section, vous allez créer une chaîne de travail qui se présente comme suit :
Les cases représentent les WorkRequest
s.
Une chaîne de travail présente également l'avantage d'accepter les entrées et de générer des sorties. La sortie d'une WorkRequest
devient l'entrée de la WorkRequest
suivante dans la chaîne.
Vous disposez déjà d'un CoroutineWorker
pour flouter une image, mais vous avez également besoin d'un CoroutineWorker
qui nettoie les fichiers temporaires et d'un CoroutineWorker
qui enregistre l'image de manière définitive.
Créer un worker de nettoyage
La classe CleanupWorker
supprime les fichiers temporaires.
- Effectuez un clic droit sur le package
com.example.bluromatic.workers
dans le volet de votre projet Android, puis sélectionnez New -> Kotlin Class/File (Nouveau -> Classe/Fichier Kotlin). - Nommez la nouvelle classe Kotlin
CleanupWorker
. - Copiez le code de CleanupWorker.kt, comme illustré dans l'exemple de code suivant.
Étant donné que la manipulation des fichiers n'entre pas dans le cadre de cet atelier de programmation, vous pouvez copier le code suivant pour CleanupWorker
.
workers/CleanupWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
/**
* Cleans up temporary files generated during blurring process
*/
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
/** 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
*/
makeStatusNotification(
applicationContext.resources.getString(R.string.cleaning_up_files),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
return@withContext try {
val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
if (outputDirectory.exists()) {
val entries = outputDirectory.listFiles()
if (entries != null) {
for (entry in entries) {
val name = entry.name
if (name.isNotEmpty() && name.endsWith(".png")) {
val deleted = entry.delete()
Log.i(TAG, "Deleted $name - $deleted")
}
}
}
}
Result.success()
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_cleaning_file),
exception
)
Result.failure()
}
}
}
}
Créer un worker d'enregistrement de l'image dans un fichier
La classe SaveImageToFileWorker
enregistre le fichier temporaire dans un fichier permanent.
SaveImageToFileWorker
accepte les entrées et les sorties. L'entrée est une String
de l'URI de l'image floutée temporairement. Elle est stockée avec la clé KEY_IMAGE_URI
. La sortie est une String
de l'URI de l'image floutée enregistrée. Elle est stockée avec la clé KEY_IMAGE_URI
.
- Effectuez un clic droit sur le package
com.example.bluromatic.workers
dans le volet de votre projet Android, puis sélectionnez New -> Kotlin Class/File (Nouveau -> Classe/Fichier Kotlin). - Nommez la nouvelle classe Kotlin
SaveImageToFileWorker
. - Copiez le code de SaveImageToFileWorker.kt, comme indiqué dans l'exemple de code suivant.
Étant donné que la manipulation des fichiers n'entre pas dans le cadre de cet atelier de programmation, vous pouvez copier le code suivant pour SaveImageToFileWorker
. Dans le code fourni, notez que les valeurs resourceUri
et output
sont récupérées et stockées avec la clé KEY_IMAGE_URI
. Ce processus est très semblable au code que vous avez écrit précédemment pour les objets de données d'entrée et de sortie.
workers/SaveImageToFileWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
/**
* Saves the image to a permanent file
*/
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
private val title = "Blurred Image"
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z",
Locale.getDefault()
)
override suspend fun doWork(): Result {
// 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
makeStatusNotification(
applicationContext.resources.getString(R.string.saving_image),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
val resolver = applicationContext.contentResolver
return@withContext try {
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
val imageUrl = MediaStore.Images.Media.insertImage(
resolver, bitmap, title, dateFormatter.format(Date())
)
if (!imageUrl.isNullOrEmpty()) {
val output = workDataOf(KEY_IMAGE_URI to imageUrl)
Result.success(output)
} else {
Log.e(
TAG,
applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
)
Result.failure()
}
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_saving_image),
exception
)
Result.failure()
}
}
}
}
Créer une chaîne de travail
Actuellement, le code ne crée et n'exécute qu'une seule WorkRequest
.
Au cours de cette étape, vous allez modifier le code pour créer et exécuter une chaîne de requêtes de travail au lieu d'une seule requête de floutage d'image.
Dans la chaîne de requêtes de travail, la première requête consistera à nettoyer les fichiers temporaires.
- Au lieu d'appeler
OneTimeWorkRequestBuilder
, appelezworkManager.beginWith()
.
L'appel de la méthode beginWith()
renvoie un objet WorkContinuation
et crée la première requête d'une chaîne de WorkRequest
s comme point de départ.
data/WorkManagerBluromaticRepository.kt
import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
override fun applyBlur(blurLevel: Int) {
// Add WorkRequest to Cleanup temporary images
var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))
// Add WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...
Pour ajouter des requêtes à cette chaîne de requêtes de travail, appelez la méthode then()
et transmettez un objet WorkRequest
.
- Supprimez l'appel de
workManager.enqueue(blurBuilder.build())
, qui mettait une seule requête de travail en file d'attente. - Ajoutez la requête de travail suivante à la chaîne en appelant la méthode
.then()
.
data/WorkManagerBluromaticRepository.kt
...
//workManager.enqueue(blurBuilder.build())
// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
- Créez une requête de travail pour enregistrer l'image, puis ajoutez-la à la chaîne.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.SaveImageToFileWorker
...
continuation = continuation.then(blurBuilder.build())
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.build()
continuation = continuation.then(save)
...
- Pour commencer le travail, appelez la méthode
enqueue()
au niveau de l'objet de continuation.
data/WorkManagerBluromaticRepository.kt
...
continuation = continuation.then(save)
// Start the work
continuation.enqueue()
...
Ce code génère et exécute la chaîne de requêtes de travail suivante : une WorkRequest
CleanupWorker
suivie d'une WorkRequest
BlurWorker
suivie d'une WorkRequest
SaveImageToFileWorker
.
- Exécutez l'application.
Vous pouvez maintenant cliquer sur Start (Démarrer) et voir les notifications lorsque les différents workers s'exécutent. Vous continuez à voir l'image floutée dans Device Explorer (Explorateur de l'appareil). Dans une section à venir, vous ajouterez un bouton afin que les utilisateurs puissent voir l'image floutée sur l'appareil.
Dans les captures d'écran suivantes, le message de notification affiche le worker en cours d'exécution.
Notez que le dossier de sortie contient plusieurs images floutées : les images qui sont dans des étapes floues intermédiaires et l'image finale qui affiche le niveau de flou sélectionné.
Bravo ! Vous pouvez maintenant nettoyer les fichiers temporaires, flouter une image et l'enregistrer :
12. Télécharger le code de solution
Pour télécharger le code de cet atelier de programmation, utilisez les commandes suivantes :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout intermediate
Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.
Si vous le souhaitez, vous pouvez consulter le code de solution de cet atelier de programmation sur GitHub.
13. Conclusion
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
OneTimeWorkRequest
- Paramètres d'entrée et de sortie
- Enchaînement de tâches et de
WorkRequest
WorkManager est compatible avec bien d'autres fonctionnalités que nous n'avons pu traiter dans cet atelier de programmation, comme les tâches répétitives, 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 Planifier des tâches avec WorkManager.