Les joies de coder une application s’exécutant dans un lecteur vidéo

Le titre de cet article ne le mentionne pas explicitement mais nous allons parler ici de Flex et de son socle d’exécution : le Flash Player.

Pour rappel, développer en Flex revient grosso modo à coder le métier de votre application en ActionScript 3 (syntaxe à mi-chemin entre le Java et le JavaScript) et coder les vues en MXML (syntaxe dérivée du XML). Et malgré la relative simplicité d’utilisation du MXML pour décrire les composants graphiques, il peut arriver d’avoir quelques surprises sur des morceaux de code très simples.

Pour exemple la méthode ci-dessous qui, insérée dans le code d’une vue assez complexe, lèvera aléatoirement une NullPointerException (ou tout du moins son équivalent en Flex) à la ligne 6, nous indiquant que l’objet Label n’est pas encore disponible (alors qu’il semble parfaitement instancié plus bas).

1
2
3
4
5
6
7
/**
*
* @param value
*/

public function updateLabel(value:String):void {
    _myLabel.text = value;
}
1
<mx:Label id="_myLabel"/>

Explications

Là où le bytecode Java s’exécute dans une JVM, son équivalent en Flex (le SWF)  s’exécute dans le Flash Player, or celui-ci repose sur un noyau qui n’est rien d’autre qu’un lecteur vidéo ! (car avant d’exécuter des applications d’entreprise, le Flash Player servait exclusivement de moteur de rendu d’images et d’animations).

Ainsi, votre application Flex (ou vidéo Flex d’un point de vue de ce noyau) s’exécute en suivant un mode de traitement en image par secondes. Le traitement de chacune de ces images étant consacré à deux opérations distinctes :

  • L’exécution pure et simple du code
  • Le rendu des objets graphiques sur la scène de l’application

Or ces opérations ne se voient pas accorder le même temps d’exécution d’une image à l’autre. Cela peut dépendre de nombreux paramètres aussi variés qu’imprévisibles allant du nombre d’images par seconde de l’application (le frame rate) , au navigateur ou système d’exploitation utilisé en passant par la manière de compiler le SWF, sans oublier bien sûr la complexité de votre code à exécuter ou la richesse de vos vues à rendre.

Et en ce qui nous concerne dans l’exemple ci-dessus, il faut savoir que l’instanciation des composants déclarés en MXML se fait par défaut lorsque le composant devient “affichable” (cela permet d’éviter d’instancier toutes les vues d’un coup lors du démarrage de l’application).

C’est pour cela que dans le cas de figure décrit ci-dessous, aucune erreur ne sera levée car l’exécution du code accédant au Label arrive après son rendu sur la scène.

Alors que dans le cas de figure suivant, c’est le contraire et la fameuse exception est levée.

En ce qui nous concerne, il faut donc que le rendu d’un composant soit terminé avant de pouvoir accéder à sa référence, voici trois solutions pour s’assurer de ce résultat.

Trois solutions possibles

Faire appel à la méthode callLater

Cette méthode peu connue est accessible à toute classe héritant de UIComponent (autant dire quasiment tous les composants graphiques) . Elle indique au Flash Player de reporter l’exécution de la méthode passée en paramètre à la prochaine image.

Il suffit alors d’utiliser un flag couplé à l’écoute de l’événement creationComplete de la vue pour accéder à notre propriété en toute quiétude.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//flag
private var _isViewCreated:Boolean = false;

/**
*
* @param event
*/

private function creationCompleteHandler(event:FlexEvent):void {
    _isViewCreated = true;
}

/**
*
* @param value
*/

public function updateLabel(value:String):void {
    if(_isViewCreated) {
        _myLabel.text = value;
    }else{
        callLater(updateLabel,[value]);
    }
}
1
<mx:Label id="_myLabel"/>

En effet l’événement creationComplete est lancé quand le Flash Player a fini d’initialiser et de rendre la vue et ses composants. Ainsi si le flag _isViewCreated est à true, on est assuré de pouvoir accéder à la référence du Label.

Utiliser le data binding

Le data binding en Flex est le mécanisme permettant d’espionner un objet et de propager automatiquement une modification de celui-ci aux autres objets qui l’écoutent.

Dans notre cas, il va nous permettre de faire la même chose que la méthode callLater mais de manière transparente pour le développeur.

1
2
3
4
5
6
7
8
9
10
[Bindable]
private var _labelText:String;

/**
*
* @param value
*/

public function updateLabel(value:String):void {
    _labelText = value;
}
1
<mx:Label text="{_labelText }"/>

Ici, la variable _labelText est indiquée “bindable”, ce qui veut dire que la propriété text du Label sera notifiée automatiquement d’un changement de sa valeur (à cause de la syntaxe “{_labelText }“). Cerise sur le gâteau, que la référence du Label ne soit pas encore accessible ou que _labelText ne soit pas encore setté, aucune exception ne sera remontée à l’utilisateur, le data binding l’occultera.

Cette méthode reste la moins verbeuse et la plus confortable pour le développeur mais le data binding reste toutefois un mécanisme à utiliser avec parcimonie à cause de la montagne de code généré qu’il engendre à la compilation et les effets de bord qui peuvent se produire dans une application trop complexe.

Recourir aux méthodes du framework gérant le cycle de vie d’un composant

Cette troisième et dernière solution n’est pas la plus simple à mettre en place mais elle a le mérite d’être la plus robuste et plus performante.

En effet, pour éviter d’effectuer plusieurs fois le même travail inutilement lors du traitement des images décrit plus haut, le framework Flex met à disposition un mécanisme d’invalidation.

Par exemple, si vous voulez changer la couleur et la taille du texte d’un Label, le Flash Player ne va pas effectuer le rendu de la nouvelle couleur aussitôt après que vous l’ayez changée puis effectuer le changement de taille, il va plutôt marquer ces deux attributs comme “invalides” et reporter le rendu final du composant dans une seule et même opération.

Ce mécanisme permet donc de gagner en performance mais aussi de s’assurer que nos objets graphiques sont bien disponibles.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// flag
private var _labelTextChanged:Boolean = false;
private var _labelText:String = null;

/**
*
* @param value
*/

public function updateLabel(value:String):void {
    _labelText = value;
    if(_labelText != value) {
        _labelTextChanged = true;
        invalidateProperties();
    }
}

/**
* @inherited
*/

override protected function commitProperties():void {
    super.commitProperties();
    if(_labelTextChanged) {
        if(_myLabel != null) {
            _myLabel.text = _labelText;
        }
    }
}
1
<mx:Label id="_myLabel"/>

La méthode commitProperties est appelée par le framework Flex à la suite d’un appel à la méthode invalidateProperties ou addChild et elle a pour mission de traiter les propriétés de  la classe qui ont changé de valeur, en l’occurrence ici on s’occupe de la propriété _labelText.

Conclusion

J’ai illustré par cet exemple les problèmes quelques peu déroutants liés à la nature du Flash Player et au fort asynchronisme du framework Flex mais il existe bien d’autres situations similaires dont les solutions décrites plus haut resteraient parfaitement valables (comme accéder “trop tôt” à la largeur d’un composant, renvoyant une largeur à zéro et faussant tous les calculs à venir).

Enfin, je n’ai parlé que brièvement du data binding et des méthodes du cycle de vie d’un composant mais ces deux vastes sujets en Flex mériteraient largement qu’un article leur soit consacré, à suivre donc.

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

Laisser un commentaire