• Aucun résultat trouvé

10 Exemple-09 : un client REST asynchrone

Dans le projet précédent, nous avons contourné une bonne pratique qui dit qu'on ne doit pas ouvrir une connexion réseau dans le thread de l'activité. Nous allons ici la créer dans un autre thread. La tâche qui s'exécutera dans ce thread sera par ailleurs asynchrone. La vue [Vue_01] la lancera sans attendre sa fin. Elle sera avertie par un événement que la tâche a terminé son travail. L'architecture évolue comme suit :

La tâche s'intercale entre la vue et la couche [métier]. Elle va exécuter les méthodes de la couche [métier] dans des threads différents de celui de l'activité. Elle se déroulera de façon asynchrone. Concrètement cela signifie que l'utilisateur peut continuer à interagir avec la vue en même temps que la tâche s'exécute en tâche de fond. La tâche doit avertir la vue lorsqu'elle a terminé son travail. Pour implémenter la tâche, nous allons étendre une classe Android, la classe [AsyncTask].

10.1 La classe [AsyncTask]

D'après la documentation Android :

An asynchronous task AsyncTask<Params, Progress, Result> is defined by a computation that runs on a background thread and whose result is published on the UI thread. An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute.

La classe [AsyncTask<Params, Progress, Result>] peut avoir différentes signatures. Nous utiliserons la signature AsyncTask<Object,Object,Void>. La classe a quatre méthodes principales :

• la méthode [doInBackGround] est la méthode exécutée en tâche de fond. Sa signature est la suivante protected abstract Result doInBackground (Params... params)

Elle reçoit une suite de paramètres de type Params. Pour nous ce type sera Object. C'est le premier type générique de notre signature AsyncTask<Object,Object,Void>. Elle rend une donnée de type Result. Nous, nous ne rendrons aucun résultat. C'est le troisième type générique de notre signature AsyncTask<Object,Object,Void> ;

• la méthode [onProgressUpdate] permet de publier dans le thread de l'activité l'avancement de la tâche. Sa signature est la suivante :

protected void onProgressUpdate (Progress... values)

Nous n'utiliserons pas cette méthode. Dans la signature AsyncTask<Object,Object,Void>, le type Progress est le deuxième type générique, ici Object ;

• la méthode [onPreExecute] est exécutée dans le thread de l'activité avant l'exécution de la méthode [doInBackGround]. Sa signature est la suivante :

protected void onPreExecute ()

C'est dans cette méthode que la [Task] enverra à son boss la notification [WORK_STARTED]. La notification est donc envoyée dans le thread de l'UI ;

• la méthode [onPostExecute] est exécutée dans le thread de l'activité après l'exécution de la méthode [doInBackGround]. Sa signature est la suivante :

Couche [DAO] Couche [métier] Vue Activité Android

protected void onPostExecute (Result result)

où le type Result est le troisième type générique de la signature AsynTask<Object, Object, Void>, donc Void ici. C'est dans cette méthode que la tâche transmettra son résultat à l'objet qui l'a appelée. Cette information remontera jusqu'à la vue. Celle-ci pourra utiliser cette information pour se mettre à jour. C'est possible car on sera alors dans le thread de l'activité.

10.2 Le projet Android

Créez un projet [exemple-09-client-rest-asynchrone] par copie du projet [exemple-08-client-rest] :

Modifiez les caractéristiques du projet dans [pom.xml] de la façon suivante : <groupId>exemples</groupId>

<artifactId>exemple-09-client-rest-asynchrone</artifactId> <version>0.0.1-SNAPSHOT</version>

<packaging>apk</packaging>

<name>exemple-09-client-rest-asynchrone</name>

10.3 Les éléments de l'interface asynchrone

Nous allons rassembler les éléments de l'interface asynchrone dans un package [istia.st.android.tasks] :

La tâche asynchrone aura l'interface [ITask] suivante : 1. package istia.st.android.tasks; 2.

3. import istia.st.android.metier.IMetier; 4.

5. public interface ITask {

7. // pour lui rendre son résultat 8. void setCaller(ICaller caller); 9.

10. // la tâche asynchrone doit avoir accès à la couche [métier] 11. void setMetier(IMetier métier);

12. }

Cette interface découle de l'architecture du projet :

La tâche interagit avec la vue et la couche [métier] :

• elle doit avoir une référence sur la couche [métier]. La méthode [setMetier] de la ligne 11 sert à injecter cette référence ; • elle doit savoir à qui elle doit rendre son résultat. Comme elle s'exécute dans un autre thread que la vue qui l'a appelée,

celle-ci n'attend pas son résultat. La tâche doit savoir à qui rendre le résultat qu'elle va obtenir. C'est la méthode [setCaller] de la ligne 8 qui sert à injecter cette information.

L'interface [ICaller] est la suivante :

1. package istia.st.android.tasks; 2.

3. public interface ICaller {

4. // définit une méthode pour récupérer l'info produite par une tâche 5. public void callBack(Object info);

6. }

On se rappelle que la tâche [ITask] reçoit un objet [ICaller]. Pour rendre l'information [info] qu'elle a produite, la tâche utilisera la méthode [ICaller].callBack(info).

La tâche chargée d'obtenir les nombres aléatoires est la tâche [AleaTask] suivante : 1. package istia.st.android.tasks;

2.

3. import istia.st.android.metier.IMetier; 4. import android.os.AsyncTask;

5.

6. public class AleaTask extends AsyncTask<Object, Object, Void> implements ITask { 7.

8. // couche [métier]

9. private IMetier métier;

10. // le caller

11. private ICaller caller;

12. // l'info produite par la tâche 13. private Object info;

14.

15. @Override

16. protected Void doInBackground(Object... params) {

17. // on est dans un autre thread que celui de l'UI

18. // on récupère les trois paramètres - on suppose qu'ils sont corrects 19. int a = (Integer) params[0];

20. int b = (Integer) params[1]; 21. int n = (Integer) params[2];

22. // on appelle la couche [métier]

Couche [DAO] Couche [métier] Vue Activité Android

23. info = métier.getAleas(a, b, n);

24. // fin

25. return null; 26. }

27.

28. protected void onPostExecute(Void result) {

29. // on est dans le thread de l'UI

30. // on rend l'info au caller

31. caller.callBack(info); 32. }

33.

34. // setters

35. public void setMetier(IMetier métier) { 36. this.métier = métier;

37. } 38.

39. public void setCaller(ICaller caller) { 40. this.caller = caller;

41. } 42. 43. }

• ligne 6 : la tâche implémente l'interface [ITask] d'où la présence des méthodes [setMetier] (ligne 35) et [setCaller] (ligne 39) ;

• ligne 6 : la tâche étend la classe AsyncTask<Object, Object, Void> d'où la présence des méthodes [doInBackground] (ligne 16) et [onPostExecute] (ligne 28) ;

• ligne 16 : c'est la méthode [doInBackground] qui est exécutée en tâche de fond. C'est dans cette méthode qu'on doit faire les connexions réseau. Cela signifie pour nous que c'est dans cette méthode qu'on doit appeler la couche [métier] ;

ligne 16 : la méthode va recevoir trois paramètres, dans l'ordre les valeurs a, b et n pour calculer n nombres aléatoires dans l'intervalle [a,b]. La notation [Object... params] est analogue à la notation [Object[] params]. On reçoit un tableau d'objets ;

lignes 19-21 : on récupère les trois paramètres a, b et n ;

• ligne 23 : on appelle la couche [métier]. La méthode [métier.getaleas] rend un type [List<Object>] qu'on met dans le type [Object] de la ligne 13. La couche [métier] ne lance pas d'exception. C'est pourquoi on n'a pas utilisé de try / catch ; • ligne 28 : la méthode [onPostExecute] est exécutée une fois que la méthode [doInBackground] a terminé son travail. Par

ailleurs, elle s'exécute dans le thread de l'activité Android. L'information [info] produite par la méthode [doInBackground] peut alors être utilisée pour mettre à jour l'interface visuelle de l'activité ;

• ligne 31 : on appelle la méthode [callBack] du [ICaller] de la ligne 11 pour rendre l'information [info] produite par la méthode [doInBackground].