Android : Threading, Moteur de base de données SQLite – Feuilleton de l’été épisode 4

Nous vous présentons aujourd’hui l’avant-dernier épisode de notre série d’été sur Android. Lors de l’étape précédente, nous avons récupéré la liste des filières disponibles chez Oxiane via un appel à un service REST-RPC. Cependant nous avons utilisé pour l’appel le même thread que l’interface utilisateur. Ce qui n’est pas une bonne idée. Nous commencerons cet épisode en corrigeant le tir. En effet, nous expliquerons comment utiliser un thread différent pour cet appel.

Toujours dans l’épisode précédent, dès lors que nous avons les données, elles sont affichées à l’écran. En conséquence, à chaque fois que l’utilisateur lance l’application une requête est effectuée vers le serveur http. Nous avons conclu ensemble que procéder ainsi peut entraîner une surcharge injustifiée du réseau, hormis le risque d’indisponibilité des données qui peut se poser. Dans cet épisode, nous allons introduire le moteur de base de données embarquée SQLite. A l’issue de la première requête, les données seront stockées en base. Lors des connexions suivantes, les données seront extraites de la base et affichées.

Nous nous sommes aussi engagé à compléter notre code afin de récupérer, à partir de la position cliquée par l’utilisateur dans la liste, la liste des cours de la filière sélectionnée. Pour ce faire, nous introduirons le concept d’Intent. Sous Android, les composants communiquent à l’aide des messages asynchrones appelés intents.

Notre feuille de route étant définie, procédons sans plus tarder à sa matérialisation.

Threading

Nous pouvons contourner le problème de blocage d’interface utilisateur en produisant des threads séparés et en gardant leurs traces, mais nous ne devons pas. Heureusement Android fournit un ensemble d’APIs sous la forme de librairie Apache HttpClient qui apportent une abstraction des classes java.net et qui sont conçues pour offrir un support http robuste et aider à prendre en charge le souci de threads séparés. Nous allons effectuer des requêtes réseau dans un thread séparé de l’interface utilisateur en utilisant l’API HttpClient.

li.pupuce {
background : transparent url(http://www.www.oxiane.com/ressources/images/subcontent_li_bg.png) no-repeat scroll 10px 0;
padding:0 0 0 35px;
margin:5px 0;
}
ol.listeComptage, ol.listeComptage li { margin:0; padding:0 0 0 35px; list-style-type:decimal ; }
ol.listeComptage li { padding-left:12px; }
ul.listeSimple, ul.listeSimple li { margin:0; padding:0 0 0 15px; list-style-type:circle; }
ul.listeSimple li { padding-left:10px; }

L’utilisation de HttpClient commande au moins 3 étapes dans le processus de développement :


  • Définition d’un handler Android : Permet d’envoyer et de traiter les messages et objets runnable d’un thread. Chaque instance Handler est associée à un seul thread et à sa liste de messages (message queue).

  • Définition d’un responseHandler Apache : pour récupérer la réponse à la requête http.

  • Effectuer la requête réseau dans un thread différent de celui de l’interface.

Handler Android

Nous allons créer le handler android en tant que membre de la classe CatalogueOxiane. Dans cette classe, créez la variable androidHandler comme indiqué au listing 1. Puis nous créons une instance de la classe android.os.Handler en implémentant sa méthode handleMessage (Message) afin de recevoir des messages. La classe Message définit un message contenant une description et des données arbitraires qui peuvent être envoyées à un Handler. La méthode Message.getData() retourne un dictionnaire (clés,valeurs) des données transportées par le message. La réponse à notre appel au service rest-rpc sera transmise au handler avec la clé « RESPONSE » et la valeur sera une chaîne de caractères représentant le document XML retourné.

La méthode fillData (String) prend en entrée la chaîne XML reçu par le handler. Elle fera appel à un parseur pour récupérer les données du document et obtenir un objet Catalogue. Puis affichera la liste des filières à l’écran. Pour le moment cette méthode n’est pas prête, nous allons l’écrire très bientôt. Remarquez que l’affichage du catalogue ne se fait plus au niveau du callback onCreate () mais par le handler Android.

Listing 1 : Handler Android


private final Handler androidHandler = new Handler()
{
  public void handleMassage (Message msg){
    String bundleResult = msg.getData().getString("RESPONSE");
    fillData(bundleResult);
  }
};

Avant de passer au responseHandler d’Apache, intéressons nous d’abord la méthode fillData(String). Elle est fournie au listing 2.

Listing 2. fillData(String)



private void fillData() {

  //parsing du stream pour récupérer le catalogue
  Catalogue oxCatalogue = parse (new InputSource(
                             new StringReader(bundleResult)));

  //instancions un ArrayList pour accueillir 
  //les titres de filières à afficher
  ArrayList listeTitres = new ArrayList();
  for (Filiere f: oxCatalogue.getFilieres()) {	    
    listeTitres.add(f.getMtitre());
  }

  // ArrayAdapter pour afficher la liste des filières
  ArrayAdapter branchAdapter = 
       new ArrayAdapter(this, R.layout.branch_row,    
                                          R.id.branch, listeTitres);
  //Indiquer l'adapter au système pour l'affichage
  setListAdapter(branchAdapter);
}

Elle est similaire à la méthode fillData() que nous avons écrit à l’épisode 3 de série à la seule différence qu’elle fait appel à la méthode parse(InputSource) qui fait juste le parsing d’une source de données contrairement à la méthode callService() qui effectuait l’appel bloquant au service web.

La méthode parse(InputSource) est fournie ci-dessous.

Listing3 : parse(InputSource)



    private Catalogue parse(InputSource in){		
		Catalogue catalogue = null;
		try {

            /* Obtenir le parseur SAXParser depuis SAXPArserFactory. */
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser sp = spf.newSAXParser();

            /* Obtenir le XMLReader du SAXParser que nous venons de créer */
            XMLReader xr = sp.getXMLReader();
            /* Créer un nouveau ContentHandler et l’appliquer au XML-Reader*/
            SaxCatalogueHandler myHandler = new SaxCatalogueHandler();
            xr.setContentHandler(myHandler);
            
            /* Parser le document XML retourner par l’URL. */
            xr.parse(in);
            /* Le parsing est terminé */

           /* Récupérons l’objet Catalogue via l’accesseur de notre Handler */
            catalogue = myHandler.getCatalogue();            
            
       } catch (Throwable e) {
    	   e.printStackTrace();            
       } 
		
		return catalogue;
	}

ResponseHandler Apache et Thread de la requête réseau

Nous définissons le ResponseHandler d’Apache http, ainsi que le thread de la requête dans la méthode performRequest() fournie au listing 4.
Nous commençons par implémenter la méthode handleResponse (HttpResponse) du ResponseHandler. Cette méthode prend en charge le message retourné sous forme d’entité http. L’appel HttpEntity.getContent() crée un objet inputStream de l’entité. Nous passons cet objet à la méthode utilitaire inputStreamToString(InputStream) qui convertit un InputStream en String. Cette méthode est founie au listing 5. La suite du ResponseHandler consiste à constituer un message avec le résultat obtenu et de l’ajouter au pool de message du handler d’android que nous avons défini précédemment.
Le code du thread consiste essentiellement à créer une instance de client http, ainsi qu’une méthode http. Et d’appeler la méthode execute du client http avec la méthode et le responseHander d’Apache. Notons que la méthode HttpGet récupère sous la forme d’une entité toute information identifiée par une URI.

Listing 4 : ResponseHandler Apache +thread pour la requête


public void performRequest(){
  //Response Handler
  final ResponseHandler responseHandler = new 
ResponseHandler(){
public String handleResponse(HttpResponse response)
				throws ClientProtocolException, IOException {				
        HttpEntity entity = response.getEntity();
	  String result = null;
	  try{
	    result = inputStreamToString(entity.getContent());
	    //obtenir un objet Message depuis le pool de message du 
          //handler android
	    Message message = androidHandler.obtainMessage();
	    //Inserer les donnees recuperee dans le message 
	    Bundle bundle = new Bundle();
	    bundle.putString("RESPONSE", result);
	    message.setData(bundle);
	    //mettre le message dans la file de message (message queue) 
          //du handler android
	    androidHandler.sendMessage(message);
	}catch(IOException e){
		//log ou handle
	}
	return result;
	}			
   };
   //thread de la requête
   new Thread(){
	 public void run(){
	  try{
		//client Http par defaut
		DefaultHttpClient client = new DefaultHttpClient();
		HttpGet httpMethod = new HttpGet(ServiceLocation);
		client.execute(httpMethod, responseHandler);
	  }catch(ClientProtocolException e){
		//log ou handle				
	  }catch(Exception e){
		//log ou handle
	  }
	}
     }.start();
 }

Finalement le code de la méthode inputStreamToString(InputStream) est comme indiqué au listing 5 ci-dessous.

Listing 5 : inputStreamToString(InputStream)



public String inputStreamToString(InputStream in) throws 
IOException{
  InputStreamReader streamReader = new InputStreamReader(in);
  BufferedReader buffer = new BufferedReader(streamReader);
  StringBuilder sb = new StringBuilder();
  String line="";		
   while ( null!=(line=buffer.readLine())){
      sb.append(line); 
   }
   return sb.toString();
 }

Il ne vous reste plus qu’à appeler la méthode performRequest() dans le callBack onCreate() à la place de la méthode fillData() tel que réalisée à l’épisode précédent.

Lancez l’application ! Vous obtenez l’écran suivant :

Déconnectez vous du réseau Internet et relancez l’application. Vous obtenez l’écran suivant.

Il s’agit de l’interface utilisateur sans la liste des filières car la requête réseau a échoué, mais votre interface n’est pas bloquée. S’il y avait d’autres fonctionnalités de l’application accessible via cette interface l’utilisateur continuerait son expérience avec le système.

Retournez dans le callBack onCreate() et remettez la méthode fillData() à la place de la méthode performRequest(). En effet vous retournez à la configuration de l’épisode 3. Toujours avec l’accès au réseau coupé, relancez l’application. Vous obtenez l’écran suivant :

Pas très élégant n’est ce pas ? Ceci justifie l’utilisation d’un thread séparé pour les requêtes réseau.


Retrouvez l’ensemble des épisodes de notre tutoriel Android, le feuiileton de l’été:


Voir également