Android pour les développeurs J2EE

Un modèle asynchrone pour les clients web


précédentsommairesuivant

IV. AVAT- Exemple 1

IV-A. Le projet

Nous nous proposons de créer une application Android avec l'unique vue suivante :

Image non disponible
  • 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 :

Image non disponible

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 :

 
CacherSélectionnez
  • 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 :

 
CacherSélectionnez
  • 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 :

Image non disponible

Il n'y a pas de couche [métier].

IV-C. Le projet Eclipse

Image non disponible
  • 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 :

 
CacherSélectionnez

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 :

 
CacherSélectionnez

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

Image non disponible

L'activité Android [MainActivity] est la suivante :

 
CacherSélectionnez
  • 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 : Image non disponible
  • ligne 17 : l'interface visuelle est construite à partir du fichier [main.xml]. Celui-ci est le suivant :
 
CacherSélectionnez
  • 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 :

 
CacherSélectionnez

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

Image non disponible

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 " :

 
CacherSélectionnez
  • 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

Image non disponible

Une application Java définit souvent ses propres exceptions. Ici, ce sera la classe [ClientException] suivante :

 
CacherSélectionnez
  • 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]

Image non disponible

La vue interagit avec l'utilisateur :

Image non disponible

Ses composants sont les suivants :

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 :

Image non disponible

Id

Type

Rôle

6

btnAnnuler

Button

annule la génération des nombres

Le code de la vue est le suivant :

 
CacherSélectionnez

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 :

 
CacherSélectionnez
  • 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 :
 
CacherSélectionnez
  • 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 :

 
CacherSélectionnez

Une fois que la vue a lancé des actions, elle voit passer leurs notifications dans la méthode [notifyEvent] suivante :

 
CacherSélectionnez
  • 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 :

 
CacherSélectionnez

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

Un clic sur le bouton [Annuler] provoque l'excéution du code suivant :

 
CacherSélectionnez
  • ligne 5 : toutes les actions lancées sont annulées, ici une seulement ;
  • ligne 7 : l'UI est mise à jour pour signaler la fin de l'attente.

IV-J. L'action [Action_01]

Image non disponible

Le code de l'action [Action_01] est le suivant :

 
CacherSélectionnez
  • 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 :

 
CacherSélectionnez
  • 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] :

 
CacherSélectionnez
  • 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] :

 
CacherSélectionnez
  • 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]

Image non disponible

Le code de la tâche asynchrone [Task_01] est le suivant :

 
CacherSélectionnez
  • 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 :

 
CacherSélectionnez
  • 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 :

Image non disponible

Pour voir les logs, mettre un petit nombre pour N. Les logs sont trouvés dans l'onglet [LogCat] d'Eclipse :

Image non disponible

Nous avons déjà commenté ces logs. Par exemple pour 2 tâches on a les logs suivants :

 
CacherSélectionnez
  • 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 :

 
CacherSélectionnez
  • 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 ; Image non disponible
  • 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].

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Serge Tahé. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.