AIR 2 : appeler un JAR exécutable via un NativeProcess


Vous avez certainement pu remarquer l’apparition attendue de la fonctionnalité d’exécution de code natif parmi les nouveautés de AIR 2.0. Les NativeProcess permettent d’étendre significativement les possibilités offertes par une application AIR.
Mais, l’un des gros atouts d’Adobe AIR résidant dans sa nature multi-plateforme, il serait dommage de restreindre l’installation de votre application à un seul système d’exploitation… ça tombe bien, Java aussi est multi-plateforme (c’est bien l’open source) : OK du coup comment j’appelle du Java depuis mon AIR ?
Explications…

Fonctionnement et limites d’un NativeProcess

Packaging de l’application

Pour diverses raisons de sécurité, une application AIR utilisant un NativeProcess doit être packagée dans le format de la plateforme cible (exe pour Windows, dmg pour Mac, rpm / deb / … pour Linux). Fini donc dans ce cas le pratique fichier .air qui marche partout !
Vous devez également indiquer dans le descripteur de l’application qu’il s’agit d’une application au profil « extendedDesktop » lui conférant donc les capacités d’exécuter un NativeProcess.
Vous devez ouvrir le fichier monAppli-app.xml et modifier la balise <supportedProfiles>

<supportedProfiles>extendedDesktop</supportedProfiles>

A partir de là, si vous tentez d’exporter un fichier .air, vous aurez un joli message d’erreur vous invitant à changer de profil.

Considérations de sécurité

Si AIR sans native process dispose de limitations de sécurité, dès lors que vous utilisez un native process, vous ouvrez la porte… Il est donc bon d’anticiper et de faire un maximum de vérifications sur vos appels pour éviter un remplacement d’un exécutable par un autre, moins sympathique, par exemple…

Prêt ? On code

Paramétrage

Un native process a besoin d’un contexte d’exécution. Ce contexte est défini à l’aide de l’objet NativeProcessStartupInfo décrivant notamment les éléments suivants :

  • workingDirectory : le répertoire d’exécution
  • executableFile : l’exécutable à lancer
  • arguments : la liste des arguments à passer à l’exécutable (Vector.<String> sous Flash Player 10, ou un bête Array sinon)

Vous pourrez alors instancier un objet NativeProcess et lui passer cet objet de paramétrage avant de le lancer.

Lancement & suivi de l’exécution

Le lancement d’un NativeProcess est asynchrone. Il propose donc un certain nombre d’événements vous permettant de suivre son exécution une fois lancé, notamment :

  • ProgressEvent.STANDARD_OUTPUT_DATA : écriture sur la sortie standard
  • ProgressEvent.STANDARD_ERROR_DATA : écriture sur la sortie d’erreur
  • IOErrorEvent.STANDARD_OUTPUT_IO_ERROR : déclenché lorsqu’une erreur apparaît en tentant de lire la sortie standard
  • IOErrorEvent.STANDARD_ERROR_IO_ERROR : déclenché lorsqu’une erreur apparaît en tentant de lire la sortie d’erreur
  • NativeProcessExitEvent.EXIT : déclenché lors que l’exécution du process est terminée

Lecture de l’entrée / sortie standard

Bien que tout à fait possible, la lecture de la sortie standard ou d’erreur via le listener ProgressEvent.STANDARD_OUTPUT_DATA ou ProgressEvent.STANDARD_ERROR_DATA n’est pas fiable à 100% concernant le contenu retourné : la sortie étant « bufferisée », vous n’aurez donc pas systématiquement le même résultat selon la machine exécutant le programme, à chaque appel de votre listener… N’utilisez donc la lecture de sortie standard que pour afficher ce qui se passe, ou à la limite récupérer la totalité du contenu écrit une fois le process terminé…

Appeler un JAR

Rappels

Pour appeler un JAR en Java, il doit être exécutable (bah oui…), et appelé en ligne de commande de la manière suivante :

java -jar monJar.jar [-cp jars du classpath]

Nous sommes dans une appli AIR, donc évitons de surcharger la commande à appeler avec un classpath, aussi je vous suggère d’utiliser les fonctionnalités d’export complets d’un JAR (si les licences associées vous l’autorisent) intégrant au final l’ensemble des sources dans un groooos JAR contenant tout ce qu’il lui faut pour fonctionner.
Notes :

  • Les utilisateurs de Maven peuvent utiliser le plugin qui va bien pour ça.
  • L’export eclipse, c’est pas mal non plus, il suffit de cocher la « case qui va bien » au moment d’exporter

Depuis un NativeProcess

On voit mieux ce qui se dessine:

  1. Définir l’exécutable java dans vos « startup infos »
  2. Définir les arguments
    • -jar
    • /chemin/vers/monJar.jar
  3. Ajouter les listeners permettant de savoir lorsque l’exécution est terminée
  4. Lancer le native process

Restons multi plateforme

Java est multi plateforme, oui, mais l’exécutable dépend de la plateforme, aussi il vous faudra récupérer le chemin vers votre JAVA_HOME (contenant le répertoire bin avec l’exécutable Java).
Vous avez donc deux solutions maintenant:

  1. La plus simple (pour le développeur) : demander à votre gentil utilisateur où est son JAVA_HOME. Ça peut le faire si c’est un geek velu qui a compris qu’on ne parlait pas de venir danser chez lui (aïe, elle est rude celle-ci)
  2. La plus compliquée (mais pratique pour l’utilisateur) : utiliser un autre exécutable (voire NativeProcess) qui remonte cette information ou permette de pointer directement sur l’exécutable en question

La seconde solution bien que séduisante est un peu plus longue à implémenter.
Pour les développeurs sous Windows, j’ai noté l’utilisation par Serge Jespers d’un petit (mais pratique) exécutable « jexe.exe » lançant le java.exe présent dans le %JAVA_HOME%\bin de la machine. L’utiliser vous évitera la première solution, sans vous lancer dans un développement spécifiquement dédié à cela (merci Serge). En revanche, si votre application est diffusée à un public d’utilisateurs non avertis, ils sont tout à fait susceptibles de ne pas avoir de variable JAVA_HOME définie sur leur machine : pensez donc à déterminer précisément votre cible avant de choisir une solution.

J’ai également noté en relisant un extrait de la licence du JRE que la redistribution est autorisée sous certaines conditions. Si vous les remplissez, vous pourriez carrément embarquer le runtime java dans votre application AIR, au risque de la faire gonfler en taille…

Un exemple ! Un exemple !

Voici un petit exemple de code permettant de lancer un JAR exécutable depuis une application AIR :

package com.lafabrick.nativeprocess
{
import flash.desktop.NativeProcess;
import flash.desktop.NativeProcessStartupInfo;
import flash.events.EventDispatcher;
import flash.events.NativeProcessExitEvent;
import flash.events.ProgressEvent;
import flash.filesystem.File;
import flash.system.Capabilities;

public class JarExec
{
protected var _process:NativeProcess;

public function JarExec()
{
}

public function getDefaultJavaExecutable():File
{
if (Capabilities.os.toLowerCase().indexOf("win") > -1)
{
return File.applicationDirectory.resolvePath("native/jexe.exe");
}
if (Capabilities.os.toLowerCase().indexOf("mac") > -1)
{
// TODO: Changer avec un paramètre de configuration
return new File("/System/Library/Frameworks/JavaVM.framework/Commands/java");
}
throw new Error("Système non pris en charge");
return null;
}

public function testJava(executablePath:String = null):void
{
// Tester le lancement de l'exécutable 'java'
try
{
var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
nativeProcessStartupInfo.workingDirectory = File.applicationDirectory;
nativeProcessStartupInfo.executable = (executablePath == null ? getDefaultJavaExecutable() : new File(executablePath));

var args:Vector.<String> = new Vector.<String>();
args.push("-version");
nativeProcessStartupInfo.arguments = args;

_process = new NativeProcess();
_process.addEventListener(NativeProcessExitEvent.EXIT, onJavaTestExit);
_process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onStandardOut);
_process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, onErrorOut);
_process.start(nativeProcessStartupInfo);

trace("-- Début d'exécution");
}
catch (e:Error)
{
trace("Impossible de lancer le process\n" + e.message + "\n" + e.getStackTrace());
}
}
protected function onStandardOut(e:ProgressEvent):void
{
trace("STDOUT: " + _process.standardOutput.readUTFBytes(_process.standardOutput.bytesAvailable));
}
protected function onErrorOut(e:ProgressEvent):void
{
trace("STDERR: " + _process.standardError.readUTFBytes(_process.standardError.bytesAvailable));
}
protected function onJavaTestExit(e:NativeProcessExitEvent):void
{
trace("-- Fin d'exécution");
}
}
}

Cet extrait donne un aperçu, mais vous pouvez vous lancer (soyons fous) à installer le package suivant :


Pour Mac

Pour Windows

Pensez au clic droit > View Source…

Packager l’application

Comme je le disais au début de ce post, il est nécessaire de packager votre application dans le format natif du système d’exploitation visé. Vous avez deux solutions pour cela :

  1. Passer par la dernière version de Flash Builder 4 qui propose cette fonctionnalité
  2. Utiliser l’application PackageAssistantPro proposée par Serge Jespers (re merci Serge)

Il s’agit d’une application AIR utilisant des NativeProcess (et packagée en ligne de commande vu que l’application n’existait pas avant… Remarquez, la version 2 et plus ont probablement été packagées avec la version 1… Mais je m’égare)

Quelques liens


Tags: , , , , , ,


6 commentaires ...

» RSS des commentaires
  1. Antonin /
    -->

    Merci pour cet article ! J’ai beaucoup aimé la blague sur le JAVA_HOME :P

  2. Hervé /
    -->

    Geek un jour… :)

  3. loudoweb /
    -->

    moi je préfère la dernière blague sur le packager qui package le packager de la version supérieur :D
    merci pour cet article très précis que je redécouvre au moment où j’en ai besoin ;)

  4. Palleas /
    -->

    Effectivement, c’est une des première fonctionnalités que j’ai testé quand j’ai eu accès à la bêta de AIR 2.0 :3

    > Si vous les remplissez, vous pourriez carrément embarquer le runtime java dans votre application AIR, au risque de la faire gonfler en taille…

    J’ai joué comme ça pour lancer une instance de MongoDB au lancement de l’application, et plutôt que de l’embed, je l’ai téléchargé au premier lancement de l’application… Le problème d’avoir une grosse application en fait, c’est que AIR fait (il me semble, je prends des pincettes, ne me tapez pas si je me goure) un hash du checksum de l’application pour sécuriser le EncryptedLocalStore, par exemple. Du coup application trop grosse = checksum qui met longtemps = hash qui fait chauffer la machine = univers qui implose (si, si).

  5. Hervé /
    -->

    @loudoweb Héhé. Ravi de donner un coup de main

    @Palleas Merci pour ce retour d’XP, ça montre les limites du système. J’imagine que dans ces cas de figure il est préférable de travailler par socket par exemple sans embarquer l’application, voire faire un installer complètement différent…

  6. Mario Vieira /
    -->

    good stuff. Coenrats.org has an example running TomCat, yours running a Jar, soon I will post one on Apache and we keep it rolling ;)


Sois pas timide...


Bad Behavior has blocked 271 access attempts in the last 7 days.