Eine IDE, sie zu knechten
Apps gleichzeitig für Android und iOS entwickeln
Crossplattform-Entwicklungssysteme erzeugen Anwendungen, die auf verschiedenen Betriebssystemen gleichermaßen nutzbar sind. Das erweitert nicht nur die Zahl der potenziellen Nutzer, sondern rüstet Apps auch für zukünftige Umwälzungen. Dieser Artikel stellt einige Entwicklungsumgebungen vor, von kostenlos bis teamfähig teuer.
Kernighan und Richie postulierten zwar in ihrem Standardwerk zu C, dass ein „sauber“ aufgebautes C-Programm auf verschiedenen Plattformen ohne große Modifikationen kompilierbar ist, aber ganz so einfach ist es dann doch nicht. Schnell war klar, dass ein Framework nötig ist, eine Abstraktionsschicht zum Kapseln der Eigenheiten der Betriebssysteme und GUIs.
Diese Frameworks müssen im Mobilbereich ob der häufigeren Wechsel der dominierenden Systeme – Palm OS, Symbian, Blackberry, Windows CE/Mobile, Android, iOS – und ob der wachsenden Fähigkeiten der Smartphones flexibler sein als für Desktops mit der Jahrzehnte alten Dominanz von Windows. Zudem bekommen Webanwendungen als weitere Plattform eine zunehmende Bedeutung. Wir werfen einen Blick auf Qt, RAD Studio von Embarcadero, Microsofts Xamarin für .Net, Adobe PhoneGap und seinen Open-Source-Kollegen Cordova, Telerik NativeScript, Facebook React Native, NS Basic und Unity. Googles Neuerscheinung Flutter stellen wir in einem künftigen Artikel vor.
Eine Hoffnung erfüllt keines dieser Crossplattform-Frameworks: Sie ersparen den Entwicklern nicht ganz, sich mit dem Host-Betriebssystem auseinanderzusetzen. Wenn nicht wenigstens einer im Entwicklerteam versteht, wie Android und iOS funktionieren, wenn niemand zumindest grundlegend mit der nativen Toolchain umgehen kann, wird das Projekt im Laufe der Entwicklung über kurz oder lang mit Karacho gegen eine Wand donnern.
Ein Beispiel dafür sind die Manifest-Dateien, in denen unter anderem Details zur Rechtevergabe stecken. Die Crossplattform-Tools erzeugen zwar brauchbare Varianten, aber in der Praxis führen speziellere Anforderungen immer wieder zu nicht lauffähigen Programmen. Nur wer das Sicherheitsmodell des jeweiligen Betriebssystems kennt, kann die Fehlermeldungen auswerten. Aber auch die Human Interface Design Guidelines, in denen die Betriebssystem-Entwickler darlegen, was sie sich beim Design des GUI-Stacks gedacht haben, sind einen Blick wert.
Mit Brachialgewalt
Wirkliche Popularität erreichte die Crossplattform-Entwicklung dank Nokia. Die Finnen hatten einst eine ganze Reihe von zu unterstützenden Betriebssystemen angesammelt: das als „abzuschießend“ markierte Java-basierte Series 40, Symbian, ein neues C++-basiertes Low-End-Betriebssystem, und Maemo oder später MeeGo für High-End-Geräte. Entwickler mussten nun ihr geistiges Eigentum im schlimmsten Fall viermal neu entwickeln – man kann sich die Begeisterungsstürme vorstellen. Zur Entschärfung dieser Problematik erwarb Nokia das hinter dem Crossplattform-Klassiker Qt stehende Unternehmen TrollTech.
Qt zeigt einige sehr interessante Aspekte von Crossplattform-Tools auf. Zur Laufzeit ist eine Qt-Applikation eine native App, die sich von einer normalen – also mit der Toolchain des Betriebssystem-Anbieters entwickelten – App nur dadurch unterscheidet, dass sie eine Gruppe zusätzlicher Bibliotheken mitbringt. Der Entwickler erzeugt seinen Code dabei unter Nutzung der Qt-Bibliotheken, die eine Abstraktionsschicht darstellen und diverse Komfortfunktionen bereitstellen.
Qt ist nicht nur aufgrund der umfangreichen Bibliothek interessant, die so ziemlich alle Einsatzzwecke von Netzwerkprogrammierung bis XML abdeckt. Zudem bringt Qt eine Vielzahl von Spracherweiterungen mit, die das Design robusterer Applikationen erleichtern, etwa das Signal-Slot-System, das die Koppelung reduziert.
Um einen normalen C++-Compiler nutzen zu können, verwendet Qt einen Trick, nämlich eine Meta-Kompilierumgebung, die aus einer Art Parser namens Moc besteht. Er analysiert das vom Entwickler geschriebene C++ und ersetzt proprietäre Elemente durch automatisch generierten Code. Als Lohn dieser Mühen entsteht standardkonformer Code, den Qt von der Toolchain des jeweiligen Betriebssystems in eine native Applikation umwandeln lässt.
Derartige „Transpilationsumgebungen“ haben den zusätzlichen Vorteil, dass Entwickler mit geringem Aufwand native Zellen einfügen können. Sie reduzieren natürlich die Portabilität des Gesamtprogramms, sodass man sie sinnvollerweise per Präprozessor oder konditioneller Kompilierung nur für unterstützte Plattformen einblendet.
Interpretation zur Laufzeit
Klassische Programmiersprachen werden kompiliert und liegen dann in Form von Maschinensprache vor. So nutzen sie die Ressourcen der Zielplattform maximal – Entwickler müssen aber für jede Hardwarearchitektur ein eigenes Kompilat anbieten. Problematisch war das beispielsweise in der Anfangszeit von Microsofts Mobilbetriebssystemen, denn Redmond unterstützte ARM-, MIPS-, und SH3-Prozessoren. Entwickler mussten nicht nur drei Kompilate bereitstellen, sondern sich auch mit Nutzern auseinandersetzen, die versehentlich ein inkompatibles Kompilat installiert haben.
Ein Weg zur Entschärfung sind interpretierende Programmiersprachen, etwa der Klassiker BASIC. Hier führt zur Laufzeit ein Interpretationsprogramm den Code aus. Das Anpassen an neue Architekturen beschränkt sich dann im Allgemeinen darauf, den Interpreter zu portieren – der Programmcode muss nicht wesentlich modifiziert werden.
Auch Java arbeitet so, ergänzt um einen Zwischenschritt: Weiterhin wühlt sich ein Compiler durch den Programmcode, er erzeugt nun aber einen maschinenunabhängigen Zwischencode. So fällt auch die Bindung an Java, denn den Zwischencode können auch Compiler anderer Programmiersprachen erzeugen. Moderne Zwischencode-Runtimes sind keine reinen Interpreter, sondern optimieren den Code und übersetzen ihn per Just-in-Time-Compiler (JIT) ganz oder teilweise in Maschinencode.
Doch das funktioniert alles nur leidlich. Das vom Java-Erfinder Sun beworbene „Write Once Run Anywhere“ wurde im Mobilbereich in „Write Once Debug Anywhere“ verballhornt. Die Ursache dafür war unter anderem die Eigenheit der Hersteller, die diversen Java-Spezifikationen auf sehr proprietäre Art und Weise auszulegen. Ein weiterer Nachteil ist, dass es wesentlich schwieriger ist, native Inseln einzubinden.
Letztlich setzte sich die von Sun eingeführte Trennung von Runtime und Entwicklercode nicht durch, um im Mobilbereich betriebssystemübergreifend zu entwickeln – das gelang Java nur am Desktop und auf manchen Prozessrechnern. Android hat zwar auch einen Java-Unterbau, aber nur zur Unterstützung verschiedener Prozessorarchitekturen.
Etwas anders geht Microsoft vor: .Net beziehungsweise Xamarin nutzt ebenfalls eine Zwischencode-Architektur, fasst aber Runtime und Zwischencode-Kompilat in einer monolithischen App zusammen, die dann in den Stores landet. Das mindert Kompatibilitätsprobleme und erlaubt das einfachere Einbinden von nativen Inseln.
Das Web im Mittelpunkt
Inzwischen laufen immer mehr auf JavaScript basierende Ausführungsumgebungen wie PhoneGap oder Facebooks React Native den Java- und .Net-Frameworks den Rang ab – komfortabel vor allem für Entwickler, die sowieso Webapps schreiben müssen. Die Ursache für diesen Erfolg ist die universelle Verfügbarkeit von Webbrowsern.
Praktisch jedes Mobil- und Embedded-Betriebssystem hat einen umfangreichen Browser, und weil so gut wie jede Webseite mindestens ein JavaScript-Framework lädt, müssen diese Browser eine JavaScript-VM mitbringen. Diese Browser stellen die Betriebssysteme den Apps zur Benutzung bereit – in Form eines WebView genannten Steuerelements, das sich in die Bedienoberfläche der App integriert. So bekommt jede App Zugriff auf einen JavaScript-Interpreter.
Das später von Adobe aufgekaufte Unternehmen Nitobi nutzt diese Technik zur Realisierung des portablen Frameworks PhoneGap beziehungsweise des quelloffenen Grundprojekts Cordova. Damit erzeugte Apps sind aus Sicht des Betriebssystems nativ. Sie bestehen aus einer WebView und einigen Ressourcen – die das eigentliche Programm als JavaScript-Code enthalten. Der Mehrwert des Frameworks besteht in diversen Werkzeugen, die den vom Entwickler erzeugten Code und die WebView-Steuerung zusammenfassen, kompilieren und in ein über einen App Store verbreitbares Format bringen. PhoneGap bietet darüber hinaus diverse Cloud-Dienste und einen in so gut wie allen Anwendungsfällen kostenpflichtigen Buildserver an, der iOS-Apps ohne Mac kompiliert.
WebViews führen zu drei Problemen: Erstens ist die Performance nicht immer optimal. Zweitens ist es schwer bis unmöglich, native Steuerelemente einzusetzen. Das ist insofern verkraftbar, als dass sich unter PhoneGap das jQuery UI als Standard etabliert hat, das den Nutzern von unzähligen anderen Apps bekannt sein dürfte. Schwerer mag das dritte Problem wiegen: das Einbinden von nativen Code-Inseln. Zwar bieten WebViews eine Möglichkeit, proprietäre JavaScript-Funktionen einzuschreiben, aber in der Praxis ist das alles andere als einfach.
Mit Dampf
Die zweite Generation von JavaScript-Frameworks findet eine andere Lösung für diese Nachteile. NativeScript und React Native kombinieren PhoneGap mit eigenen Runtimes auf Basis von Googles V8-JavaScript-Engine, die mittlerweile so extrem optimiert ist, dass sie mehr als genug Performance bietet.
Die Hersteller statten sie mit „Stubs“ aus, die dem Code die direkte Interaktion mit dem Betriebssystem ermöglichen. Das Nutzer-Interface entsteht nicht mehr in einer WebView, sondern die Runtime-Engine baut es zur Laufzeit aus nativen Steuerelementen auf. Die Apps sehen also wieder so aus wie von der jeweiligen Plattform gewohnt. Zudem stehen Schnittstellen zum Einbinden von nativem Code bereit.
Schwierig an dieser Lösung ist, die verschiedenen Bedienphilosophien der unterstützten Plattformen unter einen Hut zu bekommen. Dem begegnen die Frameworks durch hauseigene Metasprachen, in denen der Entwickler seine Bedienschnitstelle spezifiziert: NativeScript setzt auf einen XML-Dialekt, React Native auf JSX. In beiden Fällen kann man auch dynamische GUIs durch DOM-Transaktionen realisieren, was allerdings in Arbeit ausartet.
Lohnt es sich?
Für 3D-Spiele bieten diese Frameworks trotz aller Optimierungen und JIT-Kompiler zu wenig Performance. Die Entwicklung einer eigenen Spiele-Engine ist für normale Unternehmen wirtschaftlich nicht mehr tragbar. Hier hilft die Gaming-Engine Unity, die quasi nebenbei die Crossplattform-Tauglichkeit mitbringt.
Andersherum mag es für kleinere Portabilitäts-Projekte effizienter sein, nicht die ganze App in ein Framework zu pressen, sondern eine eigene kleine Lösung auf die Beine zu stellen. Diese implementiert man dann in C und bindet sie über die nativen Schnittstellen direkt in die plattformspezifischen Entwicklungsumgebungen ein.
Ein Vorteil von Crossplattform-Programmiersystemen liegt darin, dass sie Ihren Code gegen das Aufkommen und Untergehen von Betriebssystemen absichern. Zweitens erleichtern die Frameworks die von Kapitalgebern gerne geforderte Unterstützung von Android und iOS aus einer Codebasis.
Doch mitunter treibt man den Teufel mit dem Beelzebub aus. Statt vom Hersteller des Betriebssystems sind Sie dann von dem des Frameworks abhängig. Wer beispielsweise auf Embarcadero setzt, würde mehr als dümmlich schauen, wenn dort das vom Exvorstand begonnene Zerstörungswerk fortgesetzt und die Firma in den Boden gebohrt würde. Das gilt auch für Googles neues Android- und iOS-Framework Flutter, das noch zu neu ist für eine umfassende Beurteilung. Mehr dazu nach der Google I/O Anfang Mai.
Diese Abhängigkeit mindern Sie mit den quelloffenen Produkten. Eine besondere Rolle nimmt hierbei Qt ein, das über eine Stiftung gegen das Untergehen des Anbieters gesichert wurde.
Grenzen der Umsetzung
Im nächsten Schritt müssen Sie evaluieren, ob das Framework zur Bewältigung Ihrer Aufgabe geeignet ist. Bei klassischen Business-Apps ist dies im Allgemeinen problemlos gegeben. Auch spielt die Performance in Zeiten von Telefonen mit Achtkern-Prozessoren erfreulicherweise – außer bei 3D-Anwendungen – meist eine untergeordnete Rolle.
Haariger wird es, wenn Sie auf Cloud-Dienste, Hintergrund-Threads oder Notifications zurückgreifen müssen. Besonders bei Cloud-Ressourcen neigen Anbieter dazu, im Interesse der Ertragsoptimierung ihre eigenen Systeme zu pushen und Systeme von Drittanbietern oder des Entwicklers der Plattform in den Hintergrund zu stellen. Bei den Notifications und Hintergrunddiensten ist das Problem, dass die Abstraktionsschichten der Frameworks oft nur einen Teil der Möglichkeiten abdecken und schwer zu erweitern sind, wenn überhaupt.
Schlechte Kandidaten für die Nutzung von Crossplattform-Frameworks sind Systemwerkzeuge – alleine schon, weil der Bedarf mit der Plattform stark variiert. Zudem muss das Crossplattform-Framework in der Lage sein, alle benötigten Ereignisse zu exponieren und ausreichend Zugriff auf Hardware und System-Ressourcen zu gewähren.
Ein weiteres Problem ist das Einbinden nativer SDKs und Plug-ins von Drittanbietern. Die Arbeit mit nichtgrafischen Modulen funktioniert im Allgemeinen relativ leicht, sofern für alle Zielplattformen die jeweiligen SDKs existieren und als native Insel integrierbar sind.
Doch wenn die SDKs Steuerelemente anbieten wollen, artet das mitunter in erhebliche Arbeit aus, besonders bei PhoneGap und Qt. Beim an sich nativen Qt liegt die Schwierigkeit im von Nokia eingeführten Windowmanager Lighthouse, der zwischen dem eigentlichen Benutzerprogramm und dem Grafikstack liegt. Er entstand unter dem Eindruck des schwer beschränkten GUI-Stacks von Symbian, macht es nun aber fast unmöglich, native Steuerelemente einzubinden. Die von PhoneGap genutzten WebViews blenden aus Sicherheitsgründen keine nativen Steuerelemente über den gerenderten Webseiten ein. Interessanterweise erweisen sich die JavaScript-Frameworks mit Runtimes hier als kooperativer. Da ihre GUIs sowieso durch Wrapper entstehen, ist es einfach, ein weiteres Steuerelement zu paketieren.
Die nächste Fragestellung ist, ob die vorliegende Aufgabe für Ihr Unternehmen realisierbar ist. In der Praxis ist es besonders bei Kleinserien so, dass die Entwicklungskosten die Kosten des Frameworks um ein Mehrfaches übersteigen. Wenn Sie zusätzliche Entwickler benötigen, sollten Sie im Auge behalten, ob sich in Ihrer Umgebung ausreichend Entwickler befinden, die mit dem Produkt arbeiten können. Delphi beispielsweise: Die Sprache ist in Westeuropa so gut wie ausgestorben, erfreut sich in der Slowakei und Ungarn aber an Universitäten und technischen Hochschulen immenser Popularität.
Nach Android und iOS
Interessant ist auch die Innovationsbereitschaft des Framework-Herstellers, denn ein portierungsfauler Hersteller negiert die Vorteile der Crossplattform-Entwicklung wieder. Zwar dominieren momentan Android und iOS den Mobilmarkt, doch wer weiß, welche Bedeutung Googles Fuchsia erlangt oder welche Systeme vielleicht in Schwellenländern marktrelevant werden. Auch mögen Microsoft und Apple etwas im Köcher haben, um die Konvergenz von Desktop und Mobile mit Touch-fähigen Geräten nach vorne zu bringen – siehe Googles Chrome OS. Viel mehr, als einen Blick auf die Vergangenheit des Frameworks zu werfen, können Sie allerdings nicht tun.
Der Funktionsumfang auf Entwicklerseite unterscheidet sich mitunter von Plattform zu Plattform – Windows, macOS, Linux – wesentlich. So bietet Embarcadero unter Linux beispielsweise nur Delphi an, nicht aber C++. Der hauseigene GUI-Stack FireMonkey steht unter Linux zudem nur dann zur Verfügung, wenn man ein separates Paket dazukauft. Derartige Fußangeln im Lizenzierungsbereich sind durchaus häufig und betreffen besonders gerne das Deployment des generierten Codes in Embedded-Systemen.
Fazit
Die Entscheidung für und wider Crossplattform-Frameworks sollte nicht zwischen Tür und Angel getroffen werden, zu teuer wäre ein Umstieg. Das nachträgliche Portieren von einem Framework zum nächsten kommt einem kompletten Rewrite gleich.
In der Praxis stellen die Frameworks eine Steigerung der Produktivität dar: Die meisten Entwickler setzen sich nur mit der Crossplattform-Programmierumgebung tiefer auseinander, nur einzelne müssen sich ausführlich in Android und iOS einarbeiten. Zudem bieten viele Frameworks die Möglichkeit, auch eine Web-App oder eine Desktop-Version zu entwickeln – oder eine Variante für ein auf mittlerweile vielen Prozessrechnersystemen laufendes Linux. (jow@ct.de)