Quantcast
Channel: Publicis Sapient Engineering – Engineering Done Right
Viewing all articles
Browse latest Browse all 1865

Android – Appels réseau avec Volley

$
0
0

Lors du dernier Google I/O, Ficus Kirkpatrick nous a présenté Volley : une bibliothèque permettant de construire facilement des applications réseaux très performantes sur Android.

Cette bibliothèque a commencé à faire son apparition dans le Play Store en 2011 et est maintenant utilisée par Google dans plusieurs de ses applications.

Dans cet article, nous allons vous présenter Volley par la pratique en réalisant une application communiquant avec un service web REST en JSON.

Encore une nouvelle API réseau ?

Il existe plusieurs moyens à notre disposition sur Android pour faire des appels réseaux :

  • On peut citer par exemple HttpURLConnection qui malheureusement souffre de certains bugs sur les versions d’Android antérieures à Gingerbread (Android 3.0), dont un nous obligeant à désactiver le KeepAlive.
  • Il y a également Apache HTTP Client qui fonctionne mieux pour les appareils pre-Gingerbread mais qui comporte également quelques bugs et n’est pas vraiment supporté par l’équipe Android (l’API étant trop grande et difficile à améliorer sans casser la compatibilité).

Que ce soit avec HttpURLConnection ou HTTPClient, il est nécessaire de faire les appels réseaux dans un thread différent du thread principal qui est dédié à l’interface utilisateur.

Pour cela, on utilise généralement sur Android ce qu’on appelle des AsyncTask, qui permettent d’exécuter du code en tâche de fond et de récupérer le résultat sur le thread principal pour nous permettre de mettre à jour l’interface utilisateur.

Ces AsyncTask ont également plusieurs problèmes, le plus important étant qu’ils peuvent être à l’origine de fuites de mémoire.

De plus, contrairement à la croyance populaire, les AsyncTask s’exécutent par défaut en série et non en parallèle, ce qui peut poser des problèmes de performance.

C’est pour pallier tous ces problèmes que Volley a vu le jour :

  • Volley fonctionne avec un mécanisme de thread pool configurable
  • Il supporte la priorisation des requêtes (par exemple, on peut vouloir définir une priorité haute pour le téléchargement de données importantes comme les informations du client, et une priorité plus faible pour le téléchargement d’images)
  • Les résultats des requêtes sont automatiquement mis en cache disque et mémoire
  • On peut très facilement annuler des requêtes en cours, afin d’éviter qu’elles ne soient exécutées pour rien
  • Volley supporte nativement le JSON, les images, et le texte brut. Il est possible de rajouter du support à d’autres types de données facilement.
  • Enfin, on a la possibilité d’implémenter ses propres mécanismes de custom retry / backoff

Attention toutefois, Volley n’est pas forcément la solution à tous vos problèmes réseaux !

Même s’il a été pensé pour être très performant sur toutes les opérations de type RPC, il est déconseillé d’utiliser Volley pour du téléchargement de données très importantes, comme les opérations de streaming ou de téléchargement de médias audio/vidéo.

Présentation de l’API JSON que nous allons utiliser

Pour vous présenter cette bibliothèque, nous allons réaliser une application qui affiche la liste des utilisateurs du compte GitHub de Xebia-France.

Pour cela, nous allons utiliser l’API REST de GitHub qui a pour URL : https://api.github.com/orgs/xebia-france/members

Le résultat retourné par l’API (que nous avons volontairement simplifié) ressemble à cela :

[
 {
 "login": "Username",
 "avatar_url": "https://[...].png",
 "html_url": "https://github.com/Username",
 },
 ...
 ]

Pré-requis

Pour télécharger Volley, il est nécessaire de passer par les sources.

Le plus simple ici est de générer un .jar que l’on inclura dans notre projet :

git clone https://android.googlesource.com/platform/frameworks/volley
cd volley
android update project -p .
ant jar

Nous pouvons maintenant créer un nouveau projet Android et copier le fichier « bin/volley.jar » dans le répertoire « libs » de notre application.

Nous allons également placer dans le répertoire « libs » la bibliothèque « android-support-v4.jar », qui nous sera utile un peu plus tard.

Notre projet Android « fr.xebia.volleytuto » aura une activité (héritant de ListActivity) s’intitulant « GithubListActivity », ainsi qu’une Application (héritant de Application) s’intitulant « XebiaApplication ».

Puisque l’application aura besoin d’accéder à l’API via Internet, il faudra ne pas oublier d’ajouter la permission requise dans le AndroidManifest.

Voici à quoi ressemble notre AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="fr.xebia.volleytuto"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".XebiaApplication"
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="XebiaFr GitHub Members" >
        <activity android:name=".GithubListActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Création de la RequestQueue

Volley utilise une thread pool appelée RequestQueue qu’il convient d’initialiser dans un endroit facilement accessible et commun à toutes les classes nécessitant Volley.

Cela peut être dans l’Application ou dans le constructeur d’un Adapter par exemple.

Ici, nous allons initialiser la RequestQueue de Volley dans la classe principale de notre Application.

Volley propose également un ImageLoader que nous allons instancier. Cet ImageLoader permettra de télécharger des images à partir d’une URL et de les mettre en cache.

ImageLoader est un composant facultatif qui n’est nécessaire que si l’on décide d’utiliser Volley pour télécharger des images.

Ce sera le cas pour notre application, qui se servira de Volley pour récupérer les avatars des membres GitHub.

package fr.xebia.volleytuto;

import android.app.Application;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public final class XebiaApplication extends Application {

    private RequestQueue mVolleyRequestQueue;
    private ImageLoader mVolleyImageLoader;

    @Override
    public void onCreate() {
        super.onCreate();

        // On initialise notre Thread-Pool et notre ImageLoader
        mVolleyRequestQueue = Volley.newRequestQueue(getApplicationContext());
        mVolleyImageLoader = new ImageLoader(mVolleyRequestQueue, new BitmapLruCache());
        mVolleyRequestQueue.start();
    }

    public RequestQueue getVolleyRequestQueue() {
        return mVolleyRequestQueue;
    }

    public ImageLoader getVolleyImageLoader() {
        return mVolleyImageLoader;
    }

    @Override
    public void onTerminate() {
        mVolleyRequestQueue.stop();
        super.onTerminate();
    }
}

ImageLoader nécessite un objet implémentant l’interface ImageCache.

Nous avons utilisé ici un BitmapLruCache dont l’implémentation est la suivante :

package fr.xebia.volleytuto;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public final class BitmapLruCache extends LruCache<String, Bitmap> implements ImageCache {

    public BitmapLruCache() {
        this(getDefaultLruCacheSize());
    }

    public BitmapLruCache(int sizeInKiloBytes) {
        super(sizeInKiloBytes);
    }

    public static int getDefaultLruCacheSize() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;
        return cacheSize;
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;
    }

    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}

Création de la Request

Nous allons maintenant créer notre ListActivity qui appellera les serveurs REST de GitHub à l’aide de Volley pour récupérer la liste des membres de l’organisation Xebia-France

Lorsque l’on souhaite faire une requête HTTP avec Volley, il suffit d’instancier objet héritant de Request, en lui spécifiant les comportements à exécuter en cas de réussite (dans notre cas, afficher la liste des utilisateurs récupérée) et en cas d’échec (afficher un message d’erreur).

Il faudra ensuite envoyer cette Request à la RequestQueue que l’on a initialisé précédemment pour qu’elle soit exécutée.

Volley fournit par défaut les classes suivantes héritant de Request :

  • StringRequest qui retourne une String à partir d’une URL
  • ImageRequest, qui retourne un Bitmap
  • JsonObjectRequest, qui retourne un JSONObject
  • JsonArrayRequest, qui retourne un JSONArray

Il est évidemment possible de créer ses propres classes héritant de Request, afin que Volley fasse un traitement plus adapté à nos besoins.

Ici, nous souhaitons recevoir un objet de type JSONArray, nous allons donc lancer une JsonArrayRequest.

package fr.xebia.volleytuto;

import android.app.ListActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.JsonArrayRequest;
import org.json.JSONArray;
import org.json.JSONObject;

public final class GithubListActivity extends ListActivity {

    private static final String GITHUB_JSON_URL = "https://api.github.com/orgs/xebia-france/members";
    private RequestQueue mRequestQueue;
    private GithubListAdapter mAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // On récupère notre RequestQueue et notre ImageLoader depuis notre objet XebiaApplication
        XebiaApplication app = (XebiaApplication) getApplication();
        mRequestQueue = app.getVolleyRequestQueue();
        ImageLoader imageLoader = app.getVolleyImageLoader();

        mAdapter = new GithubListAdapter(app, imageLoader);
        setListAdapter(mAdapter);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // On va créer une Request pour Volley.
        // JsonArrayRequest hérite de Request et transforme automatiquement les données reçues en un JSONArray
        JsonArrayRequest request = new JsonArrayRequest(GITHUB_JSON_URL,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray jsonArray) {
                        // Ce code est appelé quand la requête réussi. Étant ici dans le thread principal, on va pouvoir mettre à jour notre Adapter
                        mAdapter.updateMembers(jsonArray);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                // Le code suivant est appelé lorsque Volley n'a pas réussi à récupérer le résultat de la requête
                Toast.makeText(GithubListActivity.this, "Error while getting JSON: " + volleyError.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
        request.setTag(this);

        // On ajoute la Request au RequestQueue pour la lancer
        mRequestQueue.add(request);
    }

    @Override
    protected void onStop() {
        mRequestQueue.cancelAll(this);
        super.onStop();
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        // Lorsque l'on clique sur un élément de la liste, cela lancera l'URL du compte GitHub de l'utilisateur sélectionné.
        JSONObject item = mAdapter.getItem(position);
        String url = item.optString("html_url");
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(url));
        startActivity(intent);
    }
}
  • Lors du onCreate, nous associons un Adapter (que l’on va créer plus tard) à notre liste. Cet adaptateur aura pour but de relier les données JSON à des layouts afin qu’elles soient affichées.
  • Lors du onResume, nous faisons une requête Volley que l’on ajoute à notre RequestQueue afin que ce dernier l’exécute.
    Notre Request est de type JsonArrayRequest et permet de récupérer directement un JSONArray.
  • Dans le onStop, nous annulons toutes les requêtes qui n’ont pas pu être terminées.
    Cela est très utile pour éviter que le téléphone continue de lancer des threads qui concernent des objets qui n’existent plus (rappel : une Activity est détruite lors d’un changement d’orientation).
    On peut associer un tag à une requête pour permettre de stopper facilement toutes les requêtes qui concernent ce tag, c’est ce que nous avons fait dans le onResume avec la méthode setTag
  • Enfin, dans le onListItemClick, lorsqu’un élément de la liste sera sélectionné, on appellera une Intent qui lancera un navigateur vers le compte GitHub de l’utilisateur sélectionné

Il ne nous reste plus qu’à créer un layout pour notre liste, et de lier les données grâce à un Adapter…

Création du layout

Chaque élément récupéré par l’API sera affiché dans une liste. Il faut penser à créer un layout pour afficher les éléments de la liste :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/avatar"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:scaleType="fitXY"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true" />

    <TextView
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@+id/avatar"
        android:textSize="16sp"
        android:textStyle="bold" />

</RelativeLayout>

Notre layout affichera pour chaque élément l’avatar de l’utilisateur, ainsi que son login.

L’élément « com.android.volley.toolbox.NetworkImageView » est une imageView particulière de Volley nécessitant en entrée une URL plutôt qu’un drawable.

Volley utilisera l’ImageLoader que l’on a précédemment instancié dans l’Application pour télécharger une image à partir de son URL et l’afficher automatiquement dans une vue.

Création de l’Adapter

Notre Adapter contient un JSONArray et affiche les données des JSONObject dans les vues.

Il utilise le pattern du ViewHolder pour des raisons de performance.

package fr.xebia.volleytuto;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public final class GithubListAdapter extends BaseAdapter {

    private final Context mContext;
    private final ImageLoader mVolleyImageLoader;
    private JSONArray mMembers;

    public GithubListAdapter(Context context, ImageLoader imageLoader) {
        mContext = context;
        mVolleyImageLoader = imageLoader;
    }

    public void updateMembers(JSONArray members) {
        mMembers = members;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return (mMembers == null) ? 0 : mMembers.length();
    }

    @Override
    public JSONObject getItem(int position) {
        JSONObject item = null;
        if (mMembers != null) {
            try {
                item = mMembers.getJSONObject(position);
            } catch (JSONException e) {
                // loguer l'erreur
            }
        }
        return item;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        TextView login;
        NetworkImageView avatar;
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
            login = (TextView) convertView.findViewById(R.id.login);
            avatar = (NetworkImageView) convertView.findViewById(R.id.avatar);
            convertView.setTag(new ViewHolder(login, avatar));
        } else {
            ViewHolder viewHolder = (ViewHolder) convertView.getTag();
            login = viewHolder.mLogin;
            avatar = viewHolder.mAvatar;
        }

        // On récupère les informations depuis le JSONObject et on les relie aux vues
        JSONObject json = getItem(position);
        login.setText(json.optString("login"));
        avatar.setImageUrl(json.optString("avatar_url"), mVolleyImageLoader);
        return convertView;
    }

    static final class ViewHolder {
        final TextView mLogin;
        final NetworkImageView mAvatar;

        public ViewHolder(TextView login, NetworkImageView avatar) {
            mLogin = login;
            mAvatar = avatar;
        }
    }
}

La partie la plus importante de cet Adapter est la ligne suivante :

avatar.setImageUrl(json.optString("avatar_url"), mVolleyImageLoader);

Cela permet à Volley d’afficher directement une image à partir de l’URL trouvée dans le champ « avatar_url » du JSON.

Et voilà ! En seulement quelques classes nous avons créé une application Android from-scratch communiquant avec un service web via la bibliothèque Volley.

Pour aller plus loin…

Il est possible de déboguer et voir le comportement de Volley en définissant une propriété système :

adb shell setprop log.tag.Volley VERBOSE

Dans cet exemple nous avons utilisé un JsonArrayRequest pour récupérer un JSONArray, mais on peut également demander à Volley d’interpréter le JSON reçu à l’aide de Gson ou même Jackson.

Si l’on n’utilise pas de JSON mais du XML ou du ProtocolBuffer, il est tout à fait possible de gérer ces format de données.

Pour cela, il suffit de créer son propre objet héritant de Request afin qu’il soit compatible avec le RequestQueue de Volley.

Voici un exemple d’implémentation permettant de récupérer un objet qui sera parsé avec Gson plutôt qu’un JSONArray.

Conclusion

Volley est une bibliothèque puissante et simple à utiliser.

Il suffit d’initialiser une RequestQueue, et d’y ajouter des Requests afin qu’elles soient exécutées.

La bibliothèque est également extensible avec facilité pour rajouter ses propres types de requêtes.

Volley offre de vraies solutions aux problématiques réseau sur Android.

Pour plus d’informations sur Volley, vous pouvez voir la vidéo de présentation lors du Google I/O 2013.

Le code source complet de l’application que l’on vient de développer quant à lui est disponible ici.


Viewing all articles
Browse latest Browse all 1865

Trending Articles