Windows Azure Toolkit For Devices
3/ Gestion des Blobs sous Android
Dans Windows Azure, les Blobs sont en quelque sorte des fichiers qui peuvent être volumineux si nécessaire. Les Blobs sont regroupés dans des Blob Container qui ne possèdent qu’un seul niveau hiérarchique (la racine). Tous les blobs sont stockés sous cette racine, les uns à la suite des autres. Il est possible de simuler une pseudo hiérarchie en incluant dans le nom des blobs des « / ».
Donc si je résume, pour stocker ou récupérer le contenu d’un blob (d’un fichier) j’ai besoin de fournir le nom du blob container qui le contient, ainsi que le nom du blob lui-même.
Pour illustrer le fonctionnement des blobs nous allons repartir du code utilisé pour l’authentification ASP Membership et l’agrémenter d’une page de gestion de blobs.
Accès sécurisé Pour être en mesure d’accéder aux blobs il faut mémoriser quelque part le token qui est retourné par la fonction d’authentification. Dans la partie consacrée à l’authentification ASP Membership, nous avions stocké le token d’identification dans une variable static de la classe « Global ». Ce token est stocké sous la forme d’un objet de type « WAZServiceAccount ».
Pour mettre en pratique la gestion des blobs, nous allons stocker une photo (prise avec l’appareil photo du device Android) et la stocker dans Windows Azure. Nous listerons aussi tous les blobs présents dans le blob container qui nous servira à stocker nos photos. En avant !
Création de l’activité « BlobsActivity » Vous allez créer une nouvelle activité qui se compose d’une liste « LvBlobs », d’un bouton « BouPhoto », d’une image « IvBlob » et d’une progress bar « progressBar ».
La liste permettra d’afficher tous les blobs présents dans le blob container, le bouton à prendre une photo pour l’uploader dans Windows Azure, l’image à afficher le contenu d’un blob sélectionné dans la liste.
Votre page devrait avoir en gros l’aspect suivant :

Avant d’oublier, nous allons tout de suite ajouter le code de navigation vers cette nouvelle activité en réponse au clic sur le bouton « Blobs » depuis l’activité « MenuActivity» :
@Override
public void onClick(View v)
{
if (v == bouBlobs)
{
Intent wIntent = new Intent(this, BlobsActivity.class);
startActivity(wIntent);
}
else if (v == bouQueues)
{
}
else if (v == bouTables)
{
}
}
| Lister le contenu d’un blob container Dans cet exemple nous allons utiliser un blob container dont le nom est « photos3 ». Chaque photo stockée dans ce container aura comme nom la date du jour (ex 20120510163044.jpg pour une image prise le 10/05/2012 à 16h30 et 44 secondes).
Pour travailler sur les blobs il nous faut un objet spécialisé possédant les credentials de l’utilisateur connecté. Cet objet, de type « CloudBlobClient » est créé à partir des informations d’authentification en utilisant la fonction « createCloudBlobClient » du compte utilisateur que nous avons précédemment stocké dans la classe « Global ».
Nous allons stocker cet objet au niveau des variables globales de l’activité « BlobsActivity » pour le réutiliser sans avoir à le résoudre à chaque fois :
if (blobClient == null)
{
blobClient = Global.account.createCloudBlobClient();
}
| Une fois que l’on possède un client de gestion des blobs, il nous faut une référence vers le blob container que l’on veut lister. Il suffit d’utiliser la fonction « getContainerReference » du client de gestion des blobs avec en paramètre le nom du container :
CloudBlobContainer wContainer = blobClient.getContainerReference("photos3");
| Maintenant que nous avons une référence vers le container qui nous intéresse, il suffit de lister son contenu. Mais avant de faire cela, il est vivement conseillé de le créer (si besoin uniquement) pour éviter des erreurs liées au fait qu’il n’existe pas. C’est la méthode « createIfNotExists » du container qu’il faut utiliser :
if (wContainer.createIfNotExist())
{
// On liste les blobs présents dans ce container
}
| Pour lister le contenu du blob container, il suffit de parcourir la collection des blobs retournée par la fonction « listBlobs » du container. Cette fonction prend en paramètre le préfix des blobs à remonter (c’est une manière de filtrer les résultats et ainsi optimiser le temps de réponse) et un booléen stipulant si l’on souhaite une version « plate » ou « hiérarchique » des informations (voir la documentation de Windows Azure pour mieux comprendre la différence entre les deux manières de remonter les informations). Nous allons utiliser une vue « plate » car nous n’avons dans cet exemple aucune notion de hiérarchie dans notre blob container :
for (CloudBlob wBlob : wContainer.listBlobs("", true))
{
// Ajouter ici chaque blob remonté dans une collection privée pour affichage
}
| Voilà, l’enchainement des actions à réaliser pour lister le contenu d’un blob container est aussi simple que ça !
Comme pour les fonctions d’authentification, nous allons utiliser les services d’une AsyncTask pour effectuer les opérations de manière asynchrones.
Pendant l’opération le bouton de prise de photos et la liste des blobs sont désactivés, l’image est cachée et la progress bar est affichée pour faire patienter l’utilisateur. En cas de problème, un message d’erreur est affiché avec la raison du problème rencontré.
Un adapter a été créé pour lister les noms des blobs retournés. Cet adapter est « nourri » avec les noms de blobs au fur et à mesure de leur itération.
L’architecture de cette AsyncTask est très classique. En voici le code complet :
private class ListerBlobsTask extends AsyncTask
{
private Exception _exception = null;
@Override
protected Void doInBackground(Void... params)
{
try
{
// On récupère le blob client si on ne l'a pas déjà
if (blobClient == null)
{
blobClient = Global.account.createCloudBlobClient();
}
// On crée la référence vers le container que l'on veut lister
CloudBlobContainer wContainer = blobClient.getContainerReference("photos3");
if (wContainer.createIfNotExist())
{
// On liste les blobs présents dans ce container
for (CloudBlob wBlob : wContainer.listBlobs("", true))
{
blobsAdapter.blobs.add(wBlob.getName());
}
}
}
catch (Exception ex)
{
_exception = ex;
}
return null;
}
protected void onPostExecute(Void result)
{
progressBar.setVisibility(View.GONE);
if (this._exception != null)
{
Global.ShowMessage(BlobsActivity.this,
"Une erreur s'est produite !",
this._exception.getMessage());
}
else
{
blobsAdapter.notifyDataSetChanged();
bouPhoto.setEnabled(true);
lvBlobs.setEnabled(true);
}
}
}
| Voici le code de l’adapter utilisé pour afficher les blobs :
private class BlobsAdapter extends BaseAdapter
{
private LayoutInflater layoutInflater;
public ArrayList blobs;
public BlobsAdapter(Context wContext)
{
blobs = new ArrayList();
layoutInflater = LayoutInflater.from(wContext);
}
@Override
int getCount()
{
if (blobs == null)
{
return 0;
}
else
{
return blobs.size();
}
}
@Override
public Object getItem(int position)
{
return blobs.get(position);
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
convertView = layoutInflater.inflate(R.layout.blobitem, null);
}
TextView txtBlobName = (TextView)convertView.findViewById(R.id.txtBlobName);
txtBlobName.setText(blobs.get(position));
return convertView;
}
}
| Et enfin, l’appel à la tâche permettant de lister les blobs présents dans le container. Cet appel est lancé directement depuis la méthode « onCreate » de l’activité :
ListerBlobsTask wTask = new ListerBlobsTask();
wTask.execute();
|
Prendre une photo et la stocker dans Windows Azure En soit, prendre une photo n’est pas très compliqué. Il suffit de demander au système de s’en occuper en lançant une intent adaptée. La photo sera stockée dans le fichier « capture.jpg » sur la carte mémoire d’extension :
public static final int REQUEST_CAPTURE = 1;
@Override
public void onClick(View v)
{
if (v == bouPhoto)
{
ivBlob.setVisibility(View.GONE);
Intent wIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File wFile = new File(Environment.getExternalStorageDirectory(), "capture.jpg");
Uri wOutputFileUri = Uri.fromFile(wFile);
wIntent.putExtra(MediaStore.EXTRA_OUTPUT, wOutputFileUri);
startActivityForResult(wIntent, BlobsActivity.REQUEST_CAPTURE);
}
}
| Au retour de l’activité de capture, on peut lancer la tâche qui sera chargée du stockage de l’image dans Windows Azure :
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == REQUEST_CAPTURE)
{
// Réponse de l'activité de capture caméra
// resultCode = RESULT_OK -> La capture de se trouve dans le fichier capture.jpg
if (resultCode == RESULT_OK)
{
// On compose le nom de l'image source
String wFilename = Environment.getExternalStorageDirectory() + "/capture.jpg";
// On affiche la progress bar
bouPhoto.setEnabled(false);
lvBlobs.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
// On lance la tâche d'ajout de l'image au container
UploadBlobTask wTask = new UploadBlobTask();
wTask.execute(wFilename);
}
}
}
| Reste maintenant à définir la tâche de stockage, qui sera, comme d’habitude dérivée de la classe AsyncTask.
Pour stocker un nouveau blob il faut quatre éléments :
Un client de gestion des blob (on connaît déjà)
Une référence vers le blob container (on connaît aussi)
Un nom de blob
Une source de données (ici les octets composant l’image)
On récupère une référence vers le blob container :
CloudBlobContainer wContainer = blobClient.getContainerReference("photos3");
| Nous allons donner à nos images un nom basé sur la date et l’heure du moment :
Calendar c = Calendar.getInstance();
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String wName = df.format(c.getTime()).concat(".jpg");
| Et créer maintenant une référence vers le blob à créer dans le blob container. La référence est gérée par un objet de type « CloudBlockBlob » :
CloudBlockBlob wBlob = wContainer.getBlockBlobReference(wName);
| Nous avons tout maintenant pour récupérer le train d’octets de l’image et l’uploader vers Windows Azure :
// wImageName contient le chemin vers le fichier image à utiliser
Bitmap bitmap = BitmapFactory.decodeFile(wImageName);
OutputStream stream = wBlob.openOutputStream();
bitmap.compress(CompressFormat.JPEG, 75, stream);
stream.close();
| On peut aussi associer au blob des métadonnées :
wBlob.getProperties().contentType = "image/jpeg";
wBlob.uploadProperties();
| Avant d’uploader les données, il est recommandé de vérifier que le blob container existe (comme nous l’avons déjà fait au moment de lister son contenu).
Comme vous pouvez le constater, rien de très compliqué à faire.
Voici le code complet de cette nouvelle AsyncTask :
private class UploadBlobTask extends AsyncTask
{
private Exception _exception = null;
@Override
protected Void doInBackground(String... params)
{
// On récupère le nom de l'image à uploader
String wImageName = params[0];
try
{
// On crée la référence vers le container que l'on veut utiliser
CloudBlobContainer wContainer = blobClient.getContainerReference("photos3");
if (wContainer.createIfNotExist())
{
Calendar c = Calendar.getInstance();
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String wName = df.format(c.getTime()).concat(".jpg");
CloudBlockBlob wBlob = wContainer.getBlockBlobReference(wName);
Bitmap bitmap = BitmapFactory.decodeFile(wImageName);
OutputStream stream = wBlob.openOutputStream();
bitmap.compress(CompressFormat.JPEG, 75, stream);
stream.close();
wBlob.getProperties().contentType = "image/jpeg";
wBlob.uploadProperties();
blobsAdapter.blobs.add(wName);
}
}
catch (Exception ex)
{
_exception = ex;
}
return null;
}
protected void onPostExecute(Void result)
{
progressBar.setVisibility(View.GONE);
bouPhoto.setEnabled(true);
lvBlobs.setEnabled(true);
if (this._exception != null)
{
Global.ShowMessage(BlobsActivity.this,
"Une erreur s'est produite !",
this._exception.getMessage());
}
else
{
blobsAdapter.notifyDataSetChanged();
}
}
}
|
Récupérer le contenu d’un blob et l’afficher Vous vous en doutez bien, pour récupérer le contenu d’un blob il nous faut les mêmes informations que pour le créer :
Un client de gestion des blob
Une référence vers le blob container
Le nom du blob à récupérer
Il faut utiliser la méthode « download » de la classe « CloudBlockBlob » pour récupérer les données (train d’octets) du blob.
Je vous donne directement le code de l’AsyncTask correspondante car maintenant vous être habitué à ce type de classe :
private class DownloadBlobTask extends AsyncTask
{
private Exception _exception = null;
private Bitmap _bitmap = null;
@Override
protected Void doInBackground(String... params)
{
try
{
// On récupère le nom du blob passé en paramètres
String wBlobName = params[0];
// On récupère le blob client si on ne l'a pas déjà
if (blobClient == null)
{
blobClient = Global.account.createCloudBlobClient();
}
// On crée la référence vers le container qui stocke le blob
CloudBlobContainer wContainer = blobClient.getContainerReference("photos3");
// On crée la référence vers le blob à télécharger
CloudBlockBlob wBlockBlob = wContainer.getBlockBlobReference(wBlobName);
// On télécharge son contenu
ByteArrayOutputStream wOutputStream = new ByteArrayOutputStream();
wBlockBlob.download(wOutputStream);
byte[] wContent = wOutputStream.toByteArray();
// On l'affiche
ByteArrayInputStream wInputStream = new ByteArrayInputStream(wContent);
_bitmap = BitmapFactory.decodeStream(wInputStream);
wInputStream.close();
}
catch (Exception ex)
{
_exception = ex;
}
return null;
}
protected void onPostExecute(Void result)
{
progressBar.setVisibility(View.GONE);
if (this._exception != null)
{
Global.ShowMessage(BlobsActivity.this,
"Une erreur s'est produite !",
this._exception.getMessage());
}
else if (_bitmap != null)
{
ivBlob.setImageBitmap(_bitmap);
ivBlob.setVisibility(View.VISIBLE);
}
bouPhoto.setEnabled(true);
lvBlobs.setEnabled(true);
}
}
| Dans cet exemple, on récupère le contenu d’un blob avec un appui long sur son nom dans la liste. Voici le code d’appel correspondant :
@Override
public boolean onItemLongClick(AdapterView> arg0, View arg1, int arg2, long arg3)
{
// On récupère le nom du blob à afficher
String wName = blobsAdapter.blobs.get(arg2);
// On prépare l'interface
bouPhoto.setEnabled(false);
lvBlobs.setEnabled(false);
ivBlob.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
// On lance le téléchargement du blob
DownloadBlobTask wTask = new DownloadBlobTask();
wTask.execute(wName);
return true;
}
| Supprimer un blob Nous ne l’avons pas implémenté dans cet exemple mais la suppression d’un blob est une opération très simple à réaliser. Il suffit d’invoquer la méthode « delete » de la classe « CloudBlockBlob » pour réaliser l’opération. Comme toujours, il faudra réaliser cette opération dans une AsyncTask, mais ça maintenant vous en avez pris l’habitude.
Conclusion Voilà, nous avons fait une petite visite guidée de l’utilisation des blobs avec WAT. Comme vous pouvez le constater ce n’est pas très compliqué et ça fonctionne très bien. |