API MassLoad (Préchargement en masse)

Ce tutoriel explique comment utiliser le framework de préchargement en masse (MassLoader) téléchargeable ci-dessous dans une animation flash. Afin de comprendre le fonctionnement, je vous conseille de bien connaître les subtilités relatives aux chargement de fichiers (URLLoader, URLRequest, Loader, LoaderContext).

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

0. Problèmatique

Charger un fichier texte, xml, image ou swf est simple avec AS3 et vous avez plusieurs manières de le faire, que ce soit via Loader (pour les fichiers binaires) ou URLLoader (pour tous les types de fichier). Cela devient un peu plus compliqué à gérer lorsqu'il s'agit de charger toute une liste de fichiers. De plus, il arrive souvent que ceux-ci soient inter-dépendants.
Imaginons un site web en flash : il arrive très souvent que lorsqu'un internaute demande d'afficher une page, plusieurs fichiers doivent être chargés (les données issues d'un php, le swf d'affichage, des images, ...). Comment faire en sorte que tout cela devienne beaucoup plus simple pour nous autres développeurs ?

I. Solution

Le petit framework téléchargeable ci-dessous vous permet justement de charger des fichiers en masse très simplement. Cela se passe en deux parties :

  • création d'un fichier téléchargeable
  • ajout du fichier dans la liste de chargement (le MassLoader)

I-A. Framework MassLoad

Pour utiliser les exemples montrés ci-dessous, il faut télécharger le framework MassLoad suivant : API MassLoad ( miroir )

I-B. Fichier téléchargeable

kézako ? Afin de pouvoir utiliser le plus simplement possible le MassLoader, il faut utiliser un type standard. Celui-ci est représenté par l'interface ILoadableFile qui permet d'utiliser n'importe quel type de gestionnaire de chargement URLLoader ou Loader. Pour créer un fichier à télécharger, voici la procédure à suivre :

 
Sélectionnez
import ch.capi.net.*;

var factory:LoadableFileFactory = new LoadableFileFactory();
var file1:ILoadableFile = factory.create("data/file.txt");

Le code ci-dessus va nous créer automatiquement un objet ILoadableFile qui utilisera un objet URLLoader afin de charger les données. L'utilisation de la classe URLLoader est définie par l'extension du fichier. Toutefois il est aussi possible de choisir manuellement quel gestionnaire de chargement utiliser :

 
Sélectionnez
import ch.capi.net.*;
import flash.net.*;
import flash.display.*;

var factory:LoadableFileFactory = new LoadableFileFactory();
var file1:ILoadableFile = factory.createURLLoaderFile(new URLRequest("data/file.txt")); //utilisation d'un URLLoader
var file2:ILoadableFile = factory.createLoaderFile(new URLRequest("data/file.swf")); //utilisation d'un Loader

//récupération des gestionnaires de chargement
var loader1:URLLoader = file1.loadManagerObject;
var loader2:Loader = file2.loadManagerObject;

Les gens qui ont jeté un coup d'oeil à la documentation auront noté que les objets de type ILoadableFile possèdent une propriété virtualBytesTotal qui peut sembler très abstraite. Celle-ci sert au pré-calcul des bytes total afin que le MassLoader puisse fournir une évaluation du volume total des données à charger.
Par défaut cette valeur est fixée à 204800 bytes, ce qui correspond à 200ko. Elle est attribuée à tous les fichiers créés par la classe LoadableFileFactory et peut être modifiée de la sorte :

 
Sélectionnez
import ch.capi.net.*;

var factory:LoadableFileFactory = new LoadableFileFactory(102400);
var file1:ILoadableFile = factory.create("data/file.txt");
trace(file1.virtualBytesTotal); //102400

Nous allons voir par la suite quelle est l'utilité de cette propriété !

I-C. Gestionnaire de chargement

Ceci est simplement la classe qui va gérer le chargement de tous les fichiers se trouvant dans sa liste. Il s'agit de la classe MassLoader qui permet de charger les fichiers de manière parallèle (plusieurs fichiers à la fois) ou séquentielle (1 à 1).
Une fois que nos fichiers ont été créés, il suffit de les ajouter à la liste de chargement et de lancer le tout :

 
Sélectionnez
import ch.capi.net.*;

//création des fichiers
var factory:LoadableFileFactory = new LoadableFileFactory();
var file1:ILoadableFile = factory.create("data/file1.txt");
var file2:ILoadableFile = factory.create("data/file2.txt");
var file3:ILoadableFile = factory.create("swf/file.swf");
var file4:ILoadableFile = factory.create("xml/file.xml");
						
//création du loader
var loader:MassLoader = new MassLoader();
						
//ajout des fichiers dans la liste de chargement
loader.addFile(file1);
loader.addFile(file2);
loader.addFile(file3);
loader.addFile(file4);
						
//lancement du chargement
loader.start();

Il est possible de spécifier le nombre de fichiers à télécharger en même temps. Dans l'exemple ci-dessus, aucune valeur n'étant spécifiée, le chargement de tous les fichiers en parallèle va être lancé. Quelques exemples (il est également possible d'utiliser la propriété parallelFiles) :

 
Sélectionnez
import ch.capi.net.*;

var loader1:MassLoader = new MassLoader(); //tous les fichiers en même temps
var loader2:MassLoader = new MassLoader(1); //1 fichier par 1 (loader séquentiel)
var loader3:MassLoader = new MassLoader(3); //3 fichiers par 3

I-D. Evénements

La classe MassLoader s'occupe de gérer différents événements :

  • Event.OPEN : lorsque le chargement des fichiers est lancé (loader.start())
  • Event.CLOSE : lorsque le chargement est stoppé (loader.stop())
  • Event.COMPLETE : lorsque tous les fichiers ont été chargés
  • ProgressEvent.PROGRESS : lorsque le chargement des données avance
  • MassLoadEvent.FILE_OPEN : lorsque le chargement d'un des fichiers de la liste commence
  • MassLoadEvent.FILE_CLOSE : lorsque le chargement d'un des fichiers de la liste est fini (qu'il y ait une erreur ou non)
 
Sélectionnez
import ch.capi.net.*;
import ch.capi.events.*;
import flash.events.*;
						
//création des fichiers
var factory:LoadableFileFactory = new LoadableFileFactory();
var file1:ILoadableFile = factory.create("data/file1.txt");
var file2:ILoadableFile = factory.create("data/file2.txt");
var file3:ILoadableFile = factory.create("swf/file.swf");
var file4:ILoadableFile = factory.create("xml/file.xml");
						
//création du loader
var loader:MassLoader = new MassLoader();
						
//ajout des fichiers dans la liste de chargement
loader.addFile(file1);
loader.addFile(file2);
loader.addFile(file3);
loader.addFile(file4);
						
//écouteur
var l:Function = function(evt:Event):void
{
	trace("event : "+evt.type);
}
						
loader.addEventListener(Event.OPEN, l);
loader.addEventListener(Event.CLOSE, l);
loader.addEventListener(Event.COMPLETE, l);
loader.addEventListener(MassLoadEvent.FILE_OPEN, l);
loader.addEventListener(MassLoadEvent.FILE_CLOSE, l);
						
//lancement du chargement
loader.start();

sortie du code précédent :

 
Sélectionnez
event open
event fileOpen
event fileOpen
event fileOpen
event fileOpen
event fileClose
event fileClose
event fileClose
event fileClose
event complete

En admettant que le MassLoader utilisé soit défini comme séquentiel (parallelFiles = 1), la trace ressemblerait à ceci :

 
Sélectionnez
event open
event fileOpen
event fileClose
event fileOpen
event fileClose
event fileOpen
event fileClose
event fileOpen
event fileClose
event complete	

I-E. Données de progression

Je détaille un peu plus ce qui se passe avec la gestion des données bytesLoaded et bytesTotal à l'intérieur du framework.
Lorsque l'on charge plusieurs fichiers d'un coup, on s'intéresse souvent à créer une barre de chargement reflétant l'avancement globale du chargement. Hors cela n'est possible que si l'on connait à l'avance le poid total (bytesTotal) de chacun des fichiers. Comme cela n'est pas possible, le framework a recours à la propriété virtualBytesTotal afin de renvoyer le poids total des fichiers :

 
Sélectionnez
import ch.capi.net.*;

//création des fichiers
var factory:LoadableFileFactory = new LoadableFileFactory(1000);
var file1:ILoadableFile = factory.create("data/file1.txt");
var file2:ILoadableFile = factory.create("data/file2.txt");
var file3:ILoadableFile = factory.create("swf/file.swf");
var file4:ILoadableFile = factory.create("xml/file.xml");
						
//création du loader
var loader:MassLoader = new MassLoader();
						
//ajout des fichiers dans la liste de chargement
loader.addFile(file1);
loader.addFile(file2);
loader.addFile(file3);
loader.addFile(file4);

						
trace(loader.bytesTotal); //4000 (chacun des fichiers a 1000 bytes comme valeur pour virtualBytesTotal)

Au fur et à mesure que le MassLoader récupère les données bytesTotal correctes de chacun des fichiers, la valeur bytesTotal de l'objet MassLoader sera ajustée. Le but est que la valeur virtualBytesTotal soit la plus proche possible de la taille réelle du fichier. Si la taille n'est pas fixe (fichier généré via php par exemple), l'idéal est que la valeur soit au-dessus de la taille réel pour éviter que la valeur bytesTotal de l'objet MassLoader soit ajustée vers le haut !

 
Sélectionnez
import ch.capi.net.*;
import flash.events.*;
						
//création des fichiers
var factory:LoadableFileFactory = new LoadableFileFactory();
var file1:ILoadableFile = factory.create("data/file1.txt");
var file2:ILoadableFile = factory.create("data/file2.txt");
var file3:ILoadableFile = factory.create("swf/file.swf");
var file4:ILoadableFile = factory.create("xml/file.xml");
						
//création du loader
var loader:MassLoader = new MassLoader();
						
//ajout des fichiers dans la liste de chargement
loader.addFile(file1);
loader.addFile(file2);
loader.addFile(file3);
loader.addFile(file4);
						
//écouteur
var p:Function = function(evt:ProgressEvent):void
{
   trace(evt.bytesLoaded+" / "+evt.bytesTotal);
}
						
loader.addEventListener(ProgressEvent.PROGRESS, p);
loader.start();

sortie :

 
Sélectionnez
0 / 614485
85 / 614485
85 / 409770
170 / 409770
170 / 205055
255 / 205055
255 / 340
340 / 340
340 / 340

Pourquoi la valeur des bytesTotal baisse-t-elle ? En fait, si la valeur bytesTotal de l'objet ILoadableFile est égale à 0, alors le MassLoader va utiliser la valeur virtualBytesTotal pour compenser. Une fois la valeur de bytesTotal connue, c'est elle qui sera utilisée ! Dans l'exemple ci-dessus, la valeur baisse énormément car les fichiers sont très petits.
Cela va-t-il poser problème pour mes barres de preload ? Tout dépend de comment vous l'avez codée... A forciori si vous utilisez un ratio (ou un pourcentage), cela ne posera aucun problème car la valeur bytesLoaded ne sera jamais plus grande que bytesTotal.

I-F. Erreur de chargement

La classe MassLoader ne s'occupe pas de savoir si le chargement d'un fichier a été fait correctement ou non. Elle enverra toujours les événements MassLoadEvent.FILE_OPEN lorsqu'elle lance le chargement d'un fichier et MassLoadEvent.FILE_CLOSE lorsque le chargement de celui-ci est fini (avec erreur ou non). Afin de pouvoir gérer finement les erreurs qui pourraient survenir, voila une solution :

 
Sélectionnez
var onFileOpen:Function = function(evt:MassLoadEvent):void
{
	var file:ILoadableFile = evt.file;
	var loadManager:IEventDispatcher = file.loadManagerObject as IEventDispatcher;
						
	loadManagerObject.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
	loadManagerObject.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
}
var onFileClose:Function = function(evt:MassLoadEvent):void
{
	var file:ILoadableFile = evt.file;
	var loadManager:IEventDispatcher = file.loadManagerObject as IEventDispatcher;
						
	loadManagerObject.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
	loadManagerObject.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
}
var onIOError:Function = function(evt:IOErrorEvent):void
{
	trace("IOError");
}
var onSecurityError:Function = function(evt:SecurityErrorEvent):void
{
	trace("SecurityError");
}
						
var massloader:MassLoader = new MassLoader();
massloader.addEventListener(MassLoadEvent.FILE_OPEN, onFileOpen);
massloader.addEventListener(MassLoadEvent.FILE_CLOSE, onFileClose);

I-G. Modélisation

Pour les gens s'intéressant un peu à la modélisation de l'API et qui souhaite avoir une vue d'ensemble de l'architecture, ci-dessous un schéma représentatif des liens entre les différents éléments :

Modélisation de l'API
  • En blanc : les classes publiques
  • En bleu : les interfaces publiques
  • En grisé : les classes internes

Où sont ces classes ? Elles se trouvent toutes dans le package ch.capi.net !
C'est quoi ces classes internes en grisé ? Je ne les vois pas dans la doc... Et oui ! Ce sont des classes qui implémentent l'interface ILoadableFile. Celles-ci sont masquées car elles seront automatiquement instanciées par la classe LoadableFileFactory suivant ce que vous demandez (cf les méthodes de cette classe). En gros, vous n'avez besoin que de savoir que vous récupérez un objet de type ILoadableFile.
Tiens, par contre, je vois une classe MassLoadOrganizer... C'est une classe concernant la gestion de chargement de fichiers inter-dépendants. Cette classe n'est pas encore finalisée (elle va de paire avec la classe OrganizedFile) et fera l'objet d'un prochain tutorial.
Ca veut dire quoi 'aggregates' ? Cela signifie simplement que l'interface IMassLoader (ou plutôt ses implémentations) stockera des objets ILoadManager.
Et le 'uses' pour LoadableFileFactory ? La classe LoadableFileFactory s'occupe d'instancier les classes ULoadableFile et LLoadableFile.
Pourquoi la classe AbstractLoadableFile n'implémente pas l'interface ILoadableFile ? Cette classe existe par souci de simplicité envers les implémentations de l'interface ILoadableFile. Ainsi les classes héritant de la classe AbstractLoadableFile qui doivent implémenter cette interface n'auront plus qu'à implémenter les méthodes start() et stop() sans se soucier des propriétés !

II. FAQ

Comment installer cette API ? Il faut que le dossier 'ch' soit dans votre classpath (dans les paramètres de publication) ou alors simplement dans le même dossier que le fichier fla.
Est-ce que cela marche avec Flash 6/7/8 ? Non. Elle est uniquement compatible Flash 9 / AS3, mais si vous êtes intéressé à la porter en AS2... ;)

Quelle est la dernière version ? La version 2.0
Est-ce que je peux modifier la valeur de la propriété parallelFiles durant un chargement ? Oui, mais cela n'affectera pas le chargement en cours, il faut le stopper, puis le redémarrer.
Si je stoppe le chargement et je le relance, est-ce que tous les fichiers seront rechargés ? Non, seuls ceux qui n'ont pas été chargés (ou seulement partiellement) seront relancés.
Après que le MassLoader ait fini de tout charger, la liste des fichiers récupérés par la méthode getFiles est vide... Normal ! Lorsqu'un fichier a été chargé, celui-ci est enlevé de la liste de fichiers à charger et l'événement MassLoadEvent.FILE_CLOSE est envoyé. Toutefois les valeurs des propriétés bytesLoaded et bytesTotal sont maintenues à jour.
Je vois plein d'autres classes dans ch.capi.data. Ca sert à quoi ? Ce sont des classes qui servent à la gestion des données. Elles sont en partie utilisée dans l'API MassLoad, mais peuvent sans aucun problème être utilisées dans un contexte totalement différent. Vous trouverez des articles sur ces structures sur le blog d'ekameleon.

Cette API m'intéresse ! Est-ce que je peux l'utiliser dans mes projets personnels/professionnels ? Aucun problème :) C'est fait pour ça !
J'aimerais proposer des améliorations/suggestions pour cette API. A qui dois-je m'adresser ? Lancez simplement un sujet sur le forum Flash ou alors contactez-moi. Vos idées/remarques/critiques sont les bienvenues !

III. Conclusion

L'utilisation de cette API simplifie grandement la gestion de préchargement en masse et est facilement abordable par les développeurs moins expérimentés en restant très souple pour ceux qui voudraient approfondir/améliorer son fonctionnement. Le framework offre encore d'autres possibilités qui sont répertoriées dans la documentation.

IV. Sources, documentation et exemples

L'API est téléchargeable ici : MassLoad API 2.0 ( miroir )
Il y a un exemple d'utilisation téléchargeable la : Exemples ( miroir )

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Ce document est à destination de tout le monde