Epicez vos appels réseau Android avec RoboSpice

Le besoin

Lorsque l’on développe une application Android, il peut arriver que l’on ait besoin de faire appel à des web services.

Ces appels réseau peuvent prendre beaucoup ANRde temps et vous risquez de voir une boîte de dialogue indésirable apparaître (oups !). Il faut donc réaliser ces appels de manière asynchrone. Il peut aussi être intéressant de limiter les échanges pour consommer moins de bande passante notamment grâce à un système de cache.

Il est courant d’utiliser une AsyncTask et de se débrouiller pour enregistrer les données quelque part si besoin. Mais utiliser une AsyncTask n’est pas une bonne solution pour réaliser des appels réseau, nous verrons pourquoi. Heureusement, RoboSpice est là et il s’occupe de tout.

Si vous avez envie d’un résumé de ce qu’apporte RoboSpice, il vous suffit de regarder cette infographie. J’explique plus en détail les différents problèmes de l’existant dans la suite.

L’existant

AsyncTask

L’AsyncTask est la méthode la plus utilisée pour exécuter une tâche asynchrone. C’est notamment ce qui est décrit dans les tutoriels officiels Android pour réaliser un appel réseau. Cependant, l’AsyncTask ignore le cycle de vie de l’Activity et cela pose quelques problèmes non négligeables.

Dans notre cas, le but de l’AsyncTask est d’exécuter une tâche asynchrone et de répercuter le résultat dans l’Activity qui l’a appellée. L’AsyncTask  a donc une référence vers l’Activity. Tournez simplement votre appareil, l’Activity est détruite et une nouvelle est créée. L’AsyncTask ne connait pas la nouvelle Activity et ne peut donc plus la mettre à jour. C’est dommage surtout que pendant ce temps, l’AsyncTask s’exécute, le Garbage Collector ne peut pas s’occuper de l’Activity détruite car elle est toujours référencée. Nous sommes en présence d’une jolie fuite mémoire.

Des solutions existent mais demandent un effort pour être mises en place et sont couteuses en temps de développement. En gros il est difficile d’utiliser proprement les AsyncTasks. Elles restent réservées aux tâches très courtes.

Loader

Depuis Android 3.0 il existe le Loader, aussi disponible dans support v4, qui peuvent être utilisés par les Activities ou les Fagments. Celui-ci permet d’éviter les problèmes de fuite mémoire et de mise à jour de la mauvaise Activity en suivant le cycle de vie de l’Activity.

Le Loader est particulièrement adapté à la gestion des Cursors. Il surveille les changements de la source de données pour en informer l’Activity et se reconnecte au dernier Cursor après un changement de configuration pour ne pas recharger les données.

Il ne convient cependant pas parfaitement aux appels réseau. En effet, lancez une requête et tournez votre appareil avant d’avoir eu le résultat, la requête est perdue et une nouvelle est lancée. De plus, bien que le résultat de la requête soit conservé entre chaque changement de configuration, celui-ci est perdu après un changement d’Activity. Quittez l’Activity et revenez dessus, le résultat est perdu et la requête est relancée.

La solution RoboSpice

La solution

Une équipe d’Octo Technologie s’est penchée sur le problème et a créé RoboSpice. Bien qu’historiquement RoboSpice ait été créé pour exécuter des appels réseau, il est tout à fait possible de l’utiliser pour d’autres types de tâches longues. Vous pouvez utiliser cette bibliothèque à partir d’Android 8 / Foyo / 2.2.

RoboSpice utilise un Service Android pour exécuter la tâche. Des listeners (tout type de Context) sont informés de la progression de la tâche et du résultat sur l’UI Thread. Ils sont attachés et détachés en fonction de leur cycle de vie, il n’y a donc plus de fuite mémoire et le listener reçoit bien les informations même après un changement de configuration.

Un système de cache configurable (délai d’expiration, format d’enregistrement) enregistre le résultat de chaque requête. Cela permet d’éviter les appels réseaux répétés et donc d’économiser de la bande passante.

Pour finir, RoboSpice est orienté objet. Le résultat des requêtes est un objet et il est possible d’en envoyer dans la requête. Plusieurs extensions de client HTTP sont disponibles pour se plier aux habitudes de chacun :

Comment l’utiliser

Pour le coté pratique, allez lire le Starter Guide de RoboSpice. Il explique très bien comment utiliser RoboSpice en prenant l’exemple d’une Activity affichant une liste de tweets récupérée à partir d’un web service REST/JSON.

Vous allez devoir utiliser 4 classes :

  • SpiceRequest
  • SpiceService
  • RequestListener
  • SpiceManager

SpiceRequest

// Attention !! Cette classe ne doit pas être une classe interne du Context
public class MySpiceRequest extends SpiceRequest< ReturnType > {

    public MySpiceRequest() {
        super( ReturnType.class );
    }

    @Override
    public ReturnType loadDataFromNetwork() throws Exception {
        // Code à exécuter de manière asynchrone
        // Retourner le résultat de la requête
    }
}

Votre travail consiste à compléter la méthode loadDataFromNetwork et à choisir de quelle spécialisation de SpiceRequest hériter. Les différentes spécialisations de SpiceRequest simplifient l’écriture de loadDataFromNetwork. Par exemple si vous voulez utiliser Spring for Android RestTemplate, SpringAndroidSpiceRequest est fait pour vous comme le montre le bout de code suivant.

public ReturnType loadDataFromNetwork() throws Exception {
    return getRestTemplate().getForObject( "http://myaddress.com/get_something", ReturnType.class );
}

Cherchez la SpiceRequest qui vous correspond dans la Javadoc.

RequestListener

// Classe interne de l'Activity
private class MyRequestListener implements RequestListener< ReturnType > { 

    @Override
    public void onRequestFailure( SpiceException spiceException ) { 
        // Mettre à jour l'interface quand une erreur s'est produite 
        // durant l'exécution de la requête
    } 

    @Override
    public void onRequestSuccess( ReturnType result ) { 
        // Mettre à jour l'interface avec le résultat de la requête
    } 
}

Créez votre RequestListener avec les méthodes onRequestFailure et onRequestSuccess pour traiter le résultat de votre requête.

SpiceService

Un SpiceService se charge de gérer le cache, d’exécuter les requêtes et de notifier les RequestListeners associés aux requêtes.

Vous avez la possibilité d’écrire votre propre SpiceService si vous voulez tout gérer vous même mais il existe des SpiceServices prédéfinis fournis par RoboSpice. Ces SpiceServices prédéfinis devraient répondre à  la plupart de vos problématiques. Vous avez le choix du client HTTP, du format des données à traiter (JSON, XML, …) et de comment les traiter.

Par exemple, JacksonSpringAndroidSpiceService utilise Spring Android pour exécuter les requêtes, Jackson pour traiter les données JSON et enregistre les résultats en cache. Pour l’utiliser, il faut déclarer le service dans votre AndroidManifest. (Associez un SpringAndroidSpiceService avec une SpringAndroidSpiceRequest)

<service
    android:name="com.octo.android.robospice.JacksonSpringAndroidSpiceService"
    android:exported="false" />

Si vous préférez Google HTTP Client et Gson, pas de problème, GsonGoogleHttpClientSpiceService devrait faire l’affaire. Il en existe d’autres que je vous laisse découvrir dans la Javadoc.

SpiceManager

Le SpiceManager fait le lien entre le Context voulant exécuter des requêtes (Activity, Fragments…) et le SpiceService. Il connait le cycle de vie du context et peut ainsi s’occuper d’attacher/détacher les listeners en fonction de leur cycle de vie.

Pour exécuter une requête, ajoutez ce code dans votre Activity (ou autre Context)

// Utiliser le SpiceService choisi
protected SpiceManager spiceManager = new SpiceManager( MySpiceService.class );

@Override
protected void onStart() {
    super.onStart();
    // Penser à démarrer le SpiceManager
    spiceManager.start( this );
}

@Override
protected void onStop() {
    // Penser à arrêter le SpiceManager
    spiceManager.shouldStop();
    super.onStop();
}

public void request() {
    // Exécute MySpiceRequest et notifie MyRequestListener du résultat
    // CACHE_KEY est la clé pour enregistrer et retrouver le résultat de la requête
    // Les données en cache expireront après une heure (temps en millisecondes)
    spiceManager.execute( new MySpiceRequest(), "CACHE_KEY", DurationInMillis.ONE_HOUR, new MyRequestListener() );
}

Conclusion

Plus besoin de se prendre la tête avec le cycle de vie de l’Activity, les AsyncTasks et le cache, RoboSpice fourni un moyen simple et robuste de réaliser des appels réseau et autres tâches asynchrones dans une application Android. N’hésitez pas à l’utiliser, ce n’est pas compliqué d’épicer un peu votre code Android. En plus c’est open source.

VN:R_U [1.9.22_1171]
Rating: 0 (from 0 votes)
Share
Ce contenu a été publié dans Android, Outils, avec comme mot(s)-clef(s) , , , . Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire