• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 13/01/2020

Lisez des objets JSON depuis une requête HTTP

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Requête HTTP et intégration dans une AsyncTask

Dans cette section, nous présentons comment réaliser une requête HTTP vers un service répondant au format JSON. La première étape consiste à intégrer cette requête à une AsyncTask.

La classe HttpURLConnection  est un helper pour simplifier l’envoi de requêtes HTTP et la récupération de la réponse. La classe prépare la requête GET, éventuellement de type POST, et permet au développeur de lire les données reçues si la requête a réussi. La lecture des données se fait naturellement comme on lirait un fichier c’est-à-dire comme un flux de données continues.

 

Dans le code source issu de la documentation, on construit donc l’URL, puis on ouvre la connexion avecopenConnection()  qui renvoie un objet de type HttpURLConnection. Puis, commence la lecture à partir de l’objet renvoyé par getInputStream().

URL url = new URL("https://openclassrooms.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
} finally {
urlConnection.disconnect();
}

Dans le code, on voit un appel à readStream()  qui est une méthode à implémenter soi-même et qui consomme les données du flux in  afin de construire le résultat (ce qui n’est pas obligatoire, on peut consommer les données et les analyser mais par exemple ne jamais reconstruire la String totale). Un exemple d’implémentation est présenté ci-dessous. On reconstruit dans un StringBuilder  la chaîne qui correspond au contenu de la réponse de la requête HTTP.

private String readStream(InputStream is) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader r = new BufferedReader(new InputStreamReader(is),1000);
for (String line = r.readLine(); line != null; line =r.readLine()){
sb.append(line);
}
is.close();
return sb.toString();
}

 Dans la méthode postExecute()  on peut rafraîchir l'interface graphique, par exemple le TextView  montré dans l'écran ci-dessous :

 

Réinstanciation d’un message JSON et navigation dans des objets JSON

Les services en ligne répondent aux requêtes de l’utilisateur principalement en utilisant le format XML ou JSON. Lorsque le format JSON est utilisé, il est utile de comprendre comment est structuré l’information. D’un point de vue programmation, on peut ensuite facilement réinstancier des objets JSON et les manipuler.

Principe d'une reponse JSON à une requête HTTP (logo Json CC-BY Linux Screenshots)
Principe d'une réponse JSON à une requête HTTP (logo Json CC-BY Linux Screenshots)

Les données que l’on récupère de services en ligne sont souvent au format JSON. L’intérêt du format réside dans sa compacité et dans la facilité pour le manipuler. Toutes les données sont écrites comme des chaînes de caractères (comme en XML) et ces données sont structurées en objets ou tableaux d’objets qui contiennent des paires de clefs/valeurs.

L'exemple le plus simple d'un objet JSON contenant des données est le suivant :

{
"clef1": "value1",
"clef2": 10,
"clef3": "value3"
}

Dans cet objet JSON, délimité par des accolades, on trouve à l’intérieur des paires de clefs/valeurs. Une clef est une chaîne de caractères (unique dans l’objet) et la valeur est une chaîne, un entier, un flottant, un booléen, un autre objet JSON, ou un tableau. Dans l'exemple suivant, la valeur de la clef "objet" est un autre objet JSON :

{
"clef1": "value1",
"clef2": "value2",
"clef3": "value3",
"objet": { "clefa": "value4" }
}

D'un point de vue de la programmation, on utilise la classe JSONObject  pour réinstancier l'objet JSON correspondant (ligne 1). Ensuite, on peut parcourir toutes les paires de clef valeur avec l'itérateur Iterator<String>  et l'appel à json.keys(). Pour chaque clef, on peut récupérer la valeur associée avec l'appel à json.getString(key)  si l'on sait déjà que la valeur attendue est de typeString. Pour un autre type, par exemple un entier, on fera de même avec l'appeljson.getInteger(key). Si la valeur associée à la clef est un objet (comme en ligne 5 du listing précédent), il faut tout simplement utiliser la méthode  json.get(key)  pour récupérer un nouvel objet JSON (comme montrée en ligne 9 du listing ci-dessous).

JSONObject json = new JSONObject(s);
Iterator<String> it = json.keys();
while (it.hasNext())
{
String key = it.next();
Log.i("CIO", "JSON key: " + key);
Log.i("CIO", " value: " + json.getString(key));
Log.i("CIO", " value of type: " + json.get(key).getClass());
}
Log.i("CIO", "Sous objet, valeur: " + json.getJSONObject("objet").getString("clefa"));

Lorsqu'on exécute ce code sur le message JSON précédent, on voit bien apparaître les différents types des valeurs associées aux clefs (StringIntegerStringJSONObject), comme montré ci-dessous. En ligne 12 du code précédent, on observe comment aller chercher la valeur associée à la clef "clefa" dans le sous-objet associé à la clef "objet". La valeur obtenue est "value4", comme montré en dernière ligne ci-dessous.

JSON key: clef1
value: value1
value of type: class java.lang.String
JSON key: clef2
value: 10
value of type: class java.lang.Integer
JSON key: clef3
value: value3
value of type: class java.lang.String
JSON key: objet
value: {"clefa":"value4"}
value of type: class org.json.JSONObject
Sous objet, valeur: value4

Si le message JSON est complexe, l’interprétation du message peut utiliser des bibliothèques plus puissantes. On peut aussi valider la correction du format du message, ce qui peut éviter des bugs lors de l’interprétation. Mais globalement, on a ainsi un moyen facile de décoder la réponse à une requête vers un service en ligne.

Application à Flickr

Pour faire la synthèse de cette partie, nous proposons ici d'étudier une application dont le but est d'afficher une série d'image qui correspondent à un tag (par exemple ici "star wars") à partir du service Flickr.

Pour se faire, il faut utiliser l'API prévue à cet effet. Pour notre exemple, nous utiliserons une API non authentifiée, c'est-à-dire qui ne nécessite pas de token d'autorisation associé à des identifiants, ce qui simplifie la mise en œuvre. L'API permet de récupérer une dizaine d'informations sur les récentes images correspondant à un tag, en utilisant l'URL :

http://www.flickr.com/services/feeds/photos_public.gne?tags=starwars&format=json

Le flux JSON obtenu à partir de cette URL renvoie un tableau de 20 objets JSON contenant chacun les informations relatives à une image :

Dans cet objet JSON, il faut donc aller chercher la valeur de la clef object->items[0]->media->m, puis télécharger cette image. Il faut bien entendu répéter cela pour les 20 images, et les placer dans uneListView  (ce point technique, hors du champs de ce cours, n'est pas expliqué ici). On aura donc deux tâches asynchrones différentes :

  • AsyncFlickrJSONData: tâche qui télécharge le message JSON.

  • AsyncBitmapDownloader: tâche qui charge une image dans un objet Bitmap, afin de l'afficher.

AsyncFlickrJSONData

Cette classe prend en entrée l'URL de l'API de Flickr et doit construire l'objet JSON correspondant. Elle réutilise donc les éléments vus en début de ce chapitre, à savoir HttpUrlConnection  au sein d'uneAsyncTask<String, Void, JSONObject>.

public class AsyncFlickrJSONData extends AsyncTask<String, Void, JSONObject> {
private AppCompatActivity myActivity;
public AsyncFlickrJSONData(AppCompatActivity mainActivity) {
myActivity = mainActivity;
}
@Override
protected JSONObject doInBackground(String... strings) {
URL url = null;
HttpURLConnection urlConnection = null;
String result = null;
try {
url = new URL(strings[0]);
urlConnection = (HttpURLConnection) url.openConnection(); // Open
InputStream in = new BufferedInputStream(urlConnection.getInputStream()); // Stream
result = readStream(in); // Read stream
}
catch (MalformedURLException e) { e.printStackTrace(); }
catch (IOException e) {
e.printStackTrace();
}
finally {
if (urlConnection != null)
urlConnection.disconnect();
}
JSONObject json = null;
try {
json = new JSONObject(result);
} catch (JSONException e) {
e.printStackTrace();
}
return json; // returns the result
}
...
}

 Dans la méthode onPostExecute(JSONObject s), on va récupérer les 20 urls des images du message (ligne 6 et 7) :

protected void onPostExecute(JSONObject s) {
try {
JSONArray items = s.getJSONArray("items");
for (int i = 0; i<items.length(); i++)
{
JSONObject flickr_entry = items.getJSONObject(i);
String urlmedia = flickr_entry.getJSONObject("media").getString("m");
Log.i("CIO", "URL media: " + urlmedia);
// Downloading image
AsyncBitmapDownloader abd = new AsyncBitmapDownloader(tableau);
abd.execute(urlmedia);
}
}
}

Le traitement de l'url de la variable urlmedia  est géré par une nouvelle tâche asynchrone,AsyncBitmapDownloader, que l'on exécute pour chaque image (ligne 12).

AsyncBitmapDownloader

Cette classe prend en paramètre l'URL d'une image et renvoie l'objet BitMap correspondant. On utilise donc à nouveau HttpUrlConnection  mais cette fois au sein d'uneAsyncTask<String, Void, Bitmap>.

public class AsyncBitmapDownloader extends AsyncTask<String, Void, Bitmap> {
BitmapAdapter adapter_ = null;
public AsyncBitmapDownloader(BitmapAdapter adapter) {
adapter_ = adapter;
}
@Override
protected Bitmap doInBackground(String... strings) {
URL url = null;
HttpURLConnection urlConnection = null;
Bitmap bm = null;
try {
url = new URL(strings[0]);
urlConnection = (HttpURLConnection) url.openConnection(); // Open
InputStream in = new BufferedInputStream(urlConnection.getInputStream()); // Stream
bm = BitmapFactory.decodeStream(in);
in.close();
}
catch (MalformedURLException e) { e.printStackTrace(); }
catch (IOException e) {
e.printStackTrace();
}
finally {
if (urlConnection != null)
urlConnection.disconnect();
}
return bm;
}
...
}

Il reste à faire le travail d'ajouter ces images une ListView, ce qui prend un peu d'énergie. Les curieux peuvent aller voir le code source de l'application sur github. Le résultat obtenu est alors :

Exemple de certificat de réussite
Exemple de certificat de réussite