iOS RoboVM – eine Native Cross Plattform

I mean, Android is Java, so there is a lot of opportunity here for sharing code between Android and iOS (Niklas Therning). Dieses Zitat des Erfinders von RoboVM aus einem Artikel auf Jaxenter.com möchte ich als Einstieg für meine Beschreibung nehmen, wie eine iOS-Applikation mit Java-Code realisiert werden kann.

RoboVM als Native Cross-Plattform

Das RoboVM Open-Source-Projekt ermöglicht die Ausführung von Java und anderen JVM-Sprachen auf der iOS-Plattform, also beispielsweise dem iPhone oder dem iPad. RoboVM ist aber nicht nur auf Java beschränkt. ScalaClojure und Kotlin sind einige der anderen JVM-basierten Sprachen, die mit RoboVM funktionieren. In diesem Blog konzentrieren wir uns jedoch auf die Kombination von Java-Code und RoboVM.

Bei dieser Gelegenheit möchte ich kurz darauf hinweisen, dass die von RoboVM verwendete Java-Runtime auf der Android Java-Runtime basiert und damit nicht alle Java-Features unterstützt werden.

Unabhängig davon – der Vorteil, iOS-Apps auf Java-Basis umsetzen zu können, liegt auf der Hand: Java-Entwickler können ihre Fähigkeiten einbringen, ohne neue Sprachen oder Tools lernen zu müssen. Diese Native Cross-Plattform-Entwicklung schlägt die Brücke, um in Java die nativen Cocoa Touch APIs nutzen zu können.

Wie funktioniert RoboVM?

Doch wie läuft das Ganze nun technisch? Nun, damit der Code funktioniert, werden die Java-Klassen in Bytecode kompiliert. RoboVM verwendet einen sogenannten Ahead-of-Time Compiler (AOT), welcher auf LLVM basiert und den Java Bytecode in nativen ARM- oder x86-Maschienencode übersetzt. Ohne gesonderten Interpreter kann dieser Code dann auf der Ziel-CPU ausgeführt werden – neben iOS-Geräten auch auf Linux-, OS-X- oder x86-Systemen.

Wie bekomme ich RoboVM zum Laufen? 

Um RoboVM in der Entwicklung nutzen zu können, braucht es folgende technische Voraussetzungen: Man benötigt einen Mac mit installiertem Xcode inklusive zugehörigem Kommandozeilen-Tool, Java, Eclipse und natürlich die aktuellste Version von RoboVM.

In den RoboVM JAR-Dateien sind die Cocoa Bindings enthalten, wodurch ein Zugriff auf die nativen Funktionenvon iOS gewährleistet wird. Es ist erforderlich, dass diese JARs (Cocoa Bindings) auch im Klassenpfad enthalten sind.

Es gibt verschiedene Integrationsmöglichkeiten, um den Code zu bauen und auf einem iOS auszuführen.

        alternativ:

Das RoboVM Eclipse Plugin vereinfacht das Aufsetzen einer in Java geschriebenen App, die CocoaTouch API verwendet, sowie das Deployment der App auf dem iOS-Simulator bzw. -Device.

Mit Rechtsklick auf das RoboVM Projekt Run As -> iOS Device App auf einem angeschlossenen Gerät ausführen oder mit Run As -> iOS Simulator-App auf dem iOS-Simulator laufen lassen (siehe Abbildung).

iOS Grundzüge in RoboVM

Um eine RoboVM-App zu schreiben, die native iOS-Oberflächenelemente wie ein TableView, d.h. eine Liste mit mehreren Einträgen beinhaltet, braucht man Vorkenntnisse aus der iOS-Entwicklung. Die Logik der iOS-App-Entwicklung findet sich hier wieder.

RoboVM kopiert das Programmiermodell von iOS nach Java. Es besteht im Kern aus Bindings für Cocoa (vgl. robovm-ios-bindings), die es erlauben, Cocoa-APIs aus Java heraus aufzurufen.

Methodennamen aus Objective-C wurden für RoboVM im Rahmen eines semi-automatisierten Prozesses übersetzt. Das heißt, mit einem Tool, das Objective-C Header-Dateien und eine Konfigurationsdatei liest und Java-Klassendateien daraus erzeugt. Methodennamen wurden dabei so geändert, dass sie für Java-Programmierer natürlicher aussehen. Ein Beispiel sind UITableViewController Bindings. Mehr zu dem Thema Bindings finden Sie hier.

Der Unterschied liegt im Detail

In der iOS Welt entsteht ein Table View mit einer Table View API. Hier ein Ausschnitt benötigter Methoden zur Konfiguration des TableViews:

// Tells the data source to return the number of rows in a given section of a table view
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
// Asks the data source for a cell to insert in a particular location of the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
// Asks the data source to return the number of sections in the table view
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

Die Klasse IUTableView übernimmt den Aufbau, die Darstellung und die Steuerung der Tabelle.  
Jedes TableView besitzt ein Delegate und eine Datenquelle.

Mit Delegates überträgt man Aufgaben auf eine Instanz einer anderen Klasse – den Delegate-Empfänger. Welche Aufgaben - das wird im Delegate definiert. Durch Delegates wird der Delegate-Empfänger über Ereignisse eines Objektes informiert.

Ein Delegate implementiert das Protokoll UITableViewDelegate und stellt Methoden für die Ereignisverarbeitung des TableViews bereit. Mit der Klasse  UITableViewController stellt das UIKit eine Basis für Tabellenansichten zur Verfügung. 

Die Datenquelle implementiert das Protokoll UITableViewDataSource und liefert die Anzahl der Abschnitte sowie die Anzahl der Zeilen pro Abschnitt. Sie ist dafür zuständig, die Views für die Zellen der Tabelle zu erzeugen und zu konfigurieren [Klaus M. Rodewig, Clemens Wagner].

TableViews sind dahingehend optimiert, dass nur so viele Zellen benötigt und deren Daten angefordert werden, wie auf einmal auf dem Bildschirm dargestellt werden können. Auch beim Scrollen werden demnach immer nur einzelne Zellen von der Datenquelle angefordert. Es gibt einen internen Queue-Mechanismus, der  es ermöglicht, dass TableViews auch Datenmengen anzeigen können, deren Speicherbedarf wesentlich größer ist als der verfügbare Hauptspeicher des Geräts. Die Methode tableView:cellForRowAtIndexPath: (RoboVM Methode: getRowCell) gibt die einzelne Zelle der Tabelle zurück.

Wie das Aussehen und das Verhalten eines Views einer nativen iOS App verändert werden kann, wird ausführlich auf der Developer-Seite von Apple beschrieben.

Angelehnte Methodennamen schaffen Vereinfachung

Bei der Table View Programmierung mit Java-Code werden Tabellen in Java nach der gleichen Logik wie in Objective-C aufgebaut: das TableView wird dabei benutzt, um alle Einträge gleichartiger Daten anzuzeigen. Als Beispiel dafür habe ich eine kleine Kontakte-App auf meinem GitHub-Account unter https://github.com/Kourtessia/RoboVM-for-iOS bereitgestellt. In dieser App nutze ich das TableView, um alle Kontaktpersonen (gleichartigen Daten) anzuzeigen. Die folgende Abbildung zeigt Screenshots aus der App.

Als erste Tabelle wird die Address Book-Tabelle mit den Zellen „female“ und „male“ angezeigt.  Nach Auswahl des Geschlechts wird man zur entsprechenden Tabelle navigiert, welche die weiblichen oder männlichen Kontakte auflistet. Über den Menüpunkt „Address Book“ landet man wieder in der ersten Tabelle.

Im Java-Code lassen sich vom Namen her an Objective-C angelehnte Methoden aufrufen, um so programmatisch eine Benutzeroberfläche eines TableViews zu erzeugen, die wie eine echte Oberfläche einer Objective-C-App aussieht 

In der unten abgebildeten Java-Klasse MaleGenderTableViewController.java lehnen sich alle Methoden, hinsichtlich Logik und Namensgebung, an die Objective-C Funktionen an. Es sind:

getNumberOfRowsInSection liefert ddie Anzahl der Zellen (hier fünf) innerhalb eines Abschnitts des TableView.

- getNumberOfSections legt  fest, in wie viele Abschnitte (hier einen) man die Zellen unterteilt.

- didSelectRow (Objective-C: tableView:didSelectRowAtIndexPath)  Wenn Sie eine Zelle im Tableview berühren, ruft das TableView die Delegate-Methode tableView:didSelectRowAtIndexPath: auf, die den Root View Controller  (den in der Hierarchie ersten ViewController -hier die erste Maske) auf den Navigationstack schiebt.

Und spätestens an dieser Stelle: bei der Implementierung der RoboVM Methode: getRowCell,  die die einzelnen Zellen der Tabelle mit den Inhalten befüllt, kann man beliebige Daten aus beliebigem Java Code anbinden.

public class MaleGenderTableViewController extends UITableViewController
{
public long getNumberOfRowsInSection(UITableView tableView, @MachineSizedSInt
long section)
{
return 5;
}
public UITableViewCell getRowCell(UITableView tableView, NSIndexPath indexPath)
{
int row = (int) NSIndexPathExtensions.getRow(indexPath);
UITableViewCell cell = new UITableViewCell(new CGRect(0, 0, 300, 60));
if (row == 0)
{
// setContentOfCell
}
return cell;
}
public @MachineSizedSInt
long getNumberOfSections(UITableView tableView)
{
return 1;
}
public @MachineSizedFloat
double getRowHeight(UITableView tableView, NSIndexPath indexPath)
{
return 90;
}
public void didSelectRow(UITableView tableView, NSIndexPath indexPath)
{
getNavigationController().popToRootViewController(true);
}
}

Eine Klasse als zentraler Dreh- und Angelpunkt

Zentrale Klasse einer RoboVM-App ist die, die das Interface UIApplicationDelegateAdapter implementiert. Sie verarbeitet die App-Ereignisse, wie z.B. den Übergang in den aktiven Zustand. Die Implementierung des UIApplicationDelegateAdapter in der Start-Methode meiner Kontakte App findet sich in der Klasse RoboVMTableViewApplicationDelegateRoboVMTableViewApplicationDelegate ist das Delegate eines Objekts der Klasse UIApplication (org.robovm.apple.uikit.UIApplication), welches die Applikation repräsentiert.

Sobald dieses Objekt ein Ereignis registriert, informiert es sein Delegate – im vorliegenden Beispiel alsoRoboVMTableViewApplicationDelegate über dieses Ereignis. UIApplication wird in der „main“-Funktion der App aufgerufen und startet diese.

Wie in der iOS-Welt über die Delegate-Methode application:didFinishLaunchingWithOptions: so wird auch in der Kontakte App die Methode didFinishLaunching von der App im Rahmen des Starts aufgerufen. Zu diesem Zeitpunkt ist die Applikation noch inaktiv. Die Methode ruft das Applikationsobjekt auf, nachdem das Betriebssystem die App gestartet hat. Dabei ist irrelevant ob der Benutzer auf das Icon geklickt oder auf eine eingehende Benachrichtigung (push notification) reagiert hat. Die Information über den Grund des Starts wird der App in der MethodedidFinishLaunching über den Parameter „launchOptions“ übergeben. In der Methode wird auf das entsprechende Ereignis reagiert - als erste Maske, in unserem Beispiel GenderListTableViewController angezeigt. 

Jeder so genannte UITableViewController bzw. UIViewController kennt den Navigations-Controller in dem er enthalten ist. Mit navigationController.addStrongRef(ViewController);  wird ein ViewController auf den Stapel gelegt und angezeigt. Die Methode weist dem Fenster der Applikation einen ViewController zu (hier: GenderListTableViewController) und bringt es über die Nachricht makeKeyAndVisible zur Anzeige (RoboVM: window.makeKeyAndVisible();).

public class RoboVMTableViewApplicationDelegate extends UIApplicationDelegateAdapter
{
private UIWindow window;
@Override
public boolean didFinishLaunching(UIApplication application, NSDictionary<NSString, ?> launchOptions)
{
window = new UIWindow(UIScreen.getMainScreen().getBounds());
GenderListTableViewController genderListTableViewController = new GenderListTableViewController();
UINavigationController navigationController = new UINavigationController(genderListTableViewController);
navigationController.addStrongRef(genderListTableViewController);
navigationController.setDelegate(new UINavigationControllerDelegateAdapter()
{
});
window.setRootViewController(navigationController);
window.setBackgroundColor(UIColor.colorWhite());
window.makeKeyAndVisible();
return true;
}
public static void main(String[] args)
{
NSAutoreleasePool pool = new NSAutoreleasePool();
UIApplication.main(args, null, RoboVMTableViewApplicationDelegate.class);
pool.close();
}
}

RoboVM- ein Framework vor dem Durchbruch?

RoboVM ist Finalist des JAX Innovation Awards 2014 in der Kategorie „Most Innovative Open Technology“ (http://jax.de/awards2014/robovm).

Auch wenn sich mit RoboVM Java auf iOS ausführen lässt, steckt das Framework bei datenbasierten Apps noch ziemlich in den Kinderschuhen. Ein anderes Fazit ergibt sich für Spiele-Apps. Dank LibGDX  wird RoboVM bereits heute erfolgreich eingesetzt, um Android-Spiele in Java zu schreiben, auf iOS zu portieren und dann im AppStore bereit zu stellen. Aktuell finden sich bereits ca. 50 Apps, die so umgesetzt wurden.

RoboVM sollte auf dem Radar bleiben

Mein Fazit fällt zwiegespalten aus. Nicht nur ist die Dokumentation zu RoboVM derzeit noch sehr lückenhaft, selbst einfachste Operationen wie ein einfacher Klick werden in der aktuellen Version mit massiver Verzögerung durchgeführt. Der Grund hierfür wird von den Entwicklern von RoboVM gerade analysiert. Auch weil es derzeit noch keinen Release-Build von RoboVM gibt, rate ich aktuell von einem produktiven Projekteinsatz ab – empfehle aber dringend, diese spannende OpenSource-Technologie weiter zu verfolgen, da ihr Potenzial beachtenswert erscheint.

Aus genau diesem Grund, und in der Hoffnung, euer Interesse geweckt zu haben, wird euch sicher die Ankündigung freuen, dass in ca. 2 Wochen ein weiterer Blog von mir aus der Serie „JavaFX App auf iOS mit RoboVM und Maven“ erscheint. Dort werde ich dann aufzeigen wie und ob eine JavaFX-Applikation mit RoboVM auf einem iOS-Device läuft.

Roadmap für RoboVM

Final schließe ich diese Betrachtung von RoboVM mit einem Ausblick

  • eine stabile Version (1.0) im dritten oder Anfang des vierten Quartals 2014
  • RoboVM sollte dann größtenteils fehlerfrei sein
  • RoboVM wird derzeit getestet mit iOS 8
  • möglichst vollständige Bindings für CocoaTouch
  • eine Dokumentation ist ein wichtiges Ziel

Links:

The official blog for RoboVM: http://blog.robovm.org
RoboVM Eclipse plugin: http://marketplace.eclipse.org/content/robovm-eclipse
RoboVM open-source project : http://www.robovm.com
https://www.facebook.com/robovm

RoboVM Cocoa Touch Bindings: http://maven-repository.com/artifact/org.robovm/robovm-cocoatouch
RoboVM Maven artifactshttp://mvnrepository.com/artifact/org.robovm
Kontakte App:  https://github.com/Kourtessia/RoboVM-for-iOS
Apps entwickeln für iPhone und iPad: http://openbook.galileocomputing.de/apps_entwickeln_fuer_iphone_und_ipad