IV. AVAT- Exemple 1▲
IV-A. Le projet▲
Nous nous proposons de créer une application Android avec l'unique vue suivante :
- l'application lance N [1] tâches asynchrones qui génèrent chacune un nombre aléatoire dans un intervalle [a,b] [2]. Avec une probabilité d'1/3, la tâche peut générer une exception.
- le bouton [4] lance une action unique qui à son tour va lancer les N tâches asynchrones ;
- afin de pouvoir annuler les N tâches lancées, on leur impose un délai d'attente avant de rendre leur réponse, exprimé en millisecondes [3]. Sur l'exemple, le délai de 5 secondes fait que les N tâches sont lancées et toutes attendent 5 secondes avant de faire le travail qui leur est demandé ;
- en [5] remontent les informations produites par les tâches, nombre ou exception ;
- la ligne [6] récapitule ce qui a été reçu. Il faut vérifier qu'on a bien reçu les N réponses des N tâches asynchrones et accessoirement on pourra vérifier la somme des nombres reçus.
Dès que les N tâches ont été lancées, un bouton [Annuler] [6] remplace le bouton [Exécuter]. Il permet d'annuler les tâches lancées :
Outre les résultats affichés sur la vue, on vérifiera les logs. Par exemple pour 2 tâches on a les logs suivants :
- ligne 1 : l'action [Action_01] a été lancée ;
- ligne 2 : la tâche [Action_01]Task_01-00 a été lancée ;
- ligne 3 : la tâche [Action_01]Task_01-01 a été lancée ;
- ligne 4 : la tâche [Action_01]Task_01-01 a généré une information ;
- ligne 5 : l'action [Action_01] a généré une information ;
- ligne 6 : la tâche [Action_01]Task_01-01 est terminée ;
- ligne 7 : la tâche [Action_01]Task_01-00 a généré une information ;
- ligne 8 : l'action [Action_01] a généré une information ;
- ligne 9 : la tâche [Action_01]Task_01-00 est terminée ;
- ligne 10 : l'action [Action_01] est terminée ;
Dans le cas d'annulation de ces deux tâches, on a les logs suivants :
- ligne 1 : l'action [Action_01] a été lancée ;
- ligne 2 : la tâche [Action_01]Task_01-00 a été lancée ;
- ligne 3 : la tâche [Action_01]Task_01-01 a été lancée ;
- ligne 4 : la tâche [Action_01]Task_01-01 a été annulée ;
- ligne 5 : la tâche [Action_01]Task_01-00 a été annulée ;
- ligne 6 : l'action [Action_01] a été annulée ;
IV-B. L'architecture▲
L'architecture de l'application est la suivante :
Il n'y a pas de couche [métier].
IV-C. Le projet Eclipse▲
- en [1], le projet [android-avat-exemple1] est un projet Maven ayant une dépendance sur le projet [android-avat] ;
- en [2], les sources du projet :
- le package [istia.st.avat-exemples.vues] rassemble les vues du projet, ici une vue ;
- le package [istia.st.avat-exemples.actions] rassemble les actions du projet, ici une action ;
- le package [istia.st.avat-exemples.tasks] rassemble les tâches du projet, ici une tâche ;
- le package [istia.st.avat-exemples.activity] contient l'activité Android [MainActivity], la fabrique d'objets [Factory], une classe de configuration [Config] et une classe d'exception [ClientException].
IV-D. Le manifeste de l'application Android▲
Le fichier [AndroidManifest.xml] du projet est le suivant :
C'est un fichier classique pour toute personne ayant développé des applications Android. Notons simplement la ligne 19 qui permet de ne pas voir apparaître un clavier logiciel lors de l'affichage de la vue.
IV-E. Les dépendances Maven▲
Le fichier [pom.xml] du projet Android est le suivant :
Le projet a deux dépendances :
- lignes 18-23 : la dépendance sur Android ;
- lignes 24-29 : la dépendance sur le projet AVAT ;
Lignes 33-52, on trouve la définition des plugins nécessaires à la construction du projet Maven Android. Nous ne nous attarderons pas dessus et nous ne les mentionnerons plus. Le lecteur les trouvera dans les codes source distribués avec ce document.
IV-F. L'activité Android▲
L'activité Android [MainActivity] est la suivante :
- ligne 5 : l'activité est de type [FragmentActivity] ;
- ligne 8 : la fabrique d'objets du projet. C'est l'activité qui l'instancie ;
- lignes 14-15 : mise en place d'un sablier. Ceci doit être fait avant que l'activité ne définisse son interface visuelle, ligne 17. Le sablier s'installe en haut et à droite de la tablette. Il a la forme suivante :
- ligne 17 : l'interface visuelle est construite à partir du fichier [main.xml]. Celui-ci est le suivant :
- ligne 1 : on a un unique composant, un [FrameLayout] avec rien dedans ;
- ligne 3 : on notera son identifiant.
Retour au code de l'activité :
- ligne 19 : la fabrique d'objets est instanciée avec un constructeur à deux paramètres :
- le 1er paramètre est l'activité elle-même ;
- le second est une instance de la classe de configuration de l'application ;
La classe [Config] est la suivante :
Elle définit le mode verbeux ou non de l'application. En mode débogage, on mettra verbose à vrai.
Retour au code de l'activité :
- ligne 21 : une instance de la première vue à afficher est demandée à la fabrique d'objets à l'aide de trois paramètres :
- n° de l'objet demandé,
- le boss de l'objet demandé s'il y en a un. Une vue n'a pas de boss,
- l'identifiant de l'objet demandé ;
- lignes 23-25 : la vue est affichée. Ligne 24, la vue vient remplacer l'objet identifiée par container dans la vue actuelle. On se rappelle que cette vue était pour l'instant celle d'un [FrameLayout] nommé container. Ce [FrameLayout] est alors remplacé par la vue Vue_01.
IV-G. La fabrique d'objets▲
La fabrique d'objets est centrale à une application AVAT. Nous y reviendrons à plusieurs reprises. Pour l'instant, montrons le code d'instanciation de la vue " Vue_01 " :
- ligne 5 : la fabrique implémente l'interface [IFactory] ;
- lignes 8-11 : les constantes entières qui identifient les objets construits par la fabrique ;
- lignes 13-24 : les singletons de la fabrique. Ici, la classe de configuration (lignes 15-16), l'unique vue de l'application (ligne 19), l'activité Android (ligne 22) ;
- lignes 27-33 : le constructeur de la fabrique. Il reçoit deux paramètres :
- l'activité Android de l'application,
- la classe de configuration de l'application ;
- lignes 36-48 : la sélection de l'objet à créer ou recycler sera faite à l'aide d'un switch qui peut être assez imposant. On peut sans doute l'éviter ;
- ligne 50 : le code d'instanciation de la vue " Vue_01 ". On reçoit un paramètre : la référence du boss de la vue qui va être créée (l'activité a passé un pointeur null) ;
- ligne 52 : la vue n'est pas régénérée si elle l'a déjà été ;
- ligne 54 : instanciation de la vue ;
- lignes 55-66 : on lui injecte la fabrique (ligne 56), une équipe (lignes 58-60), l'activité Android (ligne 62), le mode verbeux de l'application (ligne 64) et un identifiant (66).
IV-H. La classe d'exception▲
Une application Java définit souvent ses propres exceptions. Ici, ce sera la classe [ClientException] suivante :
- ligne 3, l'exception dérive de [RuntimeException]. C'est donc une exception non contrôlée par le compilateur. Il n'y a pas obligation de la gérer par un try / catch ni de la déclarer par les mots clés throws ClientException.
IV-I. La vue [Vue_01]▲
La vue interagit avec l'utilisateur :
Ses composants sont les suivants :
N° |
Id |
Type |
Rôle |
1 |
edtNbAleas |
EditText |
nombre de nombres aléatoires à générer dans l'intervalle entier [a,b] |
2 |
edtA |
EditText |
valeur de a |
2 |
edtB |
EditText |
valeur de b |
3 |
edtSleepTime |
EditText |
durée d'attente des threads asynchrones |
4 |
btnExécuter |
Button |
lance la génération des nombres |
5 |
ListView |
lstReponses |
liste des nombres générés dans l'ordre inverse de leur génération. On voit d'abord le dernier généré ; |
6 |
récapitulatif de la génération |
Lorsque les tâches ont été lancées, l'UI change :
N° |
Id |
Type |
Rôle |
6 |
btnAnnuler |
Button |
annule la génération des nombres |
Le code de la vue est le suivant :
On aura dans nos exemples toujours la même structure de vue :
- ligne 40 : la méthode [onActivityCreated] pour déclarer les différents éléments de l'interface graphique ainsi que les gestionnaires d'événements ;
- ligne 88 : la méthode associée au clic du bouton [Exécuter] ;
- ligne 93 : la méthode qui vérifie la validité des données. Elle sera présente mais non détaillée. Le lecteur la trouvera dans les codes source livrés avec ce document ;
- ligne 98 : la méthode [notifyEndOfTasks] exécutée par la classe parent [Vue] lorsque toutes les actions lancées ont été exécutées ;
- ligne 103 : la méthode [notifyEvent] qui voit passer toutes les notifications envoyées par les actions lancées ;
- lignes 108 et 113 : les méthodes qui gèrent le début et la fin de l'attente. Nous les détaillerons ici mais plus dans les exemples qui suivront.
IV-I-1. Méthode [doExécuter]▲
C'est la méthode qui va lancer l'action de génération des nombres. Son code est le suivant :
- lignes 3-5 : la vue comporte des composants [TextView] d'affichage d'erreurs de saisie. Ils n'ont pas été présentés ;
- lignes 7-9 : si les données saisies ne sont pas valides, on retourne à l'UI (ligne 8) ;
- ligne 13 : on demande à la fabrique l'action [Action_01]. Dans la fabrique, le code d'instanciation de l'action est le suivant :
- l'action est créée à chaque fois qu'on en demande une référence. Ca peut être discuté. Un singleton pourrait peut-être faire l'affaire. Il faut voir dans quelles conditions il est appelé ;
- ligne 2 : l'action reçoit deux paramètres pour se construire : une référence sur son boss (une vue) et un identifiant ;
- ligne 4 : l'action est instanciée. On y injecte ensuite : son boss (ligne 7), une équipe (lignes 9-11), un identifiant de travailleur et de boss (lignes 13-15), la fabrique (ligne 17), l'état de verbosité de l'application (ligne 19) ;
Retour au code de la méthode [doExécuter] :
- ligne 15 : on passe l'action à l'état démarré. Elle n'est pas encore démarrée mais elle va bientôt l'être. On pourrait déplacer ce code dans l'action ;
- ligne 17 : on modifie l'UI pour signaler l'attente de quelque chose ;
- ligne 22 : l'action [Action_01] est lancée. On lui passe les informations suivantes : le nombre [nbAleas] de nombres aléatoires à générer, l'intervalle [a,b] de génération, la durée [sleepTime] d'attente des threads asynchrones. Un inconvénient d'AVAT est son utilisation assez fréquente d'objets non typés. Le compilateur ne peut alors aider le développeur dans l'écriture de la ligne 22. Le risque est alors de ne pas envoyer le bon nombre de paramètres ou pas dans l'ordre attendu ;
- ligne 25 : une fois l'action lancée, on signale à la classe parent [Vue] qu'elle doit surveiller la fin des actions lancées, ici une seule.
Les méthodes [beginWaiting] et [cancelWaiting] sont les suivantes :
Une fois que la vue a lancé des actions, elle voit passer leurs notifications dans la méthode [notifyEvent] suivante :
- ligne 4 : la notification doit être passée à la classe parent, en fait seulement les notifications [WORK_STARTED] et [WORK_TERMINATED]. A partir de ces deux notifications, la classe parent pourra envoyer le signal [endOfTasks] à sa fille ;
- ligne 6 : la vue gèrera en général la notification [WORK_INFO]. C'est le cas ici. Les tâches vont remonter deux types d'information :
- une exception en moyenne une fois sur 3,
- un nombre aléatoire dans l'intervalle [a,b] ;
Dans les deux cas, la notification est affichée dans la liste des réponses (lignes 9 et 12) et les compteurs respectifs sont incrémentés ;
- ligne 17 : l'action [Action_01] va envoyer la somme des nombres aléatoires générés dans sa notification [WORK_TERMINATED]. On gère ce cas ;
- ligne 23 : la liste des réponses est rafraîchie.
Lorsque les tâches sont terminées, la vue va recevoir la notification [endOfTasks] de sa classe parent. La méthode [notifyEndOfTasks] va être exécutée :
Les résultats ayant déjà été affichés, on se contente de modifier l'UI afin de montrer que l'attente est terminée.
IV-I-2. Méthode d'annulation des tâches▲
IV-J. L'action [Action_01]▲
Le code de l'action [Action_01] est le suivant :
- ligne 12 : l'action fait son travail dans la méthode [doWork]. Elle reçoit quatre paramètres (nbAleas, a, b, sleepTime) ;
- ligne 14 : elle utilise le premier paramètre, le nombre de nombres aléatoires à générer ;
- ligne 15 : elle utilise une tâche asynchrone pour chaque nombre aléatoire ;
- ligne 17 : une tâche est demandée à la fabrique d'objets. On lui passe deux informations :
- une référence sur le boss, l'action (this),
- un identifiant ;
Le code d'instanciation de la tâche dans la fabrique est le suivant :
- la fabrique génère une tâche asynchrone à chaque requête. De nouveau, on pourrait regarder si le singleton est possible et s'il a un intérêt ;
- ligne 4 : la tâche asynchrone est instanciée ;
- lignes 7-12 : on lui injecte son boss (ligne 7), son identifiant (ligne 10) et la fabrique (12) ;
- ligne 8 : la tâche n'est pas un [IBoss]. Elle n'a donc pas d'équipe.
Retour au code de la méthode [doWork] :
- lignes 18-26 : la tâche est lancée. Elle sera exécutée dans un thread asynchrone différent de celui de l'UI ;
- ligne 21 : si le pool de threads disponibles est vide, on aura une exception ;
- ligne 23 : on passe la tâche à l'état TERMINATED. En effet, elle a eu le temps de passer à l'état STARTED ;
- ligne 25 : on remonte l'exception au boss (la vue) ;
L'action est un [IBoss]. Elle gère une équipe de [Task]. Elle voit donc passer des notifications dans [notifyEvent] :
- ligne 4 : la notification est passée au parent qui gère les notifications [WORK_STARTED] et [WORK_TERMINATED] ;
- ligne 6 : l'action gère les notifications [WORK_INFO] que lui envoient les tâches qu'elle a lancées ;
- ligne 7 : la notification [WORK_INFO] est remontée telle quelle au boss de l'action (la vue). On notera que l'action se substitue à la tâche (paramètre this) ;
- lignes 9-10 : on récupère l'information transportée par la notification [WORK_INFO] et on l'ajoute à la liste des nombres aléatoires déjà générés par les tâches.
Les tâches vont se terminer et l'action va recevoir la notification [endOfTasks] :
- lignes 4-7 : la somme des nombres aléatoires mémorisés est calculée ;
- ligne 9 : la notification [WORK_TERMINATED] est envoyée au boss de l'action (la vue) accompagnée de cette somme.
IV-K. La tâche asynchrone [Task_01]▲
Le code de la tâche asynchrone [Task_01] est le suivant :
- ligne 17 : la méthode exécutée en tâche de fond. C'est elle qui fait le travail. Elle exécute normalement une méthode de la couche [métier]. Pas ici ;
- ligne 8 : l'information produite par la tâche. Ici, une exception ou un nombre aléatoire ;
- lignes 11-14 : la méthode [onPreExecute] utilisée pour envoyer la notification [WORK_STARTED] au boss (une action). Elle s'exécute dans le thread de l'UI ;
- lignes 23-28 : la méthode [onPostExecute] utilisée pour envoyer les notifications [WORK_INFO] et [WORK_STARTED] au boss (une action). Elle s'exécute dans le thread de l'UI ;
- ligne 25 : l'information produite par la méthode [doInBackground] est ici envoyée au boss (une action) ;
La méthode [doInBackGround] est la suivante :
- ligne 2 : on attend les paramètres [a, b, sleepTime] dans l'ordre ;
- lignes 4-7 : on vérifie le nombre de paramètres ;
- lignes 9-19 : on récupère les paramètres en les typant ;
- lignes 21-26 : le thread s'arrête sleepTime millisecondes. Il va alors perdre le processeur. Une autre tâche va en bénéficier ;
- lignes 28-29 : on génère un nombre aléatoire entier dans l'intervalle [0-2] ;
- lignes 30-33 : si le nombre généré est 0, l'information produite par la tâche sera une exception. Comme le nombre 0 a une chance sur 3 d'être généré, on génère donc une exception avec une chance sur 3. Si la vue initiale a demandé un grand nombre de nombres aléatoires, on devrait vérifier qu'environ le tiers des réponses sont des exceptions ;
- lignes 35-37 : on vérifie la validité de l'intervalle [a, b] ;
- ligne 39 : on génère un nombre aléatoire dans l'intervalle [a, b].
IV-L. Les tests▲
Le lecteur est invité à tester l'application. Voici quelques conseils :
Pour voir les logs, mettre un petit nombre pour N. Les logs sont trouvés dans l'onglet [LogCat] d'Eclipse :
Nous avons déjà commenté ces logs. Par exemple pour 2 tâches on a les logs suivants :
- ligne 1 : l'action [Action_01] a été lancée ;
- ligne 2 : la tâche [Action_01]Task_01-00 a été lancée ;
- ligne 3 : la tâche [Action_01]Task_01-01 a été lancée ;
- ligne 4 : la tâche [Action_01]Task_01-01 a généré une information ;
- ligne 5 : l'action [Action_01] a généré une information ;
- ligne 6 : la tâche [Action_01]Task_01-01 est terminée ;
- ligne 7 : la tâche [Action_01]Task_01-00 a généré une information ;
- ligne 8 : l'action [Action_01] a généré une information ;
- ligne 9 : la tâche [Action_01]Task_01-00 est terminée ;
- ligne 10 : l'action [Action_01] est terminée ;
Dans le cas d'annulation de ces deux tâches, on a les logs suivants :
- ligne 1 : l'action [Action_01] a été lancée ;
- ligne 2 : la tâche [Action_01]Task_01-00 a été lancée ;
- ligne 3 : la tâche [Action_01]Task_01-01 a été lancée ;
- ligne 4 : la tâche [Action_01]Task_01-01 a été annulée ;
- ligne 5 : la tâche [Action_01]Task_01-00 a été annulée ;
- ligne 6 : l'action [Action_01] a été annulée ;
- pour annuler les tâches, on mettra un délai d'attente de plusieurs secondes en [3] ;
- pour épuiser le pool de threads, on mettra 200 en [1].