2015-04-18

Android-Apps im Browser

Auf der Google IO 2014 wurde ein Proof of Concept gezeigt, der einige Android-Apps unter Chrome OS lauffähig machte. Im September desselben Jahres folgte dann eine erste Beta-Version der App Runtime for Chrome. Die Software basiert auf Googles Sandbox-Lösung Native Client. Seit einiger Zeit kann über den Chrome Web Store die Beta-Version einer App namens ARC Welder geladen werden. Das englische Verb to weld bedeutet schweißen – passend dazu zeigt das Programmsymbol den kleinen grünen Roboter mit Schutzbrille. Das Tool integriert eine .apk-Datei in die Laufzeitumgebung. Wie man hierzu vorgeht, ist auf den Chrome-Entwicklerseiten beschrieben. Als Appetithappen ein paar Screenshots:

ARC Welder Setup (1)
ARC Welder Setup (1)

ARC Welder Setup (2)
ARC Welder Setup (2)

ARC Welder Setup (3)
ARC Welder Setup (3)

Meine App TKWeek im Browser
Meine App TKWeek im Browser

Was Google mit ARC noch vorhat, ist nicht ganz klar. Tatsache ist, dass Besitzern von Chromebooks auf einen Schlag eine Unmenge an Programmen zur Verfügung stehen würde, wenn sie Apps direkt aus dem Play Store herunterladen könnten. Das Gleiche gilt für Windows-, Linux- und Mac OS X-Anwendern, die Chrome als Browser nutzen. Das Bundlen einiger weniger, vorausgewählter Apps würde doch den ganzen Aufwand kaum rechtfertigen, oder? Derzeit müssen .apk-Dateien einzeln installiert werden. Um ein Programm in die Laufzeitumgebung zu übernehmen, muss also das Installationsarchiv vorliegen. Dies wird Google dem klassischen Endanwender nicht zumuten wollen.

Note to myself: Ich muss bei Gelegenheit mal herausfinden, ob sich ARC-Apps kennen, oder jeweils in ihrer eigenen Welt leben.

Die Ausführungsgeschwindigkeit von TKWeek war auf meinem Surface 3 Pro übrigens recht gut. Dass ARC noch mit Stabilitätsproblemen kämpft, ist angesichts des Beta-Status völlig ok. Wie auch der Android-Emulator kämpft ARC derzeit mit der Hardwareanbindung: es stehen nur sehr wenige Sensoren zur Verfügung.

2015-04-14

Seifenkisten auf Abwegen

Der Zugriff auf (üblicherweise) leichtgewichtige REST-Webservices ist unter Android kein Problem – sein Java-Erbe verhilft dem kleinen grünen Roboter  zu einem ansehnlichen (Netz)Werk(zeug)-Koffer. Nur mit den (meist) etwas schwergewichtigeren SOAP-Webservices will es “out of the box” nicht so recht klappen. Zwar hat Java mit JAX-WS ab Java SE 6 entsprechende Klassenbibliotheken an Board, aber Androids Harmony-Spin-off ist irgendwie bei Java 1.5 stehen geblieben (naja, skurrile Erweiterungen und Portierungen mal außen vor gelassen). Glücklicherweise gibt es zu jedem Java Specification Request eine Referenzimplementierung. Bis einschließlich JAX-WS RI 2.2.6 ist “nur” Java SE 5 erforderlich. Warum also nicht einfach die Runtime-Bibliotheken mit einer Android-App ausliefern, und auf diese Weise auf SOAP-Services zugreifen können? Der Abschnitt Jar dependency der Release Notes listet auf, welche Bibliotheken der App hinzugefügt werden müssen. Eigentlich könnte man das Unterfangen an dieser Stelle abbrechen, denn die 18 Jars bringen üppige 4,8 MB auf die Waage. Wer möchte das schon als zusätzlichen Ballast mitgeben, “nur” um einen Webservice aufzurufen? Aber falls der Forscherdrang siegt, und man die Jars in das libs-Verzeichnis des Android-Projekts kopiert, wartet die nächste unangenehme Überraschung.

Screenshot: Fehlermeldung beim Hinzufügen eines Jars
Screenshot: Fehlermeldung beim Hinzufügen eines Jars

Google nennt den Versuch, Jars hinzuzufügen, die bestimmte Paket-Namensräume abdecken, “Ill-advised or mistaken usage of a core class” und unterbindet die Integration. Fairerweise muss man sagen, dass die Motivation durchaus legitim ist. Bestimmte Paketnamen sind eben der Standardklassenbibliothek vorbehalten. Schade nur, wenn sie dann nicht vorhanden sind. Glücklicherweise lässt Google dem Entwickler ein Hintertürchen offen, in Gestalt der Option --core-library, die man dem monierenden Tool (dex) übergeben kann. Wie, ist freilich kaum herauszubekommen. Ganz so einfach geht das nämlich nicht, wenn man mit Hilfe von Gradle baut. Hier ein Rezept, das zumindest bei mir funktioniert. In der Modul-spezifischen build.gradle-Datei ist folgendes Quelltextfragment einzufügen:

   dexOptions {  
     preDexLibraries = false  
   }  
   project.tasks.withType(com.android.build.gradle.tasks.Dex) {  
     additionalParameters=['--core-library']  
   }  

Jetzt klappt der Build zwar immer noch nicht, aber zumindest erhält man eine andere Fehlermeldung.

Screenshot: Fehlermeldung beim Build
Screenshot: Fehlermeldung beim Build

Auch dies bekommt man in den Griff, indem man build.gradle abermals erweitert:

   packagingOptions {  
     exclude "META-INF/LICENSE"  
   }  
   

Nun lässt sich das Projekt bauen und starten. Leider klappt der Zugriff auf den Webservice aber trotzdem nicht. In der Konsole findet man die Meldung, dass die Klasse java.awt.Image nicht gefunden wird.

Screenshot: Klasse java.awt.Image nicht gefunden
Screenshot: Klasse java.awt.Image nicht gefunden

Tja, schade. Wieder ein guter Zeitpunkt, das ganze sein zu lassen.

Zwinkerndes Smiley

Aber hey, warum nicht weiter forschen…? Dass es eigentlich nicht funktionieren kann, “einfach so” eine entsprechende Klasse zu bauen, ist logisch. Aber vielleicht lässt sich auf diese Weise ja herausfinden, was von java.awt.Image benötigt wird. Eine leere Implementierung lässt den Start-Vorgang dann auch etwas später abbrechen. Diesmal fehlt javax.activation.DataHandler. Diese Klasse gehört zum JavaBeans Activation Framework. Hierzu könnten Sie dessen activation.jar in das libs-Verzeichnis kopieren. Ich spare mir das und lege stattdessen wieder eine Dummy-Klasse an. Warum, sehen Sie gleich…

Screenshot: Fehler beim Initialisieren der Klasse DatatypeFactory
Screenshot: Fehler beim Initialisieren der Klasse DatatypeFactory

Diesmal schlägt das Initialisieren der Klasse DatatypeFactory fehl, weil eine andere (die Implementierungsklasse) nicht vorhanden ist:

Screenshot: Die Klasse org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl ist nicht vorhanden
Screenshot: Die Klasse org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl ist nicht vorhanden

Das ist sogar vergleichsweise einfach erklärbar, weil es so spezifiziert wurde. Die Doku der Klasse DatatypeFactory beschreibt den Initialisierungsvorgang, beim Aufruf der Methode newInstance(). Dort ist auch Folgendes zu lesen: “Note that you must supply your own implementation (such as Xerces); Android does not ship with a default implementation.” Die Idee ist nun, den Teil von Xerces hinzuzufügen, der die fehlende Klasse enthält. Es gibt sogar ein passendes Jar, xercesImpl.jar. Wiederstehen Sie aber der Versuchung, die Datei in das libs-Verzeichnis zu kopieren. Dann nämlich gibt es eine Fehlermeldung, die meiner Meinung nach mit dem zu alten Klassenformat der Binärdistribution zu tun hat. Der Bau von Xerces aus den Sourcen klappt auch nicht so ohne Weiteres, weil weitere Libs aus dem Apache-Fundus vonnöten sind.

Irgendwie frustrierend, oder? Es scheint einen vermeintlichen Rettungsanker zu geben, in Gestalt einer Android-Portierung von Xerces. Aber auch die hilft nicht, weil alle Paketnamen mit dem Präfix mf. versehen wurden. Deshalb findet DatatypeFactory die benötigte Implementierung DatatypeFactoryImpl nicht (sie befindet sich ja im Paket mf.org.apache.xerces.jaxp.datatype). Wenn Sie jetzt sagen “Dann nutze ich eben die Übergabe zum Beispiel mit System.setProperty()”, muss ich Sie wieder enttäuschen. Dann gibt es zur Laufzeit eine Exception, weil die Implementierung nicht von der originalen DatatypeFactory ableitet, sondern der aus einem Paket, das mit mf. beginnt.

Wie geht es jetzt weiter? Nun, ich habe als letzten Versuch die wenigen benötigten Klassen aus der Xerces-Source-Distribution in das Projekt kopiert. Damit wird tatsächlich alles gefunden. Glauben Sie, es funktioniert?

Screenshot: Noch eine Exception
Screenshot: Noch eine Exception

Man könnte natürlich noch analysieren, was es mit den IllegalAnnotationExceptions auf sich hat, aber darauf hatte ich dann doch keine Lust mehr. JAX-WS und Android mögen sich also offensichtlich nicht besonders gerne. Wie man aber doch noch auf SOAP-Services zugreifen kann, lesen Sie in einer der kommenden KaffeeKlatsch-Ausgaben.

2015-04-05

Outtakes, 20150405

Wussten Sie, dass Sie ab Android 5.1 android.text.format.Time nicht weiter verwenden sollten? Die Klasse war als Alternative zu java.util.Calendar und java.util.GregorianCalendar gedacht. Ein Time-Objekt repräsentiert einen Zeitpunkt, der sekundengenau angegeben werden kann. Die Klasse ist nicht threadsicher und berücksichtigt keine Schaltsekunden. Zudem sind mehrere Problemen bekannt: Für die Berechnung werden derzeit 32-Bit-Integerzahlen verwendet. Deshalb können nur Werte zwischen 1902 und 2037 verlässlich dargestellt werden. Ferner führt der Aufruf von switchTimezone() zu einem falschen Datum, wenn der aktuelle Wert aufgrund eines Sommerzeit-/Winterzeit-Übergangs gar nicht vorkommen kann. Schließlich funktionieren Parse- und Formatieroperationen nur mit dem ASCII-Zeichensatz zuverlässig. Google rät deshalb dazu, stattdessen GregorianCalendar zu verwenden. Skurril…

PS: Nicht wundern - dass ich für Klassen aus der Java-Standardklassenbibliothek Links in die Android-Doku setze, ist gewollt