Plattformübergreifende mobile Applikationen mit JavaFX
Mit Veröffentlichung der ersten Version von JavaFX (2+) führte sogleich „going mobile“ die Hitlisten der am meisten gewünschten Features an. Die Vorstellung, auf einer Codebasis Applikationen für diverse Plattformen bereitstellen zu können (das berühmte WORA Paradigma – Write Once Run Anywhere) erscheint schon sehr verlockend.
Auf der JavaOne 2013 hat Oracle prototypisch gezeigt, dass es möglich ist, JavaFX-basierte Applikationen auf mobile Geräte, auf denen Android und iOS läuft, zu portieren. Danach zog sich Oracle aus dem Thema offiziell zurück und übergab den Code der Community.
Das daraufhin Ende 2013 von Johan Vos gestartete Projekt JavaFXPorts hatte es sich zunächst zur Aufgabe gemacht, JavaFX basierend auf OpenJFX nach Android zu portieren und gewann auf der JavaOne 2014 den Duke’s Choice Award.
Im Februar 2015 nun taten sich Johan’s Firma LodgOn (JavaFXPorts, JavaFX -> Android) und Trillian Mobile (das Unternehmen ehemals hinter RoboVM, JavaFX -> iOS) zusammen, um gemeinsam das Gradle JavaFX Plugin „javafxmobile-plugin“ zu veröffentlichen.
Dieses Plugin adressiert die drei Plattformen Desktop, Android und iOS soweit möglich auf einer gemeinsamen Codebasis. Wenn nötig, werden Plattformspezifika durch native Services ergänzt. Mit javafxmobile-plugin kann somit ein gemeisamens Projekt für alle Zielplattformen erstellt werden, aus dem haraus auf die jeweilige Plattform „deployed“ wird.
„javafxmobile-plugin“
Bietet Oracle bereits Java für Windows, Linux, Mac OS und ARM, so bietet JavaFXPorts einen Rahmen für JavaFX-Community-SDKs mit Fokus auf iOS und Android. Dabei spielt ein entscheidender Unterschied eine Rolle: iOS- und Android-Programme werden zu nativen Applikationen konvertiert, so dass diese auf gleicher Low-Level-Ebene agieren wie „normale“ mobile Anwendungen.
Es wird also keine Laufzeitumgebung mitgeliefert, in der Java-Bytecode interpretiert wird, sondern es werden Apple-AppStore- und Google-Play-Store-fähige Pakete erstellt. Dabei kommt für eine iOS-Applikation RoboVM und Xcode in Verbindung mit einem iOS-Developer-Account zum Tragen, für Android wird das Android SDK benötigt (Getting Started with JavaFXPorts).
Projektstruktur
Die Projektstruktur sieht per Konvention folgendermaßen aus:
Plattformunabhängig (main)
src/main/java
Allgemeiner Java-Code
src/main/resources
Allgemeine Ressourcen
Android (android)
src/android/java
Android-Java-Code
src/android/res
Android-Assets
src/android/resources
Android-Ressourcen
iOS (ios)
src/ios/java
iOS-Java-Code
src/ios/resources
iOS-Ressourcen
src/ios/assets
iOS-Assets
Desktop (desktop)
src/desktop/java
Desktop-Java-Code
src/desktop/resources
Desktop-Ressourcen
Ressourcen und Assets?
src/…/resources
werden automatisch zum Classpath hinzugefügt.
src/…/res/ src/…/assets
werden wie plattformspezifische Ressourcen behandelt.
Gradle Tasks
Als Build-Tool kommt bei JavaFXPorts Gradle zum Einsatz, und es wird eine Anzahl an plattformspezifischen Build-Tasks bereitgestellt:
Desktop
run
startet die Applikation in der Desktop-Umgebung
Android
android
generiert ein Android „apk“ im Verzeichnis build/javafxports/android
androidInstall
installiert das generierte „apk“ auf ein angeschlossenes Android-Gerät
iOS
ios
generiert ein iOS „ipa“ im Verzeichnis build/javafxports/ios
launchIOSDevice
startet (und installiert) die Applikation auf einem angeschlossenen iOS-Gerät
launchIPhoneSimulator
startet die Applikation in einem iPhone-Simulator
launchIPadSimulator
startet die Applikation in einem iPad-Simulator
Gluon
Vor einiger Zeit startete ein neues Projekt: Gluon mit dem Ziel, die Welten Desktop, Android und iOS in einer Projektstruktur zu vereinen und rund um das Thema „Entwicklung plattformübergreifender Applikationen“ Frameworks und Services anzubieten. Dabei sollen nach und nach Plugins für die gängigsten IDEs bereitgestellt werden. Derzeit ist ein Gluon-Plugin für NetBeans verfügbar, das über das NetBeans-Plugins-Portal sehr einfach installiert werden kann.
Nach der Installation des Plugins steht für neue JavaFX-Projekte das Template „Basic Gluon Application“ zur Verfügung, das eine Gluon-, bzw. JavaFXPorts- Projektstruktur erstellt:
Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren
YouTube immer entsperren
Benötigt die neue Applikation keinerlei gerätespezifische Zugriffe, kann der gesamte Code im gemeinsamen Bereich abgelegt und eine reguläre JavaFX-Anwendung erstellt werden.
In der allermeisten Fällen wird dies jedoch nicht ausreichen. Bereits beim Zugriff auf das Dateisystem wird es spezifisch (aus Gluon Charm Down):
Desktop
1public File getPrivateStorage() {
2 String home = System.getProperty("user.home");
3 File f = new File(home, ".gluon");
4 if (!f.isDirectory()) {
5 f.mkdirs();
6 }
7 return f;
8}
Android
1public File getPrivateStorage() {
2 return FXActivity.getInstance().getFilesDir();
3}
iOS
1public File getPrivateStorage() {
2 NSFileManager fm = NSFileManager.getDefaultManager();
3 NSURL libdir = fm.getURLForDirectory(
4 NSSearchPathDirectory.LibraryDirectory,
5 NSSearchPathDomainMask.UserDomainMask, null, false);
6 File appPrivateDir = new File(libdir.getPath() + "/Caches/gluon");
7 if (!appPrivateDir.exists()) {
8 appPrivateDir.mkdirs();
9 }
10 return appPrivateDir;
11}
Auch für den Zugriff auf weitere Ressourcen und Dienste des Gerätes oder der Plattform wie Netzwerk/Internet, Adressbuch, Kalender, Audio/Video, GPS, Kamera etc. wird man auf native Implementierungen zugreifen.
Dabei ist es aus Entwicklersicht sicher wünschenswert, wenn sich der Zugriff auf gerätespezifische Ressourcen möglichst transparent und einheitlich gestaltet.
Gluon Charm Down
Dieses Ziel hat sich die vor Kurzem in der Version 0.0.1 freigegebene OSS-Zusatzbibliothek Gluon Charm Down gesetzt.
Damit kann dann via
1PlatformFactory.getPlatform()
die für die jeweilige Platform die passende Implementierung einer „Platform
“ angefordert werden.
Somit bekommt man durch
1File path = PlatformFactory.getPlatform().getPrivateStorage();
den jeweils gültigen Dateipfad für applikationspezifische Dateien.
Ähnliches gilt zum Beispiel auch für den Anwendungsstatus. Charm Down sieht hier eine CallBack-Schnittstelle vor, die es ermöglicht, auf die LifecycleEvents START, PAUSE, RESUME, STOP zu reagieren, zum Beispiel:
1public class IosPlatformProvider implements PlatformProvider {
2
3 private final BooleanProperty stop = new SimpleBooleanProperty();
4 private final BooleanProperty pause = new SimpleBooleanProperty();
5
6 {
7 PlatformFactory.getPlatform().setOnLifecycleEvent((LifecycleEvent param) -> {
8 switch (param) {
9 case START:
10 pause.set(false);
11 stop.set(false);
12 break;
13 case PAUSE:
14 pause.set(true);
15 break;
16 case RESUME:
17 pause.set(false);
18 stop.set(false);
19 break;
20 case STOP:
21 stop.set(true);
22 break;
23 }
24 return null;
25 });
26 }
27
28 @Override
29 public BooleanProperty stopProperty() {
30 return stop;
31 }
32
33 @Override
34 public BooleanProperty pauseProperty() {
35 return pause;
36 }
37}
Und das funktioniert wirklich?
Ja.
Es ist sicherlich noch einiges zu tun, aber zumindest hat der derzeitige Stand der Tools für José Perada und mich gereicht, um das JavaFX-Spiel 2048FX von Bruno Borges fit zu machen für Google Play (Android) und den Apple Store (iOS).
Die in den App Stores veröffentlichte Version liegt quelloffen bei Github .
Weitere Beiträge
von Jens Deters
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Jens Deters
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.